diff --git a/Cargo.toml b/Cargo.toml index 74b40dabf..c668d1a8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,9 @@ default = ["notosans"] [dependencies] cgmath = { version = "0.17", features = ["serde"] } -conrod_core = "0.68" -conrod_winit = "0.68" -conrod_vulkano = "0.68" +conrod_core = { git = "https://github.com/mitchmindtree/conrod", branch = "conrod_wgpu" } +conrod_wgpu = { git = "https://github.com/mitchmindtree/conrod", branch = "conrod_wgpu" } +conrod_winit = { git = "https://github.com/mitchmindtree/conrod", branch = "conrod_wgpu" } daggy = "0.6" find_folder = "0.3" image = "0.22" @@ -32,21 +32,15 @@ serde = "1" serde_derive = "1" serde_json = "1" toml = "0.5" -vulkano = "0.16" -vulkano-win = "0.16" -vulkano-shaders = "0.16" walkdir = "2" -winit = "0.19" +wgpu = "0.4" +winit = "0.21" [dev-dependencies] audrey = "0.2" nannou_audio = "0.2" nannou_laser = "0.3" nannou_osc = "0.1" -shade_runner = "0.3" - -[target.'cfg(target_os = "macos")'.dependencies] -moltenvk_deps = "0.1" # --------------- Nannou Examples [[example]] diff --git a/examples/simple_window.rs b/examples/simple_window.rs index bf71e3d4e..c4b7cfec2 100644 --- a/examples/simple_window.rs +++ b/examples/simple_window.rs @@ -13,7 +13,7 @@ fn model(app: &App) -> Model { // Create a new window! Store the ID so we can refer to it later. let _window = app .new_window() - .with_dimensions(512, 512) + .with_size(512, 512) .with_title("nannou") .view(view) // The function that will be called for presenting graphics to a frame. .event(event) // The function that will be called when the window receives events. diff --git a/src/app.rs b/src/app.rs index d6eaa3c9b..8529bbf0b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -12,12 +12,11 @@ use crate::draw; use crate::event::{self, Event, Key, LoopEvent, Update}; -use crate::frame::{Frame, RawFrame, RenderData}; +use crate::frame::{Frame, RawFrame}; use crate::geom; use crate::state; use crate::time::DurationF64; -use crate::ui; -use crate::vk::{self, DeviceOwned, GpuFuture}; +//use crate::ui; use crate::window::{self, Window}; use find_folder; use std; @@ -29,9 +28,7 @@ use std::sync::atomic::{self, AtomicBool}; use std::sync::Arc; use std::time::{Duration, Instant}; use winit; - -#[cfg(all(target_os = "macos", not(test)))] -use moltenvk_deps as mvkd; +use winit::event_loop::ControlFlow; /// The user function type for initialising their model. pub type ModelFn = fn(&App) -> Model; @@ -66,11 +63,7 @@ pub struct Builder { update: Option>, default_view: Option>, exit: Option>, - vk_instance: Option>, - vk_debug_callback: Option, create_default_window: bool, - #[cfg(all(target_os = "macos", not(test)))] - moltenvk_settings: Option, } /// The default `model` function used when none is specified by the user. @@ -91,22 +84,14 @@ fn default_model(_: &App) -> () { /// - **All windows** for graphics and user input. Windows can be referenced via their IDs. pub struct App { config: RefCell, - pub(crate) vk_instance: Arc, - pub(crate) events_loop: winit::EventsLoop, + pub(crate) event_loop_window_target: Option, + pub(crate) event_loop_proxy: Proxy, pub(crate) windows: RefCell>, draw_state: DrawState, - pub(crate) ui: ui::Arrangement, + //pub(crate) ui: ui::Arrangement, /// The window that is currently in focus. pub(crate) focused_window: RefCell>, - /// Indicates whether or not the events loop is currently asleep. - /// - /// This is set to `true` each time the events loop is ready to return and the `LoopMode` is - /// set to `Wait` for events. - /// - /// This value is set back to `false` each time the events loop receives any kind of event. - pub(crate) events_loop_is_asleep: Arc, - /// The current state of the `Mouse`. pub mouse: state::Mouse, /// State of the keyboard keys. @@ -146,14 +131,6 @@ pub struct App { pub time: DrawScalar, } -// /// Graphics related items within the `App`. -// pub struct Graphics { -// } -// -// /// Items related to the presence of one or more windows within the App. -// pub struct Windowing { -// } - /// Miscellaneous app configuration parameters. #[derive(Debug)] struct Config { @@ -171,20 +148,14 @@ struct Config { pub struct Draw<'a> { window_id: window::Id, draw: RefMut<'a, draw::Draw>, - renderer: RefMut<'a, RefCell>, - depth_format: vk::Format, + renderer: RefMut<'a, RefCell>, } // Draw state managed by the **App**. #[derive(Debug)] struct DrawState { draw: RefCell>, - renderers: RefCell>>, - // The supported depth format for each device. - // - // Devices are mapped via their `index`, which is stated to "never change" according to the - // vulkano docs. - supported_depth_formats: RefCell>, + renderers: RefCell>>, } /// The app uses a set scalar type in order to provide a simplistic API to users. @@ -195,44 +166,24 @@ pub type DrawScalar = geom::scalar::Default; /// A handle to the **App** that can be shared across threads. This may be used to "wake up" the /// **App**'s inner event loop. +#[derive(Clone)] pub struct Proxy { - events_loop_proxy: winit::EventsLoopProxy, - events_loop_is_asleep: Arc, -} - -// The type returned by a mode-specific run loop within the `run_loop` function. -struct Break { - model: M, - reason: BreakReason, -} - -// The reason why the mode-specific run loop broke out of looping. -enum BreakReason { - // The application has exited. - Exit, - // The LoopMode has been changed to the given mode. - NewLoopMode(LoopMode), + event_loop_proxy: winit::event_loop::EventLoopProxy<()>, + // Indicates whether or not the events loop is currently asleep. + // + // This is set to `true` each time the events loop is ready to return and the `LoopMode` is + // set to `Wait` for events. + // + // This value is set back to `false` each time the events loop receives any kind of event. + event_loop_is_asleep: Arc, } // State related specifically to the application loop, shared between loop modes. -struct LoopContext { - // The user's application event function. - event_fn: Option>, - // The user's update function. - update_fn: Option>, - // The user's default function for drawing to a window's swapchain's image, used in the case - // that the user has not provided a window-specific view function. - default_view: Option>, - // The moment at which `run_loop` began. +struct LoopState { + updates_since_event: usize, loop_start: Instant, - // A buffer for collecting polled events. - winit_events: Vec, - // The last instant that `update` was called. Initialised to `loop_start`. last_update: Instant, - // The instant that the loop last ended. Initialised to `loop_start`. - last_loop_end: Instant, - // The number of updates yet to be called (for `LoopMode::Wait`). - updates_remaining: usize, + total_updates: u64, } /// The mode in which the **App** is currently running the event loop and emitting `Update` events. @@ -249,7 +200,7 @@ pub enum LoopMode { /// /// 3. Check the time and sleep for the remainder of the `update_interval` then go to 1. /// - /// `view` is called at an arbitraty rate by the vulkan swapchain for each window. It uses + /// `view` is called at an arbitraty rate by the vulkan swap chain for each window. It uses /// whatever the state of the user's model happens to be at that moment in time. Rate { /// The minimum interval between emitted updates. @@ -284,7 +235,7 @@ pub enum LoopMode { update_interval: Duration, }, - /// Synchronises `Update` events with requests for a new image by the swapchain for each + /// Synchronises `Update` events with requests for a new image by the swap chain for each /// window in order to achieve minimal latency between the state of the model and what is /// displayed on screen. This mode should be particularly useful for interactive applications /// and games where minimal latency between user input and the display image is essential. @@ -301,16 +252,16 @@ pub enum LoopMode { /// last `Update` occurred, so as long as all time-based state (like animations or physics /// simulations) are driven by this, the `update` interval consistency should not cause issues. /// - /// ### The Swapchain + /// ### The Swap Chain /// - /// The purpose of the swapchain for each window is to synchronise the presentation of images + /// The purpose of the swap chain for each window is to synchronise the presentation of images /// (calls to `view` in nannou) with the refresh rate of the screen. *You can learn more about /// the swap chain /// [here](https://vulkan-tutorial.com/Drawing_a_triangle/Presentation/Swap_chain).* RefreshSync { /// The minimum duration that must occur between calls to `update`. Under the `RefreshSync` /// mode, the application loop will attempt to emit an `Update` event once every time a new - /// image is acquired from a window's swapchain. Thus, this value is very useful when + /// image is acquired from a window's swap chain. Thus, this value is very useful when /// working with multiple windows in order to avoid updating at an unnecessarily high rate. /// /// We recommend using a `Duration` that is roughly half the duration between refreshes of @@ -354,11 +305,7 @@ where update: None, default_view: None, exit: None, - vk_instance: None, - vk_debug_callback: None, create_default_window: false, - #[cfg(all(target_os = "macos", not(test)))] - moltenvk_settings: None, } } @@ -380,10 +327,6 @@ where default_view, exit, create_default_window, - vk_instance, - vk_debug_callback, - #[cfg(all(target_os = "macos", not(test)))] - moltenvk_settings, .. } = self; Builder { @@ -393,10 +336,6 @@ where default_view, exit, create_default_window, - vk_instance, - vk_debug_callback, - #[cfg(all(target_os = "macos", not(test)))] - moltenvk_settings, } } } @@ -460,69 +399,6 @@ where self } - /// The vulkan instance to use for interfacing with the system vulkan API. - /// - /// If unspecified, nannou will create one via the following: - /// - /// ```norun - /// # use nannou::prelude::*; - /// # fn main() { - /// vk::InstanceBuilder::new() - /// .build() - /// .expect("failed to creat vulkan instance") - /// # ; - /// # } - /// ``` - /// - /// If a `vk_debug_callback` was specified but the `vk_instance` is unspecified, nannou - /// will do the following: - /// - /// ```norun - /// # use nannou::prelude::*; - /// # fn main() { - /// vk::InstanceBuilder::new() - /// .extensions(vk::InstanceExtensions { - /// ext_debug_report: true, - /// ..vk::required_windowing_extensions() - /// }) - /// .layers(vec!["VK_LAYER_LUNARG_standard_validation"]) - /// .build() - /// .expect("failed to creat vulkan instance") - /// # ; - /// # } - /// ``` - pub fn vk_instance(mut self, vk_instance: Arc) -> Self { - self.vk_instance = Some(vk_instance); - self - } - - /// Specify a debug callback to be used with the vulkan instance. - /// - /// If you just want to print messages from the standard validation layers to stdout, you can - /// call this method with `Default::default()` as the argument. - /// - /// Note that if you have specified a custom `vk_instance`, that instance must have the - /// `ext_debug_report` extension enabled and must have been constructed with a debug layer. - pub fn vk_debug_callback(mut self, debug_cb: vk::DebugCallbackBuilder) -> Self { - self.vk_debug_callback = Some(debug_cb); - self - } - - #[cfg(all(target_os = "macos", not(test)))] - /// Set custom settings for the moltenvk dependacy - /// installer. - /// - /// Install silently - /// `Install::Silent` - /// Install with callbacks - /// `Install::Message(message)` - /// - /// See moltenvk_deps::Message docs for more information. - pub fn macos_installer(mut self, settings: mvkd::Install) -> Self { - self.moltenvk_settings = Some(settings); - self - } - /// Build and run an `App` with the specified parameters. /// /// This function will not return until the application has exited. @@ -532,58 +408,19 @@ where /// initialised on the main thread. pub fn run(mut self) { // Start the winit window event loop. - let events_loop = winit::EventsLoop::new(); - - // Keep track of whether or not a debug cb was specified so we know what default extensions - // and layers are necessary. - let debug_callback_specified = self.vk_debug_callback.is_some(); - - #[cfg(all(target_os = "macos", not(test)))] - let moltenvk_settings = self.moltenvk_settings; - - // The vulkan instance necessary for graphics. - let vk_instance = self.vk_instance.take().unwrap_or_else(|| { - if debug_callback_specified { - let vk_builder = vk::InstanceBuilder::new(); - - #[cfg(all(target_os = "macos", not(test)))] - let vk_builder = vk::check_moltenvk(vk_builder, moltenvk_settings); - - #[cfg(any(not(target_os = "macos"), test))] - let vk_builder = vk_builder.extensions(vk::required_windowing_extensions()); - - vk_builder - .add_extensions(vk::InstanceExtensions { - ext_debug_utils: true, - ..vk::InstanceExtensions::none() - }) - .layers(vec!["VK_LAYER_LUNARG_standard_validation"]) - .build() - .expect("failed to create vulkan instance") - } else { - let vk_builder = vk::InstanceBuilder::new(); - - #[cfg(all(target_os = "macos", not(test)))] - let vk_builder = vk::check_moltenvk(vk_builder, moltenvk_settings); - - #[cfg(any(not(target_os = "macos"), test))] - let vk_builder = vk_builder.extensions(vk::required_windowing_extensions()); - - vk_builder - .build() - .expect("failed to create vulkan instance") - } - }); - - // If a callback was specified, build it with the created instance. - let _vk_debug_callback = self.vk_debug_callback.take().map(|builder| { - builder - .build(&vk_instance) - .expect("failed to build vulkan debug callback") - }); + let event_loop = winit::event_loop::EventLoop::new(); + + // Create the proxy used to awaken the event loop. + let event_loop_proxy = event_loop.create_proxy(); + let event_loop_is_asleep = Arc::new(AtomicBool::new(false)); + let event_loop_proxy = Proxy { + event_loop_proxy, + event_loop_is_asleep, + }; // Initialise the app. - let app = App::new(events_loop, vk_instance).expect("failed to construct `App`"); + let event_loop_window_target = Some(EventLoopWindowTarget::Owned(event_loop)); + let app = App::new(event_loop_proxy, event_loop_window_target); // Create the default window if necessary if self.create_default_window { @@ -604,31 +441,14 @@ where } } - // If the loop mode was set in the user's model function, ensure all window swapchains are + // If the loop mode was set in the user's model function, ensure all window swap chains are // re-created appropriately. let loop_mode = app.loop_mode(); if loop_mode != LoopMode::default() { + let present_mode = window::preferred_present_mode(&loop_mode); let mut windows = app.windows.borrow_mut(); for window in windows.values_mut() { - let capabilities = window - .surface - .capabilities(window.swapchain.device().physical_device()) - .expect("failed to get surface capabilities"); - let min_image_count = capabilities.min_image_count; - let user_specified_present_mode = window.user_specified_present_mode; - let user_specified_image_count = window.user_specified_image_count; - let (present_mode, image_count) = window::preferred_present_mode_and_image_count( - &loop_mode, - min_image_count, - user_specified_present_mode, - user_specified_image_count, - &capabilities.present_modes, - ); - if window.swapchain.present_mode() != present_mode - || window.swapchain.num_images() != image_count - { - change_loop_mode_for_window(window, &loop_mode); - } + // TODO: Update window for loop/present mode. } } @@ -657,10 +477,6 @@ impl Builder<(), Event> { default_view: Some(View::Sketch(view)), exit: None, create_default_window: true, - vk_instance: None, - vk_debug_callback: None, - #[cfg(all(target_os = "macos", not(test)))] - moltenvk_settings: None, }; builder.run() } @@ -756,7 +572,7 @@ impl LoopMode { impl Default for LoopMode { fn default() -> Self { - LoopMode::refresh_sync() + LoopMode::rate_fps(60.0) } } @@ -780,65 +596,34 @@ impl App { // Create a new `App`. pub(super) fn new( - events_loop: winit::EventsLoop, - vk_instance: Arc, - ) -> Result { + event_loop_proxy: Proxy, + event_loop_window_target: Option, + ) -> Self { let windows = RefCell::new(HashMap::new()); let draw = RefCell::new(draw::Draw::default()); let config = RefCell::new(Default::default()); let renderers = RefCell::new(Default::default()); - let supported_depth_formats = RefCell::new(HashMap::new()); - let draw_state = DrawState { - draw, - renderers, - supported_depth_formats, - }; + let draw_state = DrawState { draw, renderers }; let focused_window = RefCell::new(None); - let ui = ui::Arrangement::new(); + //let ui = ui::Arrangement::new(); let mouse = state::Mouse::new(); let keys = state::Keys::default(); let duration = state::Time::default(); let time = duration.since_start.secs() as _; - let events_loop_is_asleep = Arc::new(AtomicBool::new(false)); let app = App { - vk_instance, - events_loop, - events_loop_is_asleep, + event_loop_proxy, + event_loop_window_target, focused_window, windows, config, draw_state, - ui, + //ui, mouse, keys, duration, time, }; - Ok(app) - } - - /// A reference to the vulkan instance associated with the `App`. - /// - /// If you would like to construct the app with a custom vulkan instance, see the - /// `app::Builder::vk_instance` method. - pub fn vk_instance(&self) -> &Arc { - &self.vk_instance - } - - /// Returns an iterator yielding each of the physical devices on the system that are vulkan - /// compatible. - /// - /// If a physical device is not specified for a window surface's swapchain, the first device - /// yielded by this iterator is used as the default. - pub fn vk_physical_devices(&self) -> vk::instance::PhysicalDevicesIter { - vk::instance::PhysicalDevice::enumerate(&self.vk_instance) - } - - /// Retrieve the default vulkan physical device. - /// - /// This is simply the first device yielded by the `vk_physical_devices` method. - pub fn default_vk_physical_device(&self) -> Option { - self.vk_physical_devices().next() + app } /// Find and return the absolute path to the project's `assets` directory. @@ -963,21 +748,16 @@ impl App { /// /// This can be used to "wake up" the **App**'s inner event loop. pub fn create_proxy(&self) -> Proxy { - let events_loop_proxy = self.events_loop.create_proxy(); - let events_loop_is_asleep = self.events_loop_is_asleep.clone(); - Proxy { - events_loop_proxy, - events_loop_is_asleep, - } + self.event_loop_proxy.clone() } - /// A builder for creating a new **Ui**. - /// - /// Each **Ui** is associated with one specific window. By default, this is the window returned - /// by `App::window_id` (the currently focused window). - pub fn new_ui(&self) -> ui::Builder { - ui::Builder::new(self) - } + // /// A builder for creating a new **Ui**. + // /// + // /// Each **Ui** is associated with one specific window. By default, this is the window returned + // /// by `App::window_id` (the currently focused window). + // pub fn new_ui(&self) -> ui::Builder { + // ui::Builder::new(self) + // } /// Produce the **App**'s **Draw** API for drawing geometry and text with colors and textures. /// @@ -991,16 +771,8 @@ impl App { None => return None, Some(window) => window, }; - let queue = window.swapchain_queue().clone(); - let device = queue.device().clone(); - let color_format = crate::frame::COLOR_FORMAT; - let mut supported_depth_formats = self.draw_state.supported_depth_formats.borrow_mut(); - let depth_format = *supported_depth_formats - .entry(device.physical_device().index()) - .or_insert_with(|| { - find_draw_depth_format(device) - .expect("no supported vulkan depth format for the App's `Draw` API") - }); + + let texture_format = crate::frame::Frame::TEXTURE_FORMAT; let draw = self.draw_state.draw.borrow_mut(); draw.reset(); @@ -1011,14 +783,7 @@ impl App { let (px_w, px_h) = window.inner_size_pixels(); // TODO: This should be configurable. let glyph_cache_dims = [px_w, px_h]; - let renderer = draw::backend::vulkano::Renderer::new( - queue, - color_format, - depth_format, - msaa_samples, - glyph_cache_dims, - ) - .expect("failed to create `Draw` renderer for vulkano backend"); + let renderer = draw::backend::wgpu::Renderer; RefCell::new(renderer) }) }); @@ -1026,7 +791,6 @@ impl App { window_id, draw, renderer, - depth_format, }) } @@ -1060,18 +824,19 @@ impl App { impl Proxy { /// Wake up the application! /// - /// This wakes up the **App**'s inner event loop and inserts an **Awakened** event. + /// This wakes up the **App**'s inner event loop and causes a user event to be emitted by the + /// event loop. /// - /// The `app::Proxy` stores a flag in order to track whether or not the `EventsLoop` is + /// The `app::Proxy` stores a flag in order to track whether or not the `EventLoop` is /// currently blocking and waiting for events. This method will only call the underlying - /// `winit::EventsLoopProxy::wakeup` method if this flag is set to true and will immediately - /// set the flag to false afterwards. This makes it safe to call the `wakeup` method as - /// frequently as necessary across methods without causing any underlying OS methods to be - /// called more than necessary. - pub fn wakeup(&self) -> Result<(), winit::EventsLoopClosed> { - if self.events_loop_is_asleep.load(atomic::Ordering::Relaxed) { - self.events_loop_proxy.wakeup()?; - self.events_loop_is_asleep + /// `winit::event_loop::EventLoopProxy::send_event` method if this flag is set to true and will + /// immediately set the flag to false afterwards. This makes it safe to call the `wakeup` + /// method as frequently as necessary across methods without causing any underlying OS methods + /// to be called more than necessary. + pub fn wakeup(&self) -> Result<(), winit::event_loop::EventLoopClosed<()>> { + if self.event_loop_is_asleep.load(atomic::Ordering::Relaxed) { + self.event_loop_proxy.send_event(())?; + self.event_loop_is_asleep .store(false, atomic::Ordering::Relaxed); } Ok(()) @@ -1080,11 +845,7 @@ impl Proxy { impl<'a> Draw<'a> { /// Draw the current state of the inner mesh to the given frame. - pub fn to_frame( - &self, - app: &App, - frame: &Frame, - ) -> Result<(), draw::backend::vulkano::DrawError> { + pub fn to_frame(&self, app: &App, frame: &Frame) -> Result<(), draw::backend::wgpu::DrawError> { let window = app .window(self.window_id) .expect("no window to draw to for `app::Draw`'s window_id"); @@ -1096,9 +857,10 @@ impl<'a> Draw<'a> { self.window_id, frame.window_id(), ); - let dpi_factor = window.hidpi_factor(); + let scale_factor = window.scale_factor(); let mut renderer = self.renderer.borrow_mut(); - renderer.draw_to_frame(&self.draw, dpi_factor, frame, self.depth_format) + // TODO: renderer.draw_to_frame. + unimplemented!(); } } @@ -1126,16 +888,40 @@ pub fn find_assets_path() -> Result { .for_folder(App::ASSETS_DIRECTORY_NAME) } -/// Find a compatible depth format for the `App`'s `Draw` API. -pub fn find_draw_depth_format(device: Arc) -> Option { - let candidates = [ - vk::Format::D32Sfloat, - vk::Format::D32Sfloat_S8Uint, - vk::Format::D24Unorm_S8Uint, - vk::Format::D16Unorm, - vk::Format::D16Unorm_S8Uint, - ]; - vk::find_supported_depth_image_format(device, &candidates) +// This type allows the `App` to provide an API for creating new windows. +// +// During the `setup` before the +pub(crate) enum EventLoopWindowTarget { + // Ownership over the event loop. + // + // This is the state before the `EventLoop::run` begins. + Owned(winit::event_loop::EventLoop<()>), + // A pointer to the target for building windows. + // + // This is the state during `EventLoop::run`. This pointer becomes invalid following + // `EventLoop::run`, so it is essential to take care that we are in the correct state when + // using this pointer. + Pointer(*const winit::event_loop::EventLoopWindowTarget<()>), +} + +impl EventLoopWindowTarget { + // Take a reference to the inner event loop window target. + // + // This method is solely used during `window::Builder::build` to allow for + pub(crate) fn as_ref(&self) -> &winit::event_loop::EventLoopWindowTarget<()> { + match *self { + EventLoopWindowTarget::Owned(ref event_loop) => (&**event_loop), + EventLoopWindowTarget::Pointer(ptr) => { + // This cast is safe, assuming that the `App`'s `EventLoopWindowTarget` will only + // ever be in the `Pointer` state while the pointer is valid - that is, during the + // call to `EventLoop::run`. Great care is taken to ensure that the + // `EventLoopWindowTarget` is dropped immediately after `EventLoop::run` completes. + // This allows us to take care of abiding by the `EventLoopWindowTarget` lifetime + // manually while avoiding having the lifetime propagate up through the `App` type. + unsafe { &*ptr as &winit::event_loop::EventLoopWindowTarget<()> } + } + } + } } // Application Loop. @@ -1151,7 +937,7 @@ pub fn find_draw_depth_format(device: Arc) -> Option { // issue and ask! fn run_loop( mut app: App, - mut model: M, + model: M, event_fn: Option>, update_fn: Option>, default_view: Option>, @@ -1160,589 +946,369 @@ fn run_loop( M: 'static, E: LoopEvent, { + // Track the moment the loop starts. let loop_start = Instant::now(); - // Initialise the loop context. - let mut loop_ctxt = LoopContext { - event_fn, - update_fn, - default_view, + // Wrap the `model` in an `Option`, allowing us to take full ownership within the `event_loop` + // on `exit`. + let mut model = Some(model); + + // Take ownership of the `EventLoop` from the `App`. + let event_loop = match app.event_loop_window_target.take() { + Some(EventLoopWindowTarget::Owned(event_loop)) => event_loop, + _ => unreachable!("the app should always own the event loop at this point"), + }; + + // Keep track of state related to the loop mode itself. + let mut loop_state = LoopState { + updates_since_event: 0, loop_start, - winit_events: vec![], last_update: loop_start, - last_loop_end: loop_start, - updates_remaining: LoopMode::DEFAULT_UPDATES_FOLLOWING_EVENT, + total_updates: 0, }; - let mut loop_mode = app.loop_mode(); + // Run the event loop. + event_loop.run(move |event, event_loop_window_target, control_flow| { + // Set the event loop window target pointer to allow for building windows. + app.event_loop_window_target = Some(EventLoopWindowTarget::Pointer( + event_loop_window_target as *const _, + )); + + let mut exit = false; + + match event { + // Check to see if we need to emit an update and request a redraw. + winit::event::Event::MainEventsCleared => { + if let Some(model) = model.as_mut() { + let loop_mode = app.loop_mode(); + let now = Instant::now(); + let mut do_update = |loop_state: &mut LoopState| { + apply_update(&mut app, model, event_fn, update_fn, loop_state, now); + }; + match loop_mode { + LoopMode::Wait { + updates_following_event, + update_interval, + } => { + if loop_state.updates_since_event < updates_following_event { + let next_update = loop_state.last_update + update_interval; + if now >= next_update { + do_update(&mut loop_state); + } + } + } - // Begin running the application loop based on the current `LoopMode`. - 'mode: loop { - let Break { - model: new_model, - reason, - } = match loop_mode { - LoopMode::Rate { update_interval } => { - run_loop_mode_rate(&mut app, model, &mut loop_ctxt, update_interval) - } - LoopMode::Wait { - updates_following_event, - update_interval, - } => { - loop_ctxt.updates_remaining = updates_following_event; - run_loop_mode_wait( - &mut app, - model, - &mut loop_ctxt, - updates_following_event, - update_interval, - ) - } - LoopMode::NTimes { - number_of_updates, - update_interval, - } => { - loop_ctxt.updates_remaining = number_of_updates; - run_loop_mode_ntimes(&mut app, model, &mut loop_ctxt, update_interval) - } - LoopMode::RefreshSync { - minimum_update_interval, - windows, - } => run_loop_mode_refresh_sync( - &mut app, - model, - &mut loop_ctxt, - minimum_update_interval, - windows, - ), - }; + LoopMode::Rate { update_interval } => { + let next_update = loop_state.last_update + update_interval; + if now >= next_update { + do_update(&mut loop_state); + } + } - model = new_model; - match reason { - // If the break reason was due to the `LoopMode` changing, switch to the new loop mode - // and continue. - BreakReason::NewLoopMode(new_loop_mode) => { - loop_mode = new_loop_mode; - change_loop_mode(&app, &loop_mode); - } - // If the loop broke due to the application exiting, we're done! - BreakReason::Exit => { - if let Some(exit_fn) = exit_fn { - exit_fn(&app, model); + LoopMode::RefreshSync { + minimum_update_interval, + .. + } => { + let next_update = loop_state.last_update + minimum_update_interval; + if now >= next_update { + do_update(&mut loop_state); + } + } + + LoopMode::NTimes { + number_of_updates, + update_interval, + } => { + if loop_state.total_updates < number_of_updates as u64 { + let next_update = loop_state.last_update + update_interval; + if now >= next_update { + do_update(&mut loop_state); + } + } + } + } } - return; } - } - } -} -// Run the application loop under the `Rate` mode. -fn run_loop_mode_rate( - app: &mut App, - mut model: M, - loop_ctxt: &mut LoopContext, - mut update_interval: Duration, -) -> Break -where - M: 'static, - E: LoopEvent, -{ - loop { - // Handle any pending window events. - app.events_loop - .poll_events(|event| loop_ctxt.winit_events.push(event)); - for winit_event in loop_ctxt.winit_events.drain(..) { - let (new_model, exit) = - process_and_emit_winit_event(app, model, loop_ctxt.event_fn, winit_event); - model = new_model; - if exit { - let reason = BreakReason::Exit; - return Break { model, reason }; - } - } + // Request a frame from the user for the specified window. + // + // TODO: Only request a frame from the user if this redraw was requested following an + // update. Otherwise, just use the existing intermediary frame. + winit::event::Event::RedrawRequested(window_id) => { + // Take the render data and swapchain. + // We'll replace them before the end of this block. + let (mut render_data, mut swap_chain, nth_frame) = { + let mut windows = app.windows.borrow_mut(); + let window = windows + .get_mut(&window_id) + .expect("no window for redraw request ID"); + let render_data = window.frame_render_data.take(); + let swap_chain = window + .swap_chain + .swap_chain + .take() + .expect("missing swap chain"); + let nth_frame = window.frame_count; + window.frame_count += 1; + (render_data, swap_chain, nth_frame) + }; + + // The command buffer to be created. + let mut command_buffer = None; + + if let Some(model) = model.as_ref() { + let swap_chain_output = swap_chain.get_next_texture(); + let swap_chain_texture = &swap_chain_output.view; + + // Borrow the window now that we don't need it mutably until setting the render + // data back. + let windows = app.windows.borrow(); + let window = windows + .get(&window_id) + .expect("failed to find window for redraw request"); + + // Construct and emit a frame via `view` for receiving the user's graphics commands. + let raw_frame = RawFrame::new_empty( + &window.device, + &window.queue, + window_id, + nth_frame, + swap_chain_texture, + window.swap_chain.descriptor.format, + window.rect(), + ); + + // If the user specified a view function specifically for this window, use it. + // Otherwise, use the fallback, default view passed to the app if there was one. + let window_view = window.user_functions.view.clone(); + + if let Some(ref default_view) = default_view { + command_buffer = match window_view { + Some(window::View::Sketch(view)) => { + let r_data = render_data.take().expect("missing `render_data`"); + let frame = Frame::new_empty(raw_frame, r_data); + view(&app, &frame); + let (r_data, raw_frame) = frame.finish(); + render_data = Some(r_data); + Some(raw_frame.finish().finish()) + } + Some(window::View::WithModel(view)) => { + let r_data = render_data.take().expect("missing `render_data`"); + let frame = Frame::new_empty(raw_frame, r_data); + let view = view.to_fn_ptr::().expect( + "unexpected model argument given to window view function", + ); + (*view)(&app, &model, &frame); + let (r_data, raw_frame) = frame.finish(); + render_data = Some(r_data); + Some(raw_frame.finish().finish()) + } + Some(window::View::WithModelRaw(raw_view)) => { + let raw_view = raw_view.to_fn_ptr::().expect( + "unexpected model argument given to window raw_view function", + ); + (*raw_view)(&app, &model, &raw_frame); + Some(raw_frame.finish().finish()) + } + None => match default_view { + View::Sketch(view) => { + let r_data = render_data.take().expect("missing `render_data`"); + let frame = Frame::new_empty(raw_frame, r_data); + view(&app, &frame); + let (r_data, raw_frame) = frame.finish(); + render_data = Some(r_data); + Some(raw_frame.finish().finish()) + } + View::WithModel(view) => { + let r_data = render_data.take().expect("missing `render_data`"); + let frame = Frame::new_empty(raw_frame, r_data); + view(&app, &model, &frame); + let (r_data, raw_frame) = frame.finish(); + render_data = Some(r_data); + Some(raw_frame.finish().finish()) + } + }, + }; + } + } - // Update the app's durations. - let now = Instant::now(); - let since_last = now.duration_since(loop_ctxt.last_update).into(); - let since_start = now.duration_since(loop_ctxt.loop_start).into(); - app.duration.since_start = since_start; - app.duration.since_prev_update = since_last; - app.time = app.duration.since_start.secs() as _; - - // Emit an update event. - let update = update_event(loop_ctxt.loop_start, &mut loop_ctxt.last_update); - if let Some(event_fn) = loop_ctxt.event_fn { - let event = E::from(update.clone()); - event_fn(&app, &mut model, event); - } - if let Some(update_fn) = loop_ctxt.update_fn { - update_fn(&app, &mut model, update); - } + // TODO: Replace the render data and swap chain. + let mut windows = app.windows.borrow_mut(); + let window = windows + .get_mut(&window_id) + .expect("no window for redraw request ID"); - // Draw to each window. - for window_id in app.window_ids() { - acquire_image_and_view_frame(app, window_id, &model, loop_ctxt.default_view.as_ref()); - } + // Submit the command buffer to the queue. + if let Some(command_buffer) = command_buffer { + window.queue.submit(&[command_buffer]); + } - // Sleep if there's still some time left within the interval. - let now = Instant::now(); - let since_last_loop_end = now.duration_since(loop_ctxt.last_loop_end); - if since_last_loop_end < update_interval { - std::thread::sleep(update_interval - since_last_loop_end); - } - loop_ctxt.last_loop_end = Instant::now(); - - // See if the loop mode has changed. If so, break. - update_interval = match app.loop_mode() { - LoopMode::Rate { update_interval } => update_interval, - loop_mode => { - let reason = BreakReason::NewLoopMode(loop_mode); - return Break { model, reason }; + window.frame_render_data = render_data; + window.swap_chain.swap_chain = Some(swap_chain); } - }; - } -} -// Run the application loop under the `Wait` mode. -fn run_loop_mode_wait( - app: &mut App, - mut model: M, - loop_ctxt: &mut LoopContext, - mut updates_following_event: usize, - mut update_interval: Duration, -) -> Break -where - M: 'static, - E: LoopEvent, -{ - loop { - // First collect any pending window events. - app.events_loop - .poll_events(|event| loop_ctxt.winit_events.push(event)); - - // If there are no events and the `Ui` does not need updating, - // wait for the next event. - if loop_ctxt.winit_events.is_empty() && loop_ctxt.updates_remaining == 0 { - let events_loop_is_asleep = app.events_loop_is_asleep.clone(); - events_loop_is_asleep.store(true, atomic::Ordering::Relaxed); - app.events_loop.run_forever(|event| { - events_loop_is_asleep.store(false, atomic::Ordering::Relaxed); - loop_ctxt.winit_events.push(event); - winit::ControlFlow::Break - }); - } + // Ignore the remaining two non-I/O events. + winit::event::Event::RedrawEventsCleared | winit::event::Event::NewEvents(_) => {} - // If there are some winit events to process, reset the updates-remaining count. - if !loop_ctxt.winit_events.is_empty() { - loop_ctxt.updates_remaining = updates_following_event; - } - - for winit_event in loop_ctxt.winit_events.drain(..) { - let (new_model, exit) = - process_and_emit_winit_event(app, model, loop_ctxt.event_fn, winit_event); - model = new_model; - if exit { - let reason = BreakReason::Exit; - return Break { model, reason }; + // Track the number of updates since the last I/O event. + // This is necessary for the `Wait` loop mode to behave correctly. + ref _other_event => { + loop_state.updates_since_event = 0; } } - // Update the app's durations. - let now = Instant::now(); - let since_last = now.duration_since(loop_ctxt.last_update).into(); - let since_start = now.duration_since(loop_ctxt.loop_start).into(); - app.duration.since_start = since_start; - app.duration.since_prev_update = since_last; - app.time = app.duration.since_start.secs() as _; - - // Emit an update event. - let update = update_event(loop_ctxt.loop_start, &mut loop_ctxt.last_update); - if let Some(event_fn) = loop_ctxt.event_fn { - let event = E::from(update.clone()); - event_fn(&app, &mut model, event); + // Process the event with the users functions and see if we need to exit. + if let Some(model) = model.as_mut() { + exit |= process_and_emit_winit_event::(&mut app, model, event_fn, &event); } - if let Some(update_fn) = loop_ctxt.update_fn { - update_fn(&app, &mut model, update); - } - loop_ctxt.updates_remaining -= 1; - // Draw to each window. - for window_id in app.window_ids() { - acquire_image_and_view_frame(app, window_id, &model, loop_ctxt.default_view.as_ref()); - } - - // Sleep if there's still some time left within the interval. + // Set the control flow based on the loop mode. + let loop_mode = app.loop_mode(); let now = Instant::now(); - let since_last_loop_end = now.duration_since(loop_ctxt.last_loop_end); - if since_last_loop_end < update_interval { - std::thread::sleep(update_interval - since_last_loop_end); - } - loop_ctxt.last_loop_end = Instant::now(); - - // See if the loop mode has changed. If so, break. - match app.loop_mode() { + *control_flow = match loop_mode { LoopMode::Wait { - update_interval: ui, - updates_following_event: ufe, + updates_following_event, + update_interval, } => { - update_interval = ui; - updates_following_event = ufe; - } - loop_mode => { - let reason = BreakReason::NewLoopMode(loop_mode); - return Break { model, reason }; - } - }; - } -} - -// Run the application loop under the `NTimes` mode. -fn run_loop_mode_ntimes( - app: &mut App, - mut model: M, - loop_ctxt: &mut LoopContext, - mut update_interval: Duration, -) -> Break -where - M: 'static, - E: LoopEvent, -{ - loop { - // First collect any pending window events. - app.events_loop - .poll_events(|event| loop_ctxt.winit_events.push(event)); - - // If there are no events and the `Ui` does not need updating, - // wait for the next event. - if loop_ctxt.winit_events.is_empty() && loop_ctxt.updates_remaining == 0 { - let events_loop_is_asleep = app.events_loop_is_asleep.clone(); - events_loop_is_asleep.store(true, atomic::Ordering::Relaxed); - app.events_loop.run_forever(|event| { - events_loop_is_asleep.store(false, atomic::Ordering::Relaxed); - loop_ctxt.winit_events.push(event); - winit::ControlFlow::Break - }); - } - - // Update the app's durations. - let now = Instant::now(); - let since_last = now.duration_since(loop_ctxt.last_update).into(); - let since_start = now.duration_since(loop_ctxt.loop_start).into(); - app.duration.since_start = since_start; - app.duration.since_prev_update = since_last; - app.time = app.duration.since_start.secs() as _; - - for winit_event in loop_ctxt.winit_events.drain(..) { - let (new_model, exit) = - process_and_emit_winit_event(app, model, loop_ctxt.event_fn, winit_event); - model = new_model; - if exit { - let reason = BreakReason::Exit; - return Break { model, reason }; + if loop_state.updates_since_event < updates_following_event { + let next_update = loop_state.last_update + update_interval; + if now < next_update { + ControlFlow::WaitUntil(next_update) + } else { + ControlFlow::Poll + } + } else { + ControlFlow::Wait + } } - } - // Only `update` and `view` if there are updates remaining. - if loop_ctxt.updates_remaining > 0 { - let update = update_event(loop_ctxt.loop_start, &mut loop_ctxt.last_update); - if let Some(event_fn) = loop_ctxt.event_fn { - let event = E::from(update.clone()); - event_fn(&app, &mut model, event); - } - if let Some(update_fn) = loop_ctxt.update_fn { - update_fn(&app, &mut model, update); - } - loop_ctxt.updates_remaining -= 1; - - // Draw to each window. - for window_id in app.window_ids() { - acquire_image_and_view_frame( - app, - window_id, - &model, - loop_ctxt.default_view.as_ref(), - ); + LoopMode::Rate { update_interval } => { + let next_update = loop_state.last_update + update_interval; + if now < next_update { + ControlFlow::WaitUntil(next_update) + } else { + ControlFlow::Poll + } } - } - - // Sleep if there's still some time left within the interval. - let now = Instant::now(); - let since_last_loop_end = now.duration_since(loop_ctxt.last_loop_end); - if since_last_loop_end < update_interval { - std::thread::sleep(update_interval - since_last_loop_end); - } - loop_ctxt.last_loop_end = Instant::now(); - // See if the loop mode has changed. If so, break. - match app.loop_mode() { - LoopMode::NTimes { - update_interval: ui, + LoopMode::RefreshSync { + minimum_update_interval, .. } => { - update_interval = ui; - } - loop_mode => { - let reason = BreakReason::NewLoopMode(loop_mode); - return Break { model, reason }; - } - }; - } -} -// Run the application loop under the `RefreshSync` mode. -fn run_loop_mode_refresh_sync( - app: &mut App, - mut model: M, - loop_ctxt: &mut LoopContext, - mut minimum_update_interval: Duration, - mut windows: Option>, -) -> Break -where - M: 'static, - E: LoopEvent, -{ - loop { - // TODO: Properly consider the impact of an individual window blocking. - for window_id in app.window_ids() { - // Skip closed windows. - if app.window(window_id).is_none() { - continue; - } - - cleanup_unused_gpu_resources_for_window(app, window_id); - - // Ensure swapchain dimensions are up to date. - loop { - if window_swapchain_needs_recreation(app, window_id) { - match recreate_window_swapchain(app, window_id) { - Ok(()) => break, - Err(vk::SwapchainCreationError::UnsupportedDimensions) => { - set_window_swapchain_needs_recreation(app, window_id, true); - continue; - } - Err(err) => panic!("{:?}", err), - } + let next_update = loop_state.last_update + minimum_update_interval; + if now < next_update { + ControlFlow::WaitUntil(next_update) + } else { + ControlFlow::Poll } - break; } - // Acquire the next image from the swapchain. - let timeout = None; - let swapchain = app.windows.borrow()[&window_id].swapchain.clone(); - let next_img = vk::swapchain::acquire_next_image(swapchain.swapchain.clone(), timeout); - let (swapchain_image_index, swapchain_image_acquire_future) = match next_img { - Ok(r) => r, - Err(vk::swapchain::AcquireError::OutOfDate) => { - set_window_swapchain_needs_recreation(app, window_id, true); - continue; + LoopMode::NTimes { + number_of_updates, + update_interval, + } => { + if loop_state.total_updates >= number_of_updates as u64 { + exit = true; + ControlFlow::Exit + } else { + let next_update = loop_state.last_update + update_interval; + if now < next_update { + ControlFlow::WaitUntil(next_update) + } else { + ControlFlow::Poll + } } - Err(err) => panic!("{:?}", err), - }; - - // Process pending app events. - let (new_model, exit, event_count) = poll_and_process_events(app, model, loop_ctxt); - model = new_model; - if exit { - let reason = BreakReason::Exit; - return Break { model, reason }; } + }; - // Only emit an `update` if there was some user input or if it's been more than - // `minimum_update_interval`. - let now = Instant::now(); - let since_last = now.duration_since(loop_ctxt.last_update).into(); - let should_emit_update = windows - .as_ref() - .map(|ws| ws.contains(&window_id)) - .unwrap_or(true) - && (event_count > 0 || since_last > minimum_update_interval); - if should_emit_update { - let since_start = now.duration_since(loop_ctxt.loop_start).into(); - app.duration.since_start = since_start; - app.duration.since_prev_update = since_last; - app.time = app.duration.since_start.secs() as _; - - // Emit an update event. - let update = update_event(loop_ctxt.loop_start, &mut loop_ctxt.last_update); - if let Some(event_fn) = loop_ctxt.event_fn { - let event = E::from(update.clone()); - event_fn(&app, &mut model, event); - } - if let Some(update_fn) = loop_ctxt.update_fn { - update_fn(&app, &mut model, update); + // If we need to exit, call the user's function and update control flow. + if exit { + if let Some(model) = model.take() { + if let Some(exit_fn) = exit_fn { + exit_fn(&app, model); } } - - // If the window has been removed, continue. - if app.window(window_id).is_none() { - continue; - } - - view_frame( - app, - &model, - window_id, - swapchain_image_index, - swapchain_image_acquire_future, - loop_ctxt.default_view.as_ref(), - ); + *control_flow = ControlFlow::Exit; + return; } + }); - loop_ctxt.last_loop_end = Instant::now(); - - // See if the loop mode has changed. If so, break. - match app.loop_mode() { - LoopMode::RefreshSync { - minimum_update_interval: mli, - windows: w, - } => { - minimum_update_interval = mli; - windows = w; - } - loop_mode => { - let reason = BreakReason::NewLoopMode(loop_mode); - return Break { model, reason }; - } - } + // Ensure the app no longer points to the window target now that `run` has completed. + // TODO: Right now `event_loop.run` can't return. This is just a reminder in case one day the + // API is changed so that it does return. + #[allow(unreachable_code)] + { + app.event_loop_window_target.take(); } } -// Recreate window swapchains as necessary due to `loop_mode` switch. -fn change_loop_mode(app: &App, loop_mode: &LoopMode) { - // Re-build the window swapchains so that they are optimal for the new loop mode. - let mut windows = app.windows.borrow_mut(); - for window in windows.values_mut() { - change_loop_mode_for_window(window, loop_mode); +// Apply an update to the model via the user's function and update the app and loop state +// accordingly. +fn apply_update( + app: &mut App, + model: &mut M, + event_fn: Option>, + update_fn: Option>, + loop_state: &mut LoopState, + now: Instant, +) where + M: 'static, + E: LoopEvent, +{ + // Update the app's durations. + let now = Instant::now(); + let since_last = now.duration_since(loop_state.last_update); + let since_start = now.duration_since(loop_state.loop_start); + app.duration.since_prev_update = since_last; + app.duration.since_start = since_start; + app.time = since_start.secs() as _; + let update = crate::event::Update { + since_start, + since_last, + }; + // User event function. + if let Some(event_fn) = event_fn { + let event = E::from(update.clone()); + event_fn(app, model, event); } -} - -// Recreate the window swapchain to match the loop mode. -fn change_loop_mode_for_window(window: &mut Window, loop_mode: &LoopMode) { - let device = window.swapchain.swapchain.device().clone(); - let surface = window.surface.clone(); - let queue = window.queue.clone(); - - // Initialise a swapchain builder from the current swapchain's params. - let mut swapchain_builder = - window::SwapchainBuilder::from_swapchain(&window.swapchain.swapchain); - - // Let the new present mode and image count be chosen by nannou or the user if - // they have a preference. - swapchain_builder.present_mode = window.user_specified_present_mode; - swapchain_builder.image_count = window.user_specified_image_count; - - // Create the new swapchain. - let (new_swapchain, new_swapchain_images) = swapchain_builder - .build( - device, - surface, - &queue, - &loop_mode, - None, - Some(&window.swapchain.swapchain), - ) - .expect("failed to recreate swapchain for new `LoopMode`"); - - // Replace the window's swapchain with the newly created one. - window.replace_swapchain(new_swapchain, new_swapchain_images); -} - -// Each window has its own associated GPU future associated with displaying the last frame. -// This method cleans up any unused resources associated with this GPU future. -fn cleanup_unused_gpu_resources_for_window(app: &App, window_id: window::Id) { + // User update function. + if let Some(update_fn) = update_fn { + update_fn(app, model, update); + } + loop_state.last_update = now; + loop_state.total_updates += 1; + loop_state.updates_since_event += 1; + // Request redraw from windows. let windows = app.windows.borrow(); - let mut guard = windows[&window_id] - .swapchain - .previous_frame_end - .lock() - .expect("failed to lock `previous_frame_end`"); - if let Some(future) = guard.as_mut() { - future.cleanup_finished(); + for window in windows.values() { + window.window.request_redraw(); } } -// Returns `true` if the window's swapchain needs to be recreated. -fn window_swapchain_needs_recreation(app: &App, window_id: window::Id) -> bool { +// Returns `true` if the window's swap chain needs to be recreated. +fn window_swap_chain_needs_recreation(app: &App, window_id: window::Id) -> bool { let windows = app.windows.borrow(); let window = &windows[&window_id]; window - .swapchain + .swap_chain .needs_recreation .load(atomic::Ordering::Relaxed) } -// Attempt to recreate a window's swapchain with the window's current dimensions. -fn recreate_window_swapchain( - app: &App, - window_id: window::Id, -) -> Result<(), vk::SwapchainCreationError> { - let mut windows = app.windows.borrow_mut(); - let window = windows.get_mut(&window_id).expect("no window for id"); - - // Get the new dimensions for the viewport/framebuffers. - let dimensions = window - .surface - .capabilities(window.swapchain.device().physical_device()) - .expect("failed to get surface capabilities") - .current_extent - .expect("current_extent was `None`"); - - // Recreate the swapchain with the current dimensions. - let (new_swapchain, new_images) = window - .swapchain - .swapchain - .recreate_with_dimension(dimensions)?; - - // Update the window's swapchain and images. - window.replace_swapchain(new_swapchain, new_images); - - Ok(()) -} - -// Shorthand for setting a window's swapchain.needs_recreation atomic bool. -fn set_window_swapchain_needs_recreation(app: &App, window_id: window::Id, b: bool) { +// Shorthand for setting a window's swap chain.needs_recreation atomic bool. +fn set_window_swap_chain_needs_recreation(app: &App, window_id: window::Id, b: bool) { let windows = app.windows.borrow_mut(); let window = windows.get(&window_id).expect("no window for id"); window - .swapchain + .swap_chain .needs_recreation .store(b, atomic::Ordering::Relaxed); } -// Poll and process any pending application events. -// -// Returns: -// -// - the resulting state of the model. -// - whether or not an event should cause exiting the application loop. -// - the number of winit events processed processed. -fn poll_and_process_events( - app: &mut App, - mut model: M, - loop_ctxt: &mut LoopContext, -) -> (M, bool, usize) -where - M: 'static, - E: LoopEvent, -{ - let mut event_count = 0; - app.events_loop - .poll_events(|event| loop_ctxt.winit_events.push(event)); - for winit_event in loop_ctxt.winit_events.drain(..) { - event_count += 1; - let (new_model, exit) = - process_and_emit_winit_event(app, model, loop_ctxt.event_fn, winit_event); - model = new_model; - if exit { - return (model, exit, event_count); - } - } - (model, false, event_count) -} - // Whether or not the given event should toggle fullscreen. -fn should_toggle_fullscreen(winit_event: &winit::WindowEvent) -> bool { +fn should_toggle_fullscreen(winit_event: &winit::event::WindowEvent) -> bool { let input = match *winit_event { - winit::WindowEvent::KeyboardInput { ref input, .. } => match input.state { + winit::event::WindowEvent::KeyboardInput { ref input, .. } => match input.state { event::ElementState::Pressed => input, _ => return false, }, @@ -1759,7 +1325,7 @@ fn should_toggle_fullscreen(winit_event: &winit::WindowEvent) -> bool { // // TODO: Somehow add special case for KDE? if cfg!(target_os = "linux") { - if !mods.logo && !mods.shift && !mods.alt && !mods.ctrl { + if *mods == winit::event::ModifiersState::empty() { if let Key::F11 = key { return true; } @@ -1767,7 +1333,7 @@ fn should_toggle_fullscreen(winit_event: &winit::WindowEvent) -> bool { // On macos and windows check for the logo key plus `f` with no other modifiers. } else if cfg!(target_os = "macos") || cfg!(target_os = "windows") { - if mods.logo && !mods.shift && !mods.alt && !mods.ctrl { + if *mods == winit::event::ModifiersState::LOGO { if let Key::F = key { return true; } @@ -1777,34 +1343,18 @@ fn should_toggle_fullscreen(winit_event: &winit::WindowEvent) -> bool { false } -// A function to simplify the creation of an `Update` event. -// -// Also updates the given `last_update` instant to `Instant::now()`. -fn update_event(loop_start: Instant, last_update: &mut Instant) -> event::Update { - // Emit an update event. - let now = Instant::now(); - let since_last = now.duration_since(*last_update).into(); - let since_start = now.duration_since(loop_start).into(); - let update = event::Update { - since_last, - since_start, - }; - *last_update = now; - update -} - // Event handling boilerplate shared between the `Rate` and `Wait` loop modes. // // 1. Checks for exit on escape. // 2. Removes closed windows from app. // 3. Emits event via `event_fn`. // 4. Returns whether or not we should break from the loop. -fn process_and_emit_winit_event( +fn process_and_emit_winit_event<'a, M, E>( app: &mut App, - mut model: M, + model: &mut M, event_fn: Option>, - winit_event: winit::Event, -) -> (M, bool) + winit_event: &winit::event::Event<'a, ()>, +) -> bool where M: 'static, E: LoopEvent, @@ -1812,14 +1362,14 @@ where // Inspect the event to see if it would require closing the App. let mut exit_on_escape = false; let mut removed_window = None; - if let winit::Event::WindowEvent { + if let winit::event::Event::WindowEvent { window_id, ref event, - } = winit_event + } = *winit_event { // If we should exit the app on escape, check for the escape key. if app.exit_on_escape() { - if let winit::WindowEvent::KeyboardInput { input, .. } = *event { + if let winit::event::WindowEvent::KeyboardInput { input, .. } = *event { if let Some(Key::Escape) = input.virtual_keycode { exit_on_escape = true; } @@ -1835,12 +1385,12 @@ where app.windows.borrow_mut().remove(window_id) } - if let winit::WindowEvent::Destroyed = *event { + if let winit::event::WindowEvent::Destroyed = *event { removed_window = remove_related_window_state(app, &window_id); // TODO: We should allow the user to handle this case. E.g. allow for doing things like // "would you like to save". We currently do this with the app exit function, but maybe a // window `close` function would be useful? - } else if let winit::WindowEvent::CloseRequested = *event { + } else if let winit::event::WindowEvent::CloseRequested = *event { removed_window = remove_related_window_state(app, &window_id); } else { // Get the size of the window for translating coords and dimensions. @@ -1853,7 +1403,8 @@ where win.set_fullscreen(None); } else { let monitor = win.current_monitor(); - win.set_fullscreen(Some(monitor)); + let fullscreen = winit::window::Fullscreen::Borderless(monitor); + win.set_fullscreen(Some(fullscreen)); } } } @@ -1876,7 +1427,7 @@ where // Check for events that would update either mouse, keyboard or window state. match *event { - winit::WindowEvent::CursorMoved { position, .. } => { + winit::event::WindowEvent::CursorMoved { position, .. } => { let (x, y): (f64, f64) = position.into(); let x = tx(x as _); let y = ty(y as _); @@ -1885,7 +1436,7 @@ where app.mouse.window = Some(window_id); } - winit::WindowEvent::MouseInput { state, button, .. } => { + winit::event::WindowEvent::MouseInput { state, button, .. } => { match state { event::ElementState::Pressed => { let p = app.mouse.position(); @@ -1898,7 +1449,7 @@ where app.mouse.window = Some(window_id); } - winit::WindowEvent::KeyboardInput { input, .. } => { + winit::event::WindowEvent::KeyboardInput { input, .. } => { app.keys.mods = input.modifiers; if let Some(key) = input.virtual_keycode { match input.state { @@ -1918,30 +1469,36 @@ where // See if the event could be interpreted as a `ui::Input`. If so, submit it to the // `Ui`s associated with this window. if let Some(window) = app.windows.borrow().get(&window_id) { - if let Some(input) = ui::winit_window_event_to_input(event.clone(), window) { - if let Some(handles) = app.ui.windows.borrow().get(&window_id) { - for handle in handles { - if let Some(ref tx) = handle.input_tx { - tx.try_send(input.clone()).ok(); - } - } - } - } + // unimplemented!("re-add input handling for UI"); + + // if let Some(input) = ui::winit_window_event_to_input(event.clone(), window) { + // if let Some(handles) = app.ui.windows.borrow().get(&window_id) { + // for handle in handles { + // if let Some(ref tx) = handle.input_tx { + // tx.try_send(input.clone()).ok(); + // } + // } + // } + // } } } } - // If the user provided an event function and winit::Event could be interpreted as some event + // If the user provided an event function and winit::event::Event could be interpreted as some event // `E`, use it to update the model. if let Some(event_fn) = event_fn { - if let Some(event) = E::from_winit_event(winit_event.clone(), app) { - event_fn(&app, &mut model, event); + if let Some(event) = E::from_winit_event(winit_event, app) { + event_fn(&app, model, event); } } // If the event was a window event, and the user specified an event function for this window, // call it. - if let winit::Event::WindowEvent { window_id, event } = winit_event { + if let winit::event::Event::WindowEvent { + window_id, + ref event, + } = *winit_event + { // Raw window events. if let Some(raw_window_event_fn) = { let windows = app.windows.borrow(); @@ -1957,20 +1514,23 @@ where let raw_window_event_fn = raw_window_event_fn .to_fn_ptr::() .expect("unexpected model argument given to window event function"); - (*raw_window_event_fn)(&app, &mut model, event.clone()); + (*raw_window_event_fn)(&app, model, event); } - let (win_w, win_h) = { + let (win_w, win_h, scale_factor) = { let windows = app.windows.borrow(); windows .get(&window_id) - .and_then(|w| w.surface.window().get_inner_size().map(|size| size.into())) - .unwrap_or((0f64, 0f64)) + .map(|w| (w.inner_size_points(), w.scale_factor())) + .map(|((w, h), sf)| (w as f64, h as f64, sf as f64)) + .unwrap_or((0f64, 0f64, 1f64)) }; // If the event can be represented by a simplified nannou event, check for relevant user // functions to be called. - if let Some(simple) = event::WindowEvent::from_winit_window_event(event, win_w, win_h) { + if let Some(simple) = + event::WindowEvent::from_winit_window_event(event, win_w, win_h, scale_factor) + { // Nannou window events. if let Some(window_event_fn) = { let windows = app.windows.borrow(); @@ -1986,7 +1546,7 @@ where let window_event_fn = window_event_fn .to_fn_ptr::() .expect("unexpected model argument given to window event function"); - (*window_event_fn)(&app, &mut model, simple.clone()); + (*window_event_fn)(&app, model, simple.clone()); } // A macro to simplify calling event-specific user functions. @@ -2011,7 +1571,7 @@ where stringify!($fn_name), ); }); - (*event_fn)(&app, &mut model, $($arg),*); + (*event_fn)(&app, model, $($arg),*); } }}; } @@ -2050,271 +1610,18 @@ where } } - // If exit on escape was triggered, we're done. - let exit = if exit_on_escape || app.windows.borrow().is_empty() { + // If the loop was destroyed, we'll need to exit. + let loop_destroyed = match winit_event { + winit::event::Event::LoopDestroyed => true, + _ => false, + }; + + // If any exist conditions were triggered, indicate so. + let exit = if loop_destroyed || exit_on_escape || app.windows.borrow().is_empty() { true } else { false }; - (model, exit) -} - -// Draw the state of the model to the swapchain image associated with the given index.. -// -// This calls the `view` function specified by the user. -fn view_frame( - app: &mut App, - model: &M, - window_id: window::Id, - swapchain_image_index: usize, - swapchain_image_acquire_future: window::SwapchainAcquireFuture, - default_view: Option<&View>, -) where - M: 'static, -{ - // Retrieve the queue and swapchain associated with this window. - let (queue, swapchain) = { - let windows = app.windows.borrow(); - let window = &windows[&window_id]; - (window.queue.clone(), window.swapchain.clone()) - }; - - // Draw the state of the model to the screen. - let (swapchain_image, nth_frame, swapchain_frame_created) = { - let mut windows = app.windows.borrow_mut(); - let window = windows.get_mut(&window_id).expect("no window for id"); - let swapchain_image = window.swapchain.images[swapchain_image_index].clone(); - let frame_count = window.frame_count; - let swapchain_frame_created = window.swapchain.frame_created; - window.frame_count += 1; - (swapchain_image, frame_count, swapchain_frame_created) - }; - - // Construct and emit a frame via `view` for receiving the user's graphics commands. - let raw_frame = RawFrame::new_empty( - queue.clone(), - window_id, - nth_frame, - swapchain_image_index, - swapchain_image, - swapchain_frame_created, - ) - .expect("failed to create `Frame`"); - // If the user specified a view function specifically for this window, use it. - // Otherwise, use the fallback, default view passed to the app if there was one. - let window_view = { - let windows = app.windows.borrow(); - windows - .get(&window_id) - .and_then(|w| w.user_functions.view.clone()) - }; - - // A function to simplify taking a window's render data. - fn take_window_frame_render_data(app: &App, window_id: window::Id) -> Option { - let mut windows = app.windows.borrow_mut(); - windows - .get_mut(&window_id) - .and_then(|w| w.frame_render_data.take()) - } - - // A function to simplify giving the frame render data back to the window. - fn set_window_frame_render_data(app: &App, window_id: window::Id, render_data: RenderData) { - let mut windows = app.windows.borrow_mut(); - if let Some(window) = windows.get_mut(&window_id) { - window.frame_render_data = Some(render_data); - } - } - - let command_buffer = match window_view { - Some(window::View::Sketch(view)) => { - let render_data = take_window_frame_render_data(app, window_id) - .expect("failed to take window's `frame_render_data`"); - let frame = Frame::new_empty(raw_frame, render_data).expect("failed to create `Frame`"); - view(app, &frame); - let (render_data, raw_frame) = frame - .finish() - .expect("failed to resolve frame's intermediary_image to the swapchain_image"); - set_window_frame_render_data(app, window_id, render_data); - raw_frame - .finish() - .build() - .expect("failed to build command buffer") - } - Some(window::View::WithModel(view)) => { - let render_data = take_window_frame_render_data(app, window_id) - .expect("failed to take window's `frame_render_data`"); - let frame = Frame::new_empty(raw_frame, render_data).expect("failed to create `Frame`"); - let view = view - .to_fn_ptr::() - .expect("unexpected model argument given to window view function"); - (*view)(app, model, &frame); - let (render_data, raw_frame) = frame - .finish() - .expect("failed to resolve frame's intermediary_image to the swapchain_image"); - set_window_frame_render_data(app, window_id, render_data); - raw_frame - .finish() - .build() - .expect("failed to build command buffer") - } - Some(window::View::WithModelRaw(raw_view)) => { - let raw_view = raw_view - .to_fn_ptr::() - .expect("unexpected model argument given to window raw_view function"); - (*raw_view)(app, model, &raw_frame); - raw_frame - .finish() - .build() - .expect("failed to build command buffer") - } - None => match default_view { - Some(View::Sketch(view)) => { - let render_data = take_window_frame_render_data(app, window_id) - .expect("failed to take window's `frame_render_data`"); - let frame = - Frame::new_empty(raw_frame, render_data).expect("failed to create `Frame`"); - view(app, &frame); - let (render_data, raw_frame) = frame - .finish() - .expect("failed to resolve frame's intermediary_image to the swapchain_image"); - set_window_frame_render_data(app, window_id, render_data); - raw_frame - .finish() - .build() - .expect("failed to build command buffer") - } - Some(View::WithModel(view)) => { - let render_data = take_window_frame_render_data(app, window_id) - .expect("failed to take window's `frame_render_data`"); - let frame = - Frame::new_empty(raw_frame, render_data).expect("failed to create `Frame`"); - view(app, &model, &frame); - let (render_data, raw_frame) = frame - .finish() - .expect("failed to resolve frame's intermediary_image to the swapchain_image"); - set_window_frame_render_data(app, window_id, render_data); - raw_frame - .finish() - .build() - .expect("failed to build command buffer") - } - None => raw_frame - .finish() - .build() - .expect("failed to build command buffer"), - }, - }; - - let mut windows = app.windows.borrow_mut(); - let window = windows.get_mut(&window_id).expect("no window for id"); - - // Wait for the previous frame presentation to be finished to avoid out-pacing the GPU on macos. - if let Some(mut previous_frame_fence_signal_future) = window - .swapchain - .previous_frame_end - .lock() - .expect("failed to lock `previous_frame_end`") - .take() - { - previous_frame_fence_signal_future.cleanup_finished(); - previous_frame_fence_signal_future - .wait(None) - .expect("failed to wait for `previous_frame_end` future to signal fence"); - } - - // The future associated with the end of the current frame. - let future_result = { - let present_future = swapchain_image_acquire_future - .then_execute(queue.clone(), command_buffer) - .expect("failed to execute future") - .then_swapchain_present( - queue.clone(), - swapchain.swapchain.clone(), - swapchain_image_index, - ); - (Box::new(present_future) as Box).then_signal_fence_and_flush() - }; - - // Handle the result of the future. - let current_frame_end = match future_result { - Ok(future) => Some(future), - Err(vk::sync::FlushError::OutOfDate) => { - window - .swapchain - .needs_recreation - .store(true, atomic::Ordering::Relaxed); - None - } - Err(e) => { - println!("{:?}", e); - None - } - }; - - *window - .swapchain - .previous_frame_end - .lock() - .expect("failed to acquire `previous_frame_end` lock") = current_frame_end; -} - -// Acquire the next swapchain image for the given window and draw to it using the user's -// view function. -// -// Returns whether or not `view_frame` was called. This will be `false` if the given window -// no longer exists or if the swapchain is `OutOfDate` and needs recreation by the time -// `acquire_next_image` is called. -fn acquire_image_and_view_frame( - app: &mut App, - window_id: window::Id, - model: &M, - view: Option<&View>, -) -> bool -where - M: 'static, -{ - // Skip closed windows. - if app.window(window_id).is_none() { - return false; - } - cleanup_unused_gpu_resources_for_window(app, window_id); - - // Ensure swapchain dimensions are up to date. - loop { - if window_swapchain_needs_recreation(app, window_id) { - match recreate_window_swapchain(app, window_id) { - Ok(()) => break, - Err(vk::SwapchainCreationError::UnsupportedDimensions) => { - set_window_swapchain_needs_recreation(app, window_id, true); - continue; - } - Err(err) => panic!("{:?}", err), - } - } - break; - } - - // Acquire the next image from the swapchain. - let timeout = None; - let swapchain = app.windows.borrow()[&window_id].swapchain.clone(); - let next_img = vk::swapchain::acquire_next_image(swapchain.swapchain.clone(), timeout); - let (swapchain_image_index, swapchain_image_acquire_future) = match next_img { - Ok(r) => r, - Err(vk::swapchain::AcquireError::OutOfDate) => { - set_window_swapchain_needs_recreation(app, window_id, true); - return false; - } - Err(err) => panic!("{:?}", err), - }; - - view_frame( - app, - model, - window_id, - swapchain_image_index, - swapchain_image_acquire_future, - view, - ); - true + exit } diff --git a/src/draw/backend/mod.rs b/src/draw/backend/mod.rs index a7be1e303..603016875 100644 --- a/src/draw/backend/mod.rs +++ b/src/draw/backend/mod.rs @@ -1 +1 @@ -pub mod vulkano; +pub mod wgpu; diff --git a/src/draw/backend/vulkano.rs b/src/draw/backend/vulkano.rs deleted file mode 100644 index b47f267c4..000000000 --- a/src/draw/backend/vulkano.rs +++ /dev/null @@ -1,668 +0,0 @@ -//! The `vulkano` backend for rendering the contents of a **Draw**'s mesh. - -use crate::draw; -use crate::frame::{Frame, ViewFbo}; -use crate::math::{BaseFloat, NumCast}; -use crate::vk::{self, DeviceOwned, DynamicStateBuilder, GpuFuture, RenderPassDesc}; -use std::error::Error as StdError; -use std::fmt; -use std::sync::Arc; - -/// A type used for rendering a **nannou::draw::Mesh** with a vulkan graphics pipeline. -pub struct Renderer { - render_pass: Arc, - graphics_pipeline: Arc, - vertices: Vec, - render_pass_images: Option, - //glyph_cache: GlyphCache, - view_fbo: ViewFbo, -} - -// TODO: Add this once the render process is switched to use primitives. -// /// Cache for all glyphs. -// pub struct GlyphCache { -// cache: text::GlyphCache<'static>, -// cpu_buffer_pool: Arc>, -// image: Arc>, -// pixel_buffer: Vec, -// } -// -// impl GlyphCache { -// /// Construct a glyph cache with the given dimensions. -// pub fn with_dimensions( -// queue: Arc, -// dims: [u32; 2], -// ) -> Result { -// const SCALE_TOLERANCE: f32 = 0.1; -// const POSITION_TOLERANCE: f32 = 0.1; -// let [width, height] = dims; -// let cache = text::GlyphCache::builder() -// .dimensions(width, height) -// .scale_tolerance(SCALE_TOLERANCE) -// .position_tolerance(POSITION_TOLERANCE) -// .build(); -// let device = queue.device().clone(); -// let image = vk::StorageImage::with_usage( -// device.clone(), -// vk::image::Dimensions::Dim2d { width, height }, -// vk::format::R8Unorm, -// vk::ImageUsage { -// transfer_destination: true, -// sampled: true, -// ..vk::ImageUsage::none() -// }, -// Some(queue.family()), -// )?; -// let cpu_buffer_pool = Arc::new(vk::CpuBufferPool::upload(device)); -// let pixel_buffer = vec![0u8; width as usize * height as usize]; -// let glyph_cache = GlyphCache { -// cache, -// cpu_buffer_pool, -// image, -// pixel_buffer, -// }; -// Ok(glyph_cache) -// } -// } - -/// The `Vertex` type passed to the vertex shader. -#[derive(Copy, Clone, Debug, Default)] -pub struct Vertex { - // /// The mode with which the `Vertex` will be drawn within the fragment shader. - // /// - // /// `0` for rendering text. - // /// `1` for rendering an image. - // /// `2` for rendering non-textured 2D geometry. - // /// - // /// If any other value is given, the fragment shader will not output any color. - // pub mode: u32, - /// The position of the vertex within vector space. - /// - /// [-1.0, 1.0, 0.0] is the leftmost, bottom position of the display. - /// [1.0, -1.0, 0.0] is the rightmost, top position of the display. - pub position: [f32; 3], - /// A color associated with the `Vertex`. - /// - /// These values should be in the linear sRGB format. - /// - /// The way that the color is used depends on the `mode`. - pub color: [f32; 4], - /// The coordinates of the texture used by this `Vertex`. - /// - /// [0.0, 0.0] is the leftmost, bottom position of the texture. - /// [1.0, 1.0] is the rightmost, top position of the texture. - pub tex_coords: [f32; 2], -} - -// /// Draw text from the text cache texture `tex` in the fragment shader. -// pub const MODE_TEXT: u32 = 0; -// /// Draw an image from the texture at `tex` in the fragment shader. -// pub const MODE_IMAGE: u32 = 1; -// /// Ignore `tex` and draw simple, colored 2D geometry. -// pub const MODE_GEOMETRY: u32 = 2; - -// The images used within the `Draw` render pass. -struct RenderPassImages { - depth: Arc, -} - -/// Errors that might occur while building the **Renderer**. -#[derive(Debug)] -pub enum RendererCreationError { - RenderPass(vk::RenderPassCreationError), - GraphicsPipeline(GraphicsPipelineError), - ImageCreation(vk::ImageCreationError), -} - -/// Errors that might occur while building the **vk::GraphicsPipeline**. -#[derive(Debug)] -pub enum GraphicsPipelineError { - Creation(vk::GraphicsPipelineCreationError), - VertexShaderLoad(vk::OomError), - FragmentShaderLoad(vk::OomError), -} - -/// Errors that might occur while drawing to a framebuffer. -#[derive(Debug)] -pub enum DrawError { - RenderPassCreation(vk::RenderPassCreationError), - GraphicsPipelineCreation(GraphicsPipelineError), - BufferCreation(vk::memory::DeviceMemoryAllocError), - ImageCreation(vk::ImageCreationError), - FramebufferCreation(vk::FramebufferCreationError), - BeginRenderPass(vk::command_buffer::BeginRenderPassError), - DrawIndexed(vk::command_buffer::DrawIndexedError), -} - -mod vertex_impl { - use super::Vertex; - crate::vk::impl_vertex!(Vertex, position, color, tex_coords); -} - -mod vs { - crate::vk::shaders::shader! { - ty: "vertex", - src: " -#version 450 - -layout(location = 0) in vec3 position; -layout(location = 1) in vec4 color; -layout(location = 2) in vec2 tex_coords; -// in uint mode; - -layout(location = 0) out vec4 v_color; -layout(location = 1) out vec2 v_tex_coords; -// flat out uint v_mode; - -void main() { - gl_Position = vec4(position, 1.0); - v_color = color; - v_tex_coords = tex_coords; - // v_mode = mode; -} -" - } -} - -mod fs { - crate::vk::shaders::shader! { - ty: "fragment", - src: " -#version 450 -// uniform sampler2D tex; - -layout(location = 0) in vec4 v_color; -layout(location = 1) in vec2 v_tex_coords; -// flat in uint v_mode; - -layout(location = 0) out vec4 f_color; - -void main() { - // // Text - // if (v_mode == uint(0)) { - // f_color = v_color * vec4(1.0, 1.0, 1.0, texture(tex, v_tex_coords).r); - - // // Image - // } else if (v_mode == uint(1)) { - // f_color = texture(tex, v_tex_coords); - - // // 2D Geometry - // } else if (v_mode == uint(2)) { - // f_color = v_color; - // } - - f_color = v_color; -} -" - } -} - -impl Vertex { - /// Create a vertex from the given mesh vertex. - pub fn from_mesh_vertex( - v: draw::mesh::Vertex, - framebuffer_width: f32, - framebuffer_height: f32, - dpi_factor: f32, - ) -> Self - where - S: BaseFloat, - { - let point = v.point(); - let x_f: f32 = NumCast::from(point.x).unwrap(); - let y_f: f32 = NumCast::from(point.y).unwrap(); - let z_f: f32 = NumCast::from(point.z).unwrap(); - // Map coords from (-fb_dim, +fb_dim) to (-1.0, 1.0) - // In vulkan, *y* increases in the downwards direction, so we negate it. - let x = 2.0 * x_f * dpi_factor / framebuffer_width; - let y = -(2.0 * y_f * dpi_factor / framebuffer_height); - let z = 2.0 * z_f * dpi_factor / framebuffer_height; - let tex_x = NumCast::from(v.tex_coords.x).unwrap(); - let tex_y = NumCast::from(v.tex_coords.y).unwrap(); - let position = [x, y, z]; - let (r, g, b, a) = v.color.into(); - let color = [r, g, b, a]; - let tex_coords = [tex_x, tex_y]; - Vertex { - position, - color, - tex_coords, - } - } -} - -impl RenderPassImages { - // Create all the necessary images for the `RenderPass` besides the swapchain image which will - // be provided by the `Frame`. - fn new( - device: Arc, - dimensions: [u32; 2], - depth_format: vk::Format, - msaa_samples: u32, - ) -> Result { - let depth = vk::AttachmentImage::transient_multisampled( - device, - dimensions, - msaa_samples, - depth_format, - )?; - Ok(RenderPassImages { depth }) - } -} - -impl Renderer { - /// Create the `Renderer`. - /// - /// This creates the `RenderPass` and `vk::GraphicsPipeline` ready for drawing. - pub fn new( - queue: Arc, - color_format: vk::Format, - depth_format: vk::Format, - msaa_samples: u32, - _glyph_cache_dims: [u32; 2], - ) -> Result { - let load_op = vk::LoadOp::Load; - let device = queue.device().clone(); - let render_pass = Arc::new(create_render_pass( - device, - color_format, - depth_format, - load_op, - msaa_samples, - )?) as Arc; - let graphics_pipeline = create_graphics_pipeline(render_pass.clone())? - as Arc; - let vertices = vec![]; - let render_pass_images = None; - let view_fbo = ViewFbo::default(); - //let glyph_cache = GlyphCache::with_dimensions(queue, glyph_cache_dims)?; - Ok(Renderer { - render_pass, - graphics_pipeline, - vertices, - render_pass_images, - view_fbo, - //glyph_cache, - }) - } - - /// Draw the given mesh to the given frame. - /// - /// TODO: Make this generic over any "framebuffer" type. - pub fn draw_to_frame( - &mut self, - draw: &draw::Draw, - dpi_factor: f32, - frame: &Frame, - depth_format: vk::Format, - ) -> Result<(), DrawError> - where - S: BaseFloat, - { - let Renderer { - ref mut render_pass, - ref mut graphics_pipeline, - ref mut vertices, - ref mut render_pass_images, - ref mut view_fbo, - //ref mut glyph_cache, - } = *self; - - // Retrieve the color/depth image load op and clear values based on the bg color. - let bg_color = draw.state.borrow().background_color; - let (load_op, clear_color, clear_depth) = match bg_color { - None => (vk::LoadOp::Load, vk::ClearValue::None, vk::ClearValue::None), - Some(color) => { - let (r, g, b, a) = color.into(); - let clear_color = [r, g, b, a].into(); - let clear_depth = 1f32.into(); - (vk::LoadOp::Clear, clear_color, clear_depth) - } - }; - - // Ensure that the render pass has the correct load op. If not, recreate it. - let recreate_render_pass = render_pass - .attachment_descs() - .next() - .map(|desc| desc.load != load_op) - .unwrap_or(true); - - let device = frame.queue().device().clone(); - let color_format = frame.image_format(); - let msaa_samples = frame.image_msaa_samples(); - - // If necessary, recreate the render pass and in turn the graphics pipeline. - if recreate_render_pass { - *render_pass = create_render_pass( - device.clone(), - color_format, - depth_format, - load_op, - msaa_samples, - )?; - *graphics_pipeline = create_graphics_pipeline(render_pass.clone())?; - } - - // Prepare clear values. - let clear_values = vec![clear_color, clear_depth]; - - let image_dims = frame.image().dimensions(); - let [img_w, img_h] = image_dims; - let queue = frame.queue().clone(); - - // Create the vertex and index buffers. - let map_vertex = |v| Vertex::from_mesh_vertex(v, img_w as _, img_h as _, dpi_factor); - vertices.extend(draw.raw_vertices().map(map_vertex)); - let (vertex_buffer, vb_future) = vk::ImmutableBuffer::from_iter( - vertices.drain(..), - vk::BufferUsage::vertex_buffer(), - queue.clone(), - )?; - let (index_buffer, ib_future) = vk::ImmutableBuffer::from_iter( - draw.inner_mesh().indices().iter().map(|&u| u as u32), - vk::BufferUsage::index_buffer(), - queue.clone(), - )?; - - // Create (or recreate) the render pass images if necessary. - let recreate_images = render_pass_images - .as_ref() - .map(|imgs| image_dims != imgs.depth.dimensions()) - .unwrap_or(true); - if recreate_images { - *render_pass_images = Some(RenderPassImages::new( - device.clone(), - image_dims, - depth_format, - msaa_samples, - )?); - } - - // Safe to `unwrap` here as we have ensured that `render_pass_images` is `Some` above. - let render_pass_images = render_pass_images - .as_mut() - .expect("render_pass_images is `None`"); - - // Ensure framebuffers are up to date with the frame's swapchain image and render pass. - view_fbo - .update(&frame, render_pass.clone(), |builder, image| { - builder.add(image)?.add(render_pass_images.depth.clone()) - }) - .unwrap(); - - // Create the dynamic state. - let dynamic_state = dynamic_state([img_w as _, img_h as _]); - - vb_future - .join(ib_future) - .then_signal_fence_and_flush() - .expect("`then_signal_fence_and_flush` failed") - .wait(None) - .expect("failed to wait for `vb` and `ib` futures"); - - // Submit the draw commands. - frame - .add_commands() - .begin_render_pass(view_fbo.expect_inner(), clear_values)? - .draw_indexed( - graphics_pipeline.clone(), - &dynamic_state, - vec![vertex_buffer], - index_buffer, - (), - (), - )? - .end_render_pass() - .expect("failed to add `end_render_pass` command"); - - Ok(()) - } -} - -/// The render pass used for the graphics pipeline. -pub fn create_render_pass( - device: Arc, - color_format: vk::Format, - depth_format: vk::Format, - load_op: vk::LoadOp, - msaa_samples: u32, -) -> Result, vk::RenderPassCreationError> { - // TODO: Remove this in favour of a nannou-specific, dynamic `RenderPassDesc` implementation. - match load_op { - vk::LoadOp::Clear => { - create_render_pass_clear(device, color_format, depth_format, msaa_samples) - } - vk::LoadOp::Load => { - create_render_pass_load(device, color_format, depth_format, msaa_samples) - } - vk::LoadOp::DontCare => unreachable!(), - } -} - -/// Create a render pass that uses `vk::LoadOp::Clear` for the multisampled color and depth -/// attachments. -pub fn create_render_pass_clear( - device: Arc, - color_format: vk::Format, - depth_format: vk::Format, - msaa_samples: u32, -) -> Result, vk::RenderPassCreationError> { - let rp = vk::single_pass_renderpass!( - device, - attachments: { - color: { - load: Clear, - store: Store, - format: color_format, - samples: msaa_samples, - }, - depth: { - load: Clear, - store: Store, - format: depth_format, - samples: msaa_samples, - } - }, - pass: { - color: [color], - depth_stencil: {depth} - } - )?; - Ok(Arc::new(rp)) -} - -/// Create a render pass that uses `vk::LoadOp::Clear` for the multisampled color and depth -/// attachments. -pub fn create_render_pass_load( - device: Arc, - color_format: vk::Format, - depth_format: vk::Format, - msaa_samples: u32, -) -> Result, vk::RenderPassCreationError> { - let rp = vk::single_pass_renderpass!( - device, - attachments: { - color: { - load: Load, - store: Store, - format: color_format, - samples: msaa_samples, - }, - depth: { - load: Load, - store: Store, - format: depth_format, - samples: msaa_samples, - } - }, - pass: { - color: [color], - depth_stencil: {depth} - } - )?; - Ok(Arc::new(rp)) -} - -/// The dynamic state for the renderer. -pub fn dynamic_state(viewport_dimensions: [f32; 2]) -> vk::DynamicState { - let viewport = vk::ViewportBuilder::new().build(viewport_dimensions); - vk::DynamicState::default().viewports(vec![viewport]) -} - -/// The graphics pipeline used by the renderer. -pub fn create_graphics_pipeline( - render_pass: R, -) -> Result, GraphicsPipelineError> -where - R: vk::RenderPassAbstract + Send + Sync + 'static, -{ - let device = render_pass.device().clone(); - let vs = vs::Shader::load(device.clone()).map_err(GraphicsPipelineError::VertexShaderLoad)?; - let fs = fs::Shader::load(device.clone()).map_err(GraphicsPipelineError::FragmentShaderLoad)?; - let subpass = vk::Subpass::from(render_pass, 0).expect("no subpass for `id`"); - let pipeline = vk::GraphicsPipeline::start() - //.sample_shading_enabled(1.0) - .vertex_input_single_buffer::() - .vertex_shader(vs.main_entry_point(), ()) - .triangle_list() - .viewports_dynamic_scissors_irrelevant(1) - .fragment_shader(fs.main_entry_point(), ()) - .blend_alpha_blending() - .render_pass(subpass) - .build(device)?; - Ok(Arc::new(pipeline)) -} - -// Error Implementations - -impl From for RendererCreationError { - fn from(err: vk::RenderPassCreationError) -> Self { - RendererCreationError::RenderPass(err) - } -} - -impl From for RendererCreationError { - fn from(err: GraphicsPipelineError) -> Self { - RendererCreationError::GraphicsPipeline(err) - } -} - -impl From for RendererCreationError { - fn from(err: vk::ImageCreationError) -> Self { - RendererCreationError::ImageCreation(err) - } -} - -impl From for GraphicsPipelineError { - fn from(err: vk::GraphicsPipelineCreationError) -> Self { - GraphicsPipelineError::Creation(err) - } -} - -impl From for DrawError { - fn from(err: vk::RenderPassCreationError) -> Self { - DrawError::RenderPassCreation(err) - } -} - -impl From for DrawError { - fn from(err: GraphicsPipelineError) -> Self { - DrawError::GraphicsPipelineCreation(err) - } -} - -impl From for DrawError { - fn from(err: vk::memory::DeviceMemoryAllocError) -> Self { - DrawError::BufferCreation(err) - } -} - -impl From for DrawError { - fn from(err: vk::ImageCreationError) -> Self { - DrawError::ImageCreation(err) - } -} - -impl From for DrawError { - fn from(err: vk::FramebufferCreationError) -> Self { - DrawError::FramebufferCreation(err) - } -} - -impl From for DrawError { - fn from(err: vk::command_buffer::BeginRenderPassError) -> Self { - DrawError::BeginRenderPass(err) - } -} - -impl From for DrawError { - fn from(err: vk::command_buffer::DrawIndexedError) -> Self { - DrawError::DrawIndexed(err) - } -} - -impl StdError for RendererCreationError { - fn description(&self) -> &str { - match *self { - RendererCreationError::RenderPass(ref err) => err.description(), - RendererCreationError::GraphicsPipeline(ref err) => err.description(), - RendererCreationError::ImageCreation(ref err) => err.description(), - } - } -} - -impl StdError for GraphicsPipelineError { - fn description(&self) -> &str { - match *self { - GraphicsPipelineError::Creation(ref err) => err.description(), - GraphicsPipelineError::VertexShaderLoad(ref err) => err.description(), - GraphicsPipelineError::FragmentShaderLoad(ref err) => err.description(), - } - } -} - -impl StdError for DrawError { - fn description(&self) -> &str { - match *self { - DrawError::RenderPassCreation(ref err) => err.description(), - DrawError::GraphicsPipelineCreation(ref err) => err.description(), - DrawError::BufferCreation(ref err) => err.description(), - DrawError::ImageCreation(ref err) => err.description(), - DrawError::FramebufferCreation(ref err) => err.description(), - DrawError::BeginRenderPass(ref err) => err.description(), - DrawError::DrawIndexed(ref err) => err.description(), - } - } -} - -impl fmt::Display for RendererCreationError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.description()) - } -} - -impl fmt::Display for GraphicsPipelineError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.description()) - } -} - -impl fmt::Display for DrawError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.description()) - } -} - -impl fmt::Debug for Renderer { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Renderer ( render_pass, graphics_pipeline, framebuffer: {} )", - if self.view_fbo.is_some() { - "Some" - } else { - "None" - }, - ) - } -} diff --git a/src/draw/backend/wgpu.rs b/src/draw/backend/wgpu.rs new file mode 100644 index 000000000..6ce6ca31a --- /dev/null +++ b/src/draw/backend/wgpu.rs @@ -0,0 +1,5 @@ +#[derive(Debug)] +pub struct Renderer; + +#[derive(Debug)] +pub struct DrawError; diff --git a/src/event.rs b/src/event.rs index 349e7fc93..265a9ba22 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,7 +1,7 @@ //! Application, event loop and window event definitions and implementations. //! //! - [**Event**](./enum.Event.html) - the defualt application event type. -//! - [**winit::WindowEvent**](./struct.WindowEvent.html) - events related to a single window. +//! - [**winit::event::WindowEvent**](./struct.WindowEvent.html) - events related to a single window. //! - [**WindowEvent**](./struct.WindowEvent.html) - a stripped-back, simplified, //! newcomer-friendly version of the **raw**, low-level winit event. @@ -11,15 +11,15 @@ use crate::App; use std::path::PathBuf; use winit; -pub use winit::{ +pub use winit::event::{ ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode as Key, }; /// Event types that are compatible with the nannou app loop. -pub trait LoopEvent: From { +pub trait LoopEvent: 'static + From { /// Produce a loop event from the given winit event. - fn from_winit_event(_: winit::Event, _: &App) -> Option; + fn from_winit_event<'a, T>(_: &winit::event::Event<'a, T>, _: &App) -> Option; } /// Update event, emitted on each pass of an application loop. @@ -36,33 +36,31 @@ pub struct Update { } /// The default application **Event** type. -#[derive(Clone, Debug)] +#[derive(Debug)] pub enum Event { /// A window-specific event has occurred for the window with the given Id. /// - /// This event is portrayed both in its "raw" form (the **winit::WindowEvent**) and its + /// This event is portrayed both in its "raw" form (the **winit::event::WindowEvent**) and its /// simplified, new-user-friendly form **SimpleWindowEvent**. WindowEvent { id: window::Id, - raw: winit::WindowEvent, simple: Option, + // TODO: Re-add this when winit#1387 is resolved. + // raw: winit::event::WindowEvent, }, /// A device-specific event has occurred for the device with the given Id. - DeviceEvent(winit::DeviceId, winit::DeviceEvent), + DeviceEvent(winit::event::DeviceId, winit::event::DeviceEvent), /// A timed update alongside the duration since the last update was emitted. /// /// The first update's delta will be the time since the `model` function returned. Update(Update), - /// The application has been awakened. - Awakened, - /// The application has been suspended or resumed. - /// - /// The parameter is true if app was suspended, and false if it has been resumed. - Suspended(bool), + Suspended, + /// The application has been awakened. + Resumed, } /// The event associated with a touch at a single point. @@ -80,7 +78,7 @@ pub struct TouchEvent { #[derive(Copy, Clone, Debug, PartialEq)] pub struct TouchpadPressure { /// The unique ID associated with the device that emitted this event. - pub device_id: winit::DeviceId, + pub device_id: winit::event::DeviceId, /// The amount of pressure applied. pub pressure: f32, /// Integer representing the click level. @@ -91,9 +89,9 @@ pub struct TouchpadPressure { #[derive(Copy, Clone, Debug, PartialEq)] pub struct AxisMotion { /// The unique ID of the device that emitted this event. - pub device_id: winit::DeviceId, + pub device_id: winit::event::DeviceId, /// The axis on which motion occurred. - pub axis: winit::AxisId, + pub axis: winit::event::AxisId, /// The motion value. pub value: geom::scalar::Default, } @@ -170,7 +168,7 @@ pub enum WindowEvent { } impl WindowEvent { - /// Produce a simplified, new-user-friendly version of the given `winit::WindowEvent`. + /// Produce a simplified, new-user-friendly version of the given `winit::event::WindowEvent`. /// /// This strips rarely needed technical information from the event type such as information /// about the source device, scancodes for keyboard events, etc to make the experience of @@ -183,9 +181,10 @@ impl WindowEvent { /// If the user requires this extra information, they should use the `raw` field of the /// `WindowEvent` type rather than the `simple` one. pub fn from_winit_window_event( - event: winit::WindowEvent, + event: &winit::event::WindowEvent, win_w: f64, win_h: f64, + scale_factor: f64, ) -> Option { use self::WindowEvent::*; @@ -199,84 +198,88 @@ impl WindowEvent { let ty = |y: f64| (-(y - win_h / 2.0)) as geom::scalar::Default; let event = match event { - winit::WindowEvent::Resized(new_size) => { - let (new_w, new_h) = new_size.into(); + winit::event::WindowEvent::Resized(new_size) => { + let (new_w, new_h) = new_size.to_logical::(scale_factor).into(); let x = tw(new_w); let y = th(new_h); Resized(Vector2 { x, y }) } - winit::WindowEvent::Moved(new_pos) => { - let (new_x, new_y) = new_pos.into(); + winit::event::WindowEvent::Moved(new_pos) => { + let (new_x, new_y) = new_pos.to_logical::(scale_factor).into(); let x = tx(new_x); let y = ty(new_y); Moved(Point2 { x, y }) } // TODO: Should separate the behaviour of close requested and destroyed. - winit::WindowEvent::CloseRequested | winit::WindowEvent::Destroyed => Closed, + winit::event::WindowEvent::CloseRequested | winit::event::WindowEvent::Destroyed => { + Closed + } - winit::WindowEvent::DroppedFile(path) => DroppedFile(path), + winit::event::WindowEvent::DroppedFile(path) => DroppedFile(path.clone()), - winit::WindowEvent::HoveredFile(path) => HoveredFile(path), + winit::event::WindowEvent::HoveredFile(path) => HoveredFile(path.clone()), - winit::WindowEvent::HoveredFileCancelled => HoveredFileCancelled, + winit::event::WindowEvent::HoveredFileCancelled => HoveredFileCancelled, - winit::WindowEvent::Focused(b) => { - if b { + winit::event::WindowEvent::Focused(b) => { + if b.clone() { Focused } else { Unfocused } } - winit::WindowEvent::CursorMoved { position, .. } => { - let (x, y) = position.into(); + winit::event::WindowEvent::CursorMoved { position, .. } => { + let (x, y) = position.to_logical::(scale_factor).into(); let x = tx(x); let y = ty(y); MouseMoved(Point2 { x, y }) } - winit::WindowEvent::CursorEntered { .. } => MouseEntered, + winit::event::WindowEvent::CursorEntered { .. } => MouseEntered, - winit::WindowEvent::CursorLeft { .. } => MouseExited, + winit::event::WindowEvent::CursorLeft { .. } => MouseExited, - winit::WindowEvent::MouseWheel { delta, phase, .. } => MouseWheel(delta, phase), + winit::event::WindowEvent::MouseWheel { delta, phase, .. } => { + MouseWheel(delta.clone(), phase.clone()) + } - winit::WindowEvent::MouseInput { state, button, .. } => match state { - ElementState::Pressed => MousePressed(button), - ElementState::Released => MouseReleased(button), + winit::event::WindowEvent::MouseInput { state, button, .. } => match state { + ElementState::Pressed => MousePressed(button.clone()), + ElementState::Released => MouseReleased(button.clone()), }, - winit::WindowEvent::Touch(winit::Touch { + winit::event::WindowEvent::Touch(winit::event::Touch { phase, location, id, .. }) => { - let (x, y) = location.into(); + let (x, y) = location.to_logical::(scale_factor).into(); let x = tx(x); let y = ty(y); let position = Point2 { x, y }; let touch = TouchEvent { - phase, + phase: phase.clone(), position, - id, + id: id.clone(), }; WindowEvent::Touch(touch) } - winit::WindowEvent::TouchpadPressure { + winit::event::WindowEvent::TouchpadPressure { device_id, pressure, stage, } => TouchPressure(TouchpadPressure { - device_id, - pressure, - stage, + device_id: device_id.clone(), + pressure: pressure.clone(), + stage: stage.clone(), }), - winit::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode { + winit::event::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode { Some(key) => match input.state { ElementState::Pressed => KeyPressed(key), ElementState::Released => KeyReleased(key), @@ -284,10 +287,10 @@ impl WindowEvent { None => return None, }, - winit::WindowEvent::AxisMotion { .. } - | winit::WindowEvent::Refresh - | winit::WindowEvent::ReceivedCharacter(_) - | winit::WindowEvent::HiDpiFactorChanged(_) => { + winit::event::WindowEvent::AxisMotion { .. } + | winit::event::WindowEvent::ReceivedCharacter(_) + | winit::event::WindowEvent::ThemeChanged(_) + | winit::event::WindowEvent::ScaleFactorChanged { .. } => { return None; } }; @@ -297,29 +300,43 @@ impl WindowEvent { } impl LoopEvent for Event { - /// Convert the given `winit::Event` to a nannou `Event`. - fn from_winit_event(event: winit::Event, app: &App) -> Option { + /// Convert the given `winit::event::Event` to a nannou `Event`. + fn from_winit_event<'a, T>(event: &winit::event::Event<'a, T>, app: &App) -> Option { let event = match event { - winit::Event::WindowEvent { window_id, event } => { + winit::event::Event::WindowEvent { window_id, event } => { let windows = app.windows.borrow(); - let (win_w, win_h) = match windows.get(&window_id) { - None => (0.0, 0.0), // The window was likely closed, these will be ignored. - Some(window) => match window.surface.window().get_inner_size() { - None => (0.0, 0.0), - Some(size) => size.into(), - }, + let (win_w, win_h, scale_factor) = match windows.get(&window_id) { + None => (0.0, 0.0, 1.0), // The window was likely closed, these will be ignored. + Some(window) => { + let (w, h) = window.inner_size_points(); + let sf = window.scale_factor(); + (w, h, sf) + } }; - let raw = event.clone(); - let simple = WindowEvent::from_winit_window_event(event, win_w, win_h); + let simple = WindowEvent::from_winit_window_event( + event, + win_w as f64, + win_h as f64, + scale_factor as f64, + ); Event::WindowEvent { - id: window_id, - raw, + id: window_id.clone(), simple, + // TODO: Re-add this when winit#1387 is resolved. + // raw, } } - winit::Event::DeviceEvent { device_id, event } => Event::DeviceEvent(device_id, event), - winit::Event::Awakened => Event::Awakened, - winit::Event::Suspended(b) => Event::Suspended(b), + winit::event::Event::DeviceEvent { device_id, event } => { + Event::DeviceEvent(device_id.clone(), event.clone()) + } + winit::event::Event::Suspended => Event::Suspended, + winit::event::Event::Resumed => Event::Resumed, + winit::event::Event::NewEvents(_) + | winit::event::Event::UserEvent(_) + | winit::event::Event::MainEventsCleared + | winit::event::Event::RedrawRequested(_) + | winit::event::Event::RedrawEventsCleared + | winit::event::Event::LoopDestroyed => return None, }; Some(event) } diff --git a/src/frame/mod.rs b/src/frame/mod.rs index 068aceab4..b26288993 100644 --- a/src/frame/mod.rs +++ b/src/frame/mod.rs @@ -1,21 +1,12 @@ //! Items related to the **Frame** type, describing a single frame of graphics for a single window. use crate::color::IntoLinSrgba; -use crate::vk::{self, DeviceOwned}; -use std::error::Error as StdError; -use std::sync::Arc; -use std::{fmt, ops}; +use crate::wgpu; +use std::ops; pub mod raw; -pub use self::raw::{AddCommands, RawFrame}; - -/// The vulkan color format used by the intermediary linear sRGBA image. -/// -/// We use a high bit depth format in order to retain as much information as possible when -/// converting from the linear representation to the swapchain format (normally a non-linear -/// representation). -pub const COLOR_FORMAT: vk::Format = vk::Format::R16G16B16A16Unorm; +pub use self::raw::RawFrame; /// A **Frame** to which the user can draw graphics before it is presented to the display. /// @@ -25,559 +16,245 @@ pub const COLOR_FORMAT: vk::Format = vk::Format::R16G16B16A16Unorm; /// **Frame** type differs in that rather than drawing directly to the swapchain image the user may /// draw to an intermediary linear sRGBA image. There are several advantages of drawing to an /// intermediary image. -pub struct Frame { - raw_frame: RawFrame, +pub struct Frame<'swap_chain> { + raw_frame: RawFrame<'swap_chain>, data: RenderData, } -/// A helper type for managing a framebuffer associated with a window's `view` function. -/// -/// Creating and maintaining the framebuffer that targets the `Frame`s image can be a tedious task -/// that requires a lot of boilerplate code. This type simplifies the process with a single -/// `update` method that creates or recreates the framebuffer if any of the following conditions -/// are met: -/// - The `update` method is called for the first time. -/// - The `frame.image_is_new()` method indicates that the swapchain or its images have recently -/// been recreated and the framebuffer should be recreated accordingly. -/// - The given render pass is different to that which was used to create the existing framebuffer. -#[derive(Default)] -pub struct ViewFramebufferObject { - fbo: vk::Fbo, -} - -/// Shorthand for the **ViewFramebufferObject** type. -pub type ViewFbo = ViewFramebufferObject; - -/// Data necessary for rendering the **Frame**'s `image` to the the `swapchain_image` of the inner -/// raw frame. -pub(crate) struct RenderData { - intermediary: IntermediaryData, -} - -// Data related to the intermediary MSAA linear sRGBA image and the non-MSAA linear sRGBA image to -// which the former is resolved. -struct IntermediaryData { - images: IntermediaryImages, - images_are_new: bool, - resolve_render_pass: Option>, - resolve_framebuffer: vk::Fbo, -} - -// The intermediary MSAA linear sRGBA image and the non-MSAA linear sRGBA image to which the former -// is resolved if necessary. If no MSAA is required, `lin_srgba_msaa` is never created. -struct IntermediaryImages { - lin_srgba_msaa: Option>, - lin_srgba: Arc, -} - -// The vertex type used to specify the quad to which the linear sRGBA image is drawn on the -// swapchain image. -#[derive(Copy, Clone, Debug, Default)] -struct Vertex { - position: [f32; 2], -} - -vk::impl_vertex!(Vertex, position); - -/// Errors that might occur during creation of the `RenderData` for a frame. +/// Data specific to the intermediary textures. #[derive(Debug)] -pub enum RenderDataCreationError { - RenderPassCreation(vk::RenderPassCreationError), - ImageCreation(vk::ImageCreationError), - GraphicsPipelineCreation(GraphicsPipelineError), - DeviceMemoryAlloc(vk::memory::DeviceMemoryAllocError), - SamplerCreation(vk::SamplerCreationError), -} - -/// Errors that might occur during creation of the `Frame`. -#[derive(Debug)] -pub enum FrameCreationError { - ImageCreation(vk::ImageCreationError), - FramebufferCreation(vk::FramebufferCreationError), -} - -/// Errors that might occur during `Frame::finish`. -#[derive(Debug)] -pub enum FrameFinishError { - BeginRenderPass(vk::command_buffer::BeginRenderPassError), +pub struct RenderData { + intermediary_lin_srgba: IntermediaryLinSrgba, + msaa_samples: u32, + // For writing the intermediary linear sRGBA texture to the swap chain texture. + texture_format_converter: wgpu::TextureFormatConverter, } -/// Errors that might occur while building the **vk::GraphicsPipeline**. +/// Intermediary textures used as a target before resolving multisampling and writing to the +/// swapchain texture. #[derive(Debug)] -pub enum GraphicsPipelineError { - Creation(vk::GraphicsPipelineCreationError), - VertexShaderLoad(vk::OomError), - FragmentShaderLoad(vk::OomError), +pub(crate) struct IntermediaryLinSrgba { + msaa_texture: Option, + texture: wgpu::TextureView, } -impl IntermediaryImages { - fn dimensions(&self) -> [u32; 2] { - vk::AttachmentImage::dimensions(&self.lin_srgba) +impl<'swap_chain> ops::Deref for Frame<'swap_chain> { + type Target = RawFrame<'swap_chain>; + fn deref(&self) -> &Self::Target { + &self.raw_frame } } -impl Frame { +impl<'swap_chain> Frame<'swap_chain> { /// The default number of multisample anti-aliasing samples used if the window with which the /// `Frame` is associated supports it. pub const DEFAULT_MSAA_SAMPLES: u32 = 8; + /// The texture format used by the intermediary linear sRGBA image. + /// + /// We use a high bit depth format in order to retain as much information as possible when + /// converting from the linear representation to the swapchain format (normally a non-linear + /// representation). + pub const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Unorm; - /// Initialise a new empty frame ready for "drawing". - pub(crate) fn new_empty( - raw_frame: RawFrame, - mut data: RenderData, - ) -> Result { - // If the image dimensions differ to that of the swapchain image, recreate it. - let image_dims = raw_frame.swapchain_image().dimensions(); - if data.intermediary.images.dimensions() != image_dims { - let msaa_samples = data - .intermediary - .images - .lin_srgba_msaa - .as_ref() - .map(|img| vk::image::ImageAccess::samples(img)) - .unwrap_or(1); - data.intermediary.images = create_intermediary_images( - raw_frame.swapchain_image().swapchain().device().clone(), - image_dims, - msaa_samples, - COLOR_FORMAT, - )?; - data.intermediary.images_are_new = true; - } - - { - let RenderData { - intermediary: - IntermediaryData { - ref images, - ref mut resolve_framebuffer, - ref resolve_render_pass, - .. - }, - } = data; - - // Ensure resolve fbo is up to date with the frame's swapchain image and render pass. - let [w, h] = images.dimensions(); - let dims = [w, h, 1]; - if let Some(msaa_img) = images.lin_srgba_msaa.as_ref() { - if let Some(rp) = resolve_render_pass.as_ref() { - resolve_framebuffer.update(rp.clone(), dims, |builder| { - builder.add(msaa_img.clone())?.add(images.lin_srgba.clone()) - })?; - } - } - } - - Ok(Frame { raw_frame, data }) + // Initialise a new empty frame ready for "drawing". + pub(crate) fn new_empty(raw_frame: RawFrame<'swap_chain>, data: RenderData) -> Self { + Frame { raw_frame, data } } - /// Called after the user's `view` function returns, this consumes the `Frame`, adds commands - /// for drawing the `lin_srgba_msaa` to the `swapchain_image` and returns the inner - /// `RenderData` and `RawFrame` so that the `RenderData` may be stored back within the `Window` - /// and the `RawFrame` may be `finish`ed. - pub(crate) fn finish(self) -> Result<(RenderData, RawFrame), FrameFinishError> { - let Frame { - mut data, - raw_frame, - } = self; + // Called after the user's `view` function returns, this consumes the `Frame`, adds commands + // for drawing the `lin_srgba_msaa` to the `swapchain_image` and returns the inner + // `RenderData` and `RawFrame` so that the `RenderData` may be stored back within the `Window` + // and the `RawFrame` may be `finish`ed. + pub(crate) fn finish(self) -> (RenderData, RawFrame<'swap_chain>) { + let Frame { data, raw_frame } = self; // Resolve the MSAA if necessary. - let clear_values = vec![vk::ClearValue::None, vk::ClearValue::None]; - if let Some(fbo) = data.intermediary.resolve_framebuffer.as_ref() { - raw_frame - .add_commands() - .begin_render_pass(fbo.clone(), clear_values)? - .end_render_pass() - .expect("failed to add `end_render_pass` command"); + if let Some(ref msaa_texture) = data.intermediary_lin_srgba.msaa_texture { + let mut encoder = raw_frame.command_encoder(); + wgpu::resolve_texture( + msaa_texture, + &data.intermediary_lin_srgba.texture, + &mut *encoder, + ); } - // Blit the linear sRGBA image to the swapchain image. + // Convert the linear sRGBA image to the swapchain image. // - // We use blit here rather than copy, as blit will take care of the conversion from linear - // to non-linear if necessar. - let [w, h] = data.intermediary.images.dimensions(); - let src = data.intermediary.images.lin_srgba.clone(); - let src_tl = [0; 3]; - let src_br = [w as i32, h as i32, 1]; - let src_base_layer = 0; - let src_mip_level = 0; - let dst = raw_frame.swapchain_image().clone(); - let dst_tl = [0; 3]; - let dst_br = [w as i32, h as i32, 1]; - let dst_base_layer = 0; - let dst_mip_level = 0; - let layer_count = 1; - let filter = vk::sampler::Filter::Linear; - raw_frame - .add_commands() - .blit_image( - src, - src_tl, - src_br, - src_base_layer, - src_mip_level, - dst, - dst_tl, - dst_br, - dst_base_layer, - dst_mip_level, - layer_count, - filter, - ) - .expect("failed to blit linear sRGBA image to swapchain image"); - - // The intermediary linear sRGBA image is no longer "new". - data.intermediary.images_are_new = false; + // To do so, we sample the linear sRGBA image and draw it to the swapchain image using + // two triangles and a fragment shader. + { + let mut encoder = raw_frame.command_encoder(); + data.texture_format_converter + .encode_render_pass(raw_frame.swap_chain_texture(), &mut *encoder); + } - Ok((data, raw_frame)) + (data, raw_frame) } - /// The image to which all user graphics should be drawn this frame. + /// The texture to which all use graphics should be drawn this frame. + /// + /// This is **not** the swapchain texture, but rather an intermediary linear sRGBA image. This + /// intermediary image is used in order to: /// - /// This is **not** the swapchain image, but rather an intermediary linear sRGBA image. This - /// intermediary image is used in order to ensure consistent MSAA resolve behaviour across - /// platforms, and to avoid the need for multiple implicit conversions to and from linear sRGBA - /// for each graphics pipeline render pass that is used. + /// - Ensure consistent MSAA resolve behaviour across platforms. + /// - Avoid the need for multiple implicit conversions to and from linear sRGBA for each + /// graphics pipeline render pass that is used. + /// - Allow for the user's rendered image to persist between frames. /// - /// The exact format of the image is equal to `Frame::image_format`, which is equal to - /// `nannou::frame::COLOR_FORMAT`. + /// The exact format of the texture is equal to `Frame::TEXTURE_FORMAT`. /// /// If the number of MSAA samples specified is greater than `1` (which it is by default if - /// supported by the platform), this will be a multisampled image. After the **view** function - /// returns, this image will be resolved to a non-multisampled linear sRGBA image. After the - /// image has been resolved if necessary, it will then be used as a shader input within a - /// graphics pipeline used to draw the swapchain image. - pub fn image(&self) -> &Arc { - match self.data.intermediary.images.lin_srgba_msaa.as_ref() { - None => &self.data.intermediary.images.lin_srgba, - Some(msaa_img) => msaa_img, - } + /// supported by the platform), this will be a multisampled texture. After the **view** + /// function returns, this texture will be resolved to a non-multisampled linear sRGBA texture. + /// After the texture has been resolved if necessary, it will then be used as a shader input + /// within a graphics pipeline used to draw the swapchain texture. + pub fn texture(&self) -> &wgpu::TextureView { + self.data + .intermediary_lin_srgba + .msaa_texture + .as_ref() + .unwrap_or(&self.data.intermediary_lin_srgba.texture) } - /// If the **Frame**'s image is new because it is the first frame or because it was recently - /// recreated to match the dimensions of the window's swapchain, this will return `true`. - /// - /// This is useful for determining whether or not a user's framebuffer might need - /// reconstruction in the case that it targets the frame's image. - pub fn image_is_new(&self) -> bool { - self.raw_frame.nth() == 0 || self.data.intermediary.images_are_new + /// The color format of the `Frame`'s intermediary linear sRGBA texture (equal to + /// `Frame::TEXTURE_FORMAT`). + pub fn texture_format(&self) -> wgpu::TextureFormat { + Self::TEXTURE_FORMAT } - /// The color format of the `Frame`'s intermediary linear sRGBA image (equal to - /// `COLOR_FORMAT`). - pub fn image_format(&self) -> vk::Format { - vk::image::ImageAccess::format(self.image()) + /// The number of MSAA samples of the `Frame`'s intermediary linear sRGBA texture. + pub fn texture_msaa_samples(&self) -> u32 { + self.data.msaa_samples } - /// The number of MSAA samples of the `Frame`'s intermediary linear sRGBA image. - pub fn image_msaa_samples(&self) -> u32 { - vk::image::ImageAccess::samples(self.image()) + /// Short-hand for constructing a `wgpu::RenderPassColorAttachmentDescriptor` for use within a + /// render pass that targets this frame's texture. The returned descriptor's `attachment` will + /// the same `wgpu::TextureView` returned by the `Frame::texture` method. + /// + /// Note that this method will not perform any resolving. In the case that `msaa_samples` is + /// greater than `1`, a render pass will be automatically added after the `view` completes and + /// before the texture is drawn to the swapchain. + pub fn color_attachment_descriptor(&self) -> wgpu::RenderPassColorAttachmentDescriptor { + let load_op = wgpu::LoadOp::Load; + let store_op = wgpu::StoreOp::Store; + let attachment = match self.data.intermediary_lin_srgba.msaa_texture { + None => &self.data.intermediary_lin_srgba.texture, + Some(ref msaa_texture) => msaa_texture, + }; + let resolve_target = None; + let clear_color = wgpu::Color::TRANSPARENT; + wgpu::RenderPassColorAttachmentDescriptor { + attachment, + resolve_target, + load_op, + store_op, + clear_color, + } } - /// Clear the image with the given color. + /// Clear the texture with the given color. pub fn clear(&self, color: C) where C: IntoLinSrgba, { let lin_srgba = color.into_lin_srgba(); let (r, g, b, a) = lin_srgba.into_components(); - let value = vk::ClearValue::Float([r, g, b, a]); - let image = self.image().clone(); - self.add_commands() - .clear_color_image(image, value) - .expect("failed to submit `clear_color_image` command"); - } -} - -impl ViewFramebufferObject { - /// Ensure the framebuffer is up to date with the render pass and `frame`'s image. - pub fn update( - &mut self, - frame: &Frame, - render_pass: R, - builder: F, - ) -> Result<(), vk::FramebufferCreationError> - where - R: 'static + vk::RenderPassAbstract + Send + Sync, - F: FnOnce( - vk::FramebufferBuilder, - Arc, - ) -> vk::FramebufferBuilderResult, - A: 'static + vk::AttachmentsList + Send + Sync, - { - let image = frame.image().clone(); - let [w, h] = image.dimensions(); - let dimensions = [w, h, 1]; - self.fbo - .update(render_pass, dimensions, |b| builder(b, image)) + let (r, g, b, a) = (r as f64, g as f64, b as f64, a as f64); + let color = wgpu::Color { r, g, b, a }; + wgpu::clear_texture(self.texture(), color, &mut *self.command_encoder()) } } impl RenderData { /// Initialise the render data. /// - /// Creates an `vk::AttachmentImage` with the given parameters. + /// Creates an `wgpu::TextureView` with the given parameters. /// - /// If `msaa_samples` is greater than 1 a `multisampled` image will be created. Otherwise the + /// If `msaa_samples` is greater than 1 a `multisampled` texture will also be created. Otherwise the /// a regular non-multisampled image will be created. pub(crate) fn new( - device: Arc, - dimensions: [u32; 2], + device: &wgpu::Device, + swap_chain_dims: [u32; 2], + swap_chain_format: wgpu::TextureFormat, msaa_samples: u32, - ) -> Result { - let intermediary_images = - create_intermediary_images(device.clone(), dimensions, msaa_samples, COLOR_FORMAT)?; - let resolve_render_pass = create_resolve_render_pass(device.clone(), msaa_samples)?; - let resolve_framebuffer = Default::default(); - let intermediary = IntermediaryData { - images: intermediary_images, - images_are_new: true, - resolve_render_pass, - resolve_framebuffer, - }; - Ok(RenderData { intermediary }) - } -} - -impl ops::Deref for Frame { - type Target = RawFrame; - fn deref(&self) -> &Self::Target { - &self.raw_frame - } -} - -impl ops::Deref for ViewFramebufferObject { - type Target = vk::Fbo; - fn deref(&self) -> &Self::Target { - &self.fbo - } -} - -impl From for RenderDataCreationError { - fn from(err: vk::RenderPassCreationError) -> Self { - RenderDataCreationError::RenderPassCreation(err) - } -} - -impl From for RenderDataCreationError { - fn from(err: vk::ImageCreationError) -> Self { - RenderDataCreationError::ImageCreation(err) - } -} - -impl From for RenderDataCreationError { - fn from(err: GraphicsPipelineError) -> Self { - RenderDataCreationError::GraphicsPipelineCreation(err) - } -} - -impl From for RenderDataCreationError { - fn from(err: vk::memory::DeviceMemoryAllocError) -> Self { - RenderDataCreationError::DeviceMemoryAlloc(err) - } -} - -impl From for RenderDataCreationError { - fn from(err: vk::SamplerCreationError) -> Self { - RenderDataCreationError::SamplerCreation(err) - } -} - -impl From for FrameCreationError { - fn from(err: vk::ImageCreationError) -> Self { - FrameCreationError::ImageCreation(err) - } -} - -impl From for FrameCreationError { - fn from(err: vk::FramebufferCreationError) -> Self { - FrameCreationError::FramebufferCreation(err) - } -} - -impl From for FrameFinishError { - fn from(err: vk::command_buffer::BeginRenderPassError) -> Self { - FrameFinishError::BeginRenderPass(err) - } -} - -impl From for GraphicsPipelineError { - fn from(err: vk::GraphicsPipelineCreationError) -> Self { - GraphicsPipelineError::Creation(err) - } -} - -impl StdError for RenderDataCreationError { - fn description(&self) -> &str { - match *self { - RenderDataCreationError::RenderPassCreation(ref err) => err.description(), - RenderDataCreationError::ImageCreation(ref err) => err.description(), - RenderDataCreationError::GraphicsPipelineCreation(ref err) => err.description(), - RenderDataCreationError::DeviceMemoryAlloc(ref err) => err.description(), - RenderDataCreationError::SamplerCreation(ref err) => err.description(), - } - } - - fn cause(&self) -> Option<&dyn StdError> { - match *self { - RenderDataCreationError::RenderPassCreation(ref err) => Some(err), - RenderDataCreationError::ImageCreation(ref err) => Some(err), - RenderDataCreationError::GraphicsPipelineCreation(ref err) => Some(err), - RenderDataCreationError::DeviceMemoryAlloc(ref err) => Some(err), - RenderDataCreationError::SamplerCreation(ref err) => Some(err), - } - } -} - -impl StdError for FrameCreationError { - fn description(&self) -> &str { - match *self { - FrameCreationError::ImageCreation(ref err) => err.description(), - FrameCreationError::FramebufferCreation(ref err) => err.description(), - } - } - - fn cause(&self) -> Option<&dyn StdError> { - match *self { - FrameCreationError::ImageCreation(ref err) => Some(err), - FrameCreationError::FramebufferCreation(ref err) => Some(err), - } - } -} - -impl StdError for FrameFinishError { - fn description(&self) -> &str { - match *self { - FrameFinishError::BeginRenderPass(ref err) => err.description(), - } - } - - fn cause(&self) -> Option<&dyn StdError> { - match *self { - FrameFinishError::BeginRenderPass(ref err) => Some(err), - } - } -} - -impl StdError for GraphicsPipelineError { - fn description(&self) -> &str { - match *self { - GraphicsPipelineError::Creation(ref err) => err.description(), - GraphicsPipelineError::VertexShaderLoad(ref err) => err.description(), - GraphicsPipelineError::FragmentShaderLoad(ref err) => err.description(), + ) -> Self { + let intermediary_lin_srgba = + create_intermediary_lin_srgba(device, swap_chain_dims, msaa_samples); + let texture_format_converter = wgpu::TextureFormatConverter::new( + device, + &intermediary_lin_srgba.texture, + swap_chain_format, + ); + RenderData { + intermediary_lin_srgba, + texture_format_converter, + msaa_samples, } } } -impl fmt::Display for RenderDataCreationError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.description()) - } -} - -impl fmt::Display for FrameCreationError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.description()) - } -} - -impl fmt::Display for FrameFinishError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.description()) - } -} - -impl fmt::Debug for RenderData { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "RenderData") - } -} - -impl fmt::Display for GraphicsPipelineError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.description()) - } +fn create_lin_srgba_msaa_texture( + device: &wgpu::Device, + swap_chain_dims: [u32; 2], + msaa_samples: u32, +) -> wgpu::TextureView { + let [width, height] = swap_chain_dims; + let msaa_texture_extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + let msaa_texture_descriptor = wgpu::TextureDescriptor { + size: msaa_texture_extent, + array_layer_count: 1, + mip_level_count: 1, + sample_count: msaa_samples, + dimension: wgpu::TextureDimension::D2, + format: Frame::TEXTURE_FORMAT, + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + }; + device + .create_texture(&msaa_texture_descriptor) + .create_default_view() } -// A function to simplify creating the `vk::AttachmentImage` used as the intermediary image render -// target. -// -// If `msaa_samples` is 0 or 1, a non-multisampled image will be created. -fn create_intermediary_images( - device: Arc, - dimensions: [u32; 2], - msaa_samples: u32, - format: vk::Format, -) -> Result { - let lin_srgba_msaa = match msaa_samples { - 0 | 1 => None, - _ => { - let usage = vk::ImageUsage { - transfer_source: true, - transfer_destination: true, - color_attachment: true, - //sampled: true, - ..vk::ImageUsage::none() - }; - Some(vk::AttachmentImage::multisampled_with_usage( - device.clone(), - dimensions, - msaa_samples, - format, - usage, - )?) - } +fn create_lin_srgba_texture(device: &wgpu::Device, swap_chain_dims: [u32; 2]) -> wgpu::TextureView { + let [width, height] = swap_chain_dims; + let texture_extent = wgpu::Extent3d { + width, + height, + depth: 1, }; - let usage = vk::ImageUsage { - transfer_source: true, - transfer_destination: true, - color_attachment: true, - sampled: true, - ..vk::ImageUsage::none() + let texture_descriptor = wgpu::TextureDescriptor { + size: texture_extent, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: Frame::TEXTURE_FORMAT, + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT | wgpu::TextureUsage::SAMPLED, }; - let lin_srgba = vk::AttachmentImage::with_usage(device, dimensions, format, usage)?; - Ok(IntermediaryImages { - lin_srgba_msaa, - lin_srgba, - }) + device + .create_texture(&texture_descriptor) + .create_default_view() } -// Create the render pass for resolving the MSAA linear sRGB image to the non-MSAA image. -fn create_resolve_render_pass( - device: Arc, +fn create_intermediary_lin_srgba( + device: &wgpu::Device, + swap_chain_dims: [u32; 2], msaa_samples: u32, -) -> Result>, vk::RenderPassCreationError> { - match msaa_samples { - // Render pass without multisampling. - 0 | 1 => Ok(None), - - // Renderpass with multisampling. - _ => { - let rp = vk::single_pass_renderpass!( - device, - attachments: { - lin_srgba_msaa: { - load: Load, - store: Store, - format: COLOR_FORMAT, - samples: msaa_samples, - }, - lin_srgba: { - load: DontCare, - store: Store, - format: COLOR_FORMAT, - samples: 1, - } - }, - pass: { - color: [lin_srgba_msaa], - depth_stencil: {} - resolve: [lin_srgba], - } - )?; - Ok(Some( - Arc::new(rp) as Arc - )) - } +) -> IntermediaryLinSrgba { + let msaa_texture = match msaa_samples { + 0 | 1 => None, + _ => Some(create_lin_srgba_msaa_texture( + device, + swap_chain_dims, + msaa_samples, + )), + }; + let texture = create_lin_srgba_texture(device, swap_chain_dims); + IntermediaryLinSrgba { + msaa_texture, + texture, } } diff --git a/src/frame/raw.rs b/src/frame/raw.rs index ba895ff77..585d4e5cb 100644 --- a/src/frame/raw.rs +++ b/src/frame/raw.rs @@ -1,16 +1,9 @@ -//! The lower-level "raw" frame type allowing to draw directly to the window's swapchain image. +//! The lower-level "raw" frame type allowing to draw directly to the window's swap chain image. -use crate::vk; -use crate::vk::command_buffer::pool::standard::StandardCommandPoolBuilder; -use crate::vk::command_buffer::{ - AutoCommandBufferBuilderContextError, BeginRenderPassError, BlitImageError, - ClearColorImageError, CopyBufferError, CopyBufferImageError, DispatchError, DrawError, - DrawIndexedError, DynamicState, FillBufferError, UpdateBufferError, -}; -use crate::vk::pipeline::input_assembly::Index; +use crate::geom; +use crate::wgpu; use crate::window; -use crate::window::SwapchainImage; -use std::sync::{Arc, Mutex}; +use std::sync::Mutex; /// Allows the user to draw a single **RawFrame** to the surface of a window. /// @@ -18,177 +11,73 @@ use std::sync::{Arc, Mutex}; /// new image that will be displayed to a window. The **RawFrame** type can be thought of as the /// canvas to which you draw this image. /// -/// ## Under the hood - Vulkan +/// ## Under the hood - WGPU /// -/// There are a couple of main goals for the **RawFrame** type: +/// **RawFrame** provides access to the **wgpu::TextureView** associated with the swap chain's +/// current target texture for a single window. /// -/// - Allow for maximum flexibility and customisation over the: -/// - Render Pass -/// - Graphics Pipeline -/// - Framebuffer Creation and -/// - Command Buffer -/// to the extent that the user may not interfere with the expected behaviour of the **App**. -/// - Provide reasonable defaults for each step so that it is friendly for new users. +/// In the case that your **view** function is shared between multiple windows, can determine which +/// window the **RawFrame** is associated with via the **RawFrame::window_id** method. /// -/// **Vulkan** -/// -/// Nannou uses Vulkan for interacting with the available graphics devices on a machine and for -/// presenting images to the swapchain associated with each window. It does so via the **vulkano** -/// crate, which is exposed publicly from the crate root. **vulkano** aims to be a type-safe, -/// near-zero-cost API around the low-level Vulkan API. As a result **vulkano** tends to be a lot -/// nicer than using the Vulkan API directly where it is the role of the user to maintain all the -/// invariants described within the spec themselves. Due to this higher-level nature, nannou -/// exposes the vulkano API directly in some areas where it is deemed reasonable/safe to do so. -/// -/// In order to provide maximum flexibility, nannou allows for fully custom -/// [**RenderPass**](https://docs.rs/vulkano/latest/vulkano/framebuffer/struct.RenderPass.html) and -/// [**GraphicsPipeline**](https://docs.rs/vulkano/latest/vulkano/pipeline/struct.GraphicsPipeline.html) -/// via the **vulkano** API but aims to take care of the surface creation, swapchain tedium and -/// rendering synchronisation behind the scenes. -/// -/// ### Render Pass and Swapchain Framebuffers. -/// -/// The render pass describes the destination for the output of the graphics pipeline. It is -/// essential that the render pass uses the same pixel format of the window's surface. It also must -/// be initialised with the same logical device with which the surface was initialised. -/// -/// For now, it is up to the user to ensure these gaurantees are met. In the future nannou may -/// provide a simplified constructor that implicitly uses the same logical device and format -/// associated with the surface. -/// -/// The user can create the framebuffers for the swapchain using this render pass by using the -/// **SwapchainFramebuffers** type. While under the hood there is no distinction between the -/// Framebuffer type used to draw to a swapchain image and any other image, nannou chooses to wrap -/// these framebuffers in a type to ensure the following invariants are met: -/// -/// - There must be one framebuffer per swapchain image. -/// - Each framebuffer must be recreated to match the dimensions of the swapchain each time the -/// swapchain requires recreation. This will occur any time the window is resized on desktop or -/// when an app comes in or out of focus on Android, and possibly in many other cases not -/// mentioned here. -/// - It should be impossible to write to these framebuffers outside the **view** function to -/// ensure framebuffer availability. -/// - Each call to **view** must draw to the framebuffer associated with the image that is ready as -/// indicated by the `swapchain::acquire_next_image` function. -/// -/// As a result, access to the swapchain framebuffers may feel relatively restrictive. If you -/// require greater flexibility (e.g. control over framebuffer dimensions, the ability to draw to a -/// framebuffer outside the **view** function, etc) then consider creating and writing to another -/// intermediary framebuffer before drawing to the swapchain framebuffers. -/// -/// See [the vulkano documenation](https://docs.rs/vulkano/latest/vulkano/framebuffer/index.html) -/// for more details on render passes and framebuffers. -/// -/// ### Graphics Pipeline -/// -/// The role of the `GraphicsPipeline` is similar to that of the GL "program" but much more -/// detailed and explicit. It allows for describing and chaining together a series of custom -/// shaders. -/// -/// For more information on the graphics pipeline and how to create one, see [the vulkano -/// documentation](https://docs.rs/vulkano/latest/vulkano/pipeline/index.html#creating-a-graphics-pipeline). -/// -/// ### Command Buffer -/// -/// The API for the **Frame** type maps directly onto a vulkano `AutoCommandBufferBuilder` under -/// the hood. This `AutoCommandBufferBuilder` is created using the `primary_one_time_submit` -/// constructor. When returned, the **App** will build the command buffer and submit it to the GPU. -/// Note that certain builder methods are *not* provided in order to avoid unexpected behaviour. -/// E.g. the `build` and submit methods are not provided as it is important that the **App** is -/// able to build the command buffer and synchronise its submission with the swapchain. -/// -/// Use the **frame.add_commands()** method to begin chaining together commands. You may call this -/// more than once throughout the duration of the **view** function. -/// -/// See [the vulkano -/// documentation](https://docs.rs/vulkano/latest/vulkano/command_buffer/index.html) for more -/// details on how command buffers work in vulkano. -/// -/// **Note:** If you find you are unable to do something or that this API is too restrictive, -/// please open an issue about it so that might be able to work out a solution! -pub struct RawFrame { - // The `AutoCommandBufferBuilder` type used for building the frame's command buffer. - // - // An `Option` is used here to allow for taking the builder by `self` as type's builder methods - // require consuming `self` and returning a new `AutoCommandBufferBuilder` as a result. - // - // This `Mutex` is only ever locked for the duration of the addition of a single command. - command_buffer_builder: Mutex>, - // The `Id` whose surface the swapchain image is associated with. +/// The user can draw to the swap chain texture by building a list of commands via a +/// `wgpu::CommandEncoder` and submitting them to the `wgpu::Queue` associated with the +/// `wgpu::Device` that was used to create the swap chain. It is important that the queue +/// matches the device. In an effort to reduce the chance for errors to occur, **RawFrame** +/// provides access to a `wgpu::CommandEncoder` whose commands are guaranteed to be submitted to +/// the correct `wgpu::Queue` at the end of the **view** function. +pub struct RawFrame<'swap_chain> { + command_encoder: Mutex, window_id: window::Id, - // The `nth` frame that has been presented to the window since the start of the application. nth: u64, - // The index associated with the swapchain image. - swapchain_image_index: usize, - // The image to which this frame is drawing. - swapchain_image: Arc, - // The index of the frame before which this swapchain was created. - swapchain_frame_created: u64, - // The queue on which the swapchain image will be drawn. - queue: Arc, -} - -/// A builder type that allows chaining together commands for the command buffer that will be used -/// to draw to the swapchain image framebuffer associated with this **RawFrame**. -pub struct AddCommands<'a> { - frame: &'a RawFrame, + swap_chain_texture: &'swap_chain wgpu::TextureView, + queue: &'swap_chain wgpu::Queue, + texture_format: wgpu::TextureFormat, + window_rect: geom::Rect, } -// The `AutoCommandBufferBuilder` type used for building the frame's command buffer. -type AutoCommandBufferBuilder = vk::AutoCommandBufferBuilder; - -impl RawFrame { +impl<'swap_chain> RawFrame<'swap_chain> { // Initialise a new empty frame ready for "drawing". pub(crate) fn new_empty( - queue: Arc, + device: &'swap_chain wgpu::Device, + queue: &'swap_chain wgpu::Queue, window_id: window::Id, nth: u64, - swapchain_image_index: usize, - swapchain_image: Arc, - swapchain_frame_created: u64, - ) -> Result { - let device = queue.device().clone(); - let cb_builder = AutoCommandBufferBuilder::primary_one_time_submit(device, queue.family())?; - let command_buffer_builder = Mutex::new(Some(cb_builder)); + swap_chain_texture: &'swap_chain wgpu::TextureView, + texture_format: wgpu::TextureFormat, + window_rect: geom::Rect, + ) -> Self { + let ce_desc = wgpu::CommandEncoderDescriptor::default(); + let command_encoder = device.create_command_encoder(&ce_desc); + let command_encoder = Mutex::new(command_encoder); let frame = RawFrame { - command_buffer_builder, + command_encoder, window_id, nth, - swapchain_image_index, - swapchain_image, - swapchain_frame_created, + swap_chain_texture, queue, + texture_format, + window_rect, }; - Ok(frame) + frame } // Called after the user's `view` function, this consumes the `RawFrame` and returns the inner // command buffer builder so that it can be completed. - pub(crate) fn finish(self) -> AutoCommandBufferBuilder { - self.command_buffer_builder + pub(crate) fn finish(self) -> wgpu::CommandEncoder { + let RawFrame { + command_encoder, .. + } = self; + command_encoder + .into_inner() + .expect("failed to lock `command_encoder`") + } + + /// Access the command encoder in order to encode commands that will be submitted to the swap + /// chain queue at the end of the call to **view**. + pub fn command_encoder(&self) -> std::sync::MutexGuard { + self.command_encoder .lock() - .expect("failed to lock `command_buffer_builder`") - .take() - .expect("`command_buffer_builder` was `None`") - } - - /// Returns whether or not this is the first time this swapchain image has been presented. - /// - /// This will be `true` following each occurrence at which the swapchain has been recreated, - /// which may occur during resize, loop mode switch, etc. - /// - /// It is important to call this each frame to determine whether or not framebuffers associated - /// with the swapchain need to be recreated. - pub fn swapchain_image_is_new(&self) -> bool { - // TODO: This is based on the assumption that the images will be acquired starting from - // index `0` each time the swapchain is recreated. Verify that this is the case. - (self.nth - self.swapchain_image_index as u64) == self.swapchain_frame_created - } - - /// Add commands to be executed by the GPU once the **RawFrame** is returned. - pub fn add_commands(&self) -> AddCommands { - let frame = self; - AddCommands { frame } + .expect("failed to acquire lock to command encoder") } /// The `Id` of the window whose vulkan surface is associated with this frame. @@ -196,6 +85,14 @@ impl RawFrame { self.window_id } + /// A **Rect** representing the full surface of the frame. + /// + /// The returned **Rect** is equivalent to the result of calling **Window::rect** on the window + /// associated with this **Frame**. + pub fn rect(&self) -> geom::Rect { + self.window_rect + } + /// The `nth` frame for the associated window since the application started. /// /// E.g. the first frame yielded will return `0`, the second will return `1`, and so on. @@ -203,445 +100,19 @@ impl RawFrame { self.nth } - /// The swapchain image that will be the target for this frame. - /// - /// NOTE: You should avoid using the returned `SwapchainImage` outside of the `view` function - /// as it may become invalid at any moment. The reason we expose the `Arc` is that some of the - /// vulkano API (including framebuffer creation) requires it to avoid some severe ownsership - /// issues. - pub fn swapchain_image(&self) -> &Arc { - &self.swapchain_image - } - - /// The index associated with the swapchain image that will be the target for this frame. - pub fn swapchain_image_index(&self) -> usize { - self.swapchain_image_index - } - - /// The queue on which the swapchain image will be drawn. - pub fn queue(&self) -> &Arc { - &self.queue - } -} - -impl<'a> AddCommands<'a> { - // Maps a call onto the command buffer builder. - fn map_cb(self, map: F) -> Result - where - F: FnOnce(AutoCommandBufferBuilder) -> Result, - { - { - let mut guard = self - .frame - .command_buffer_builder - .lock() - .expect("failed to lock `RawFrame`'s inner command buffer builder"); - let mut builder = guard - .take() - .expect("the `RawFrame`'s inner command buffer should always be `Some`"); - builder = map(builder)?; - *guard = Some(builder); - } - Ok(self) - } - - pub fn dispatch( - self, - dimensions: [u32; 3], - pipeline: Cp, - sets: S, - constants: Pc, - ) -> Result - where - Cp: vk::ComputePipelineAbstract + Send + Sync + 'static + Clone, // TODO: meh for Clone - S: vk::DescriptorSetsCollection, - { - self.map_cb(move |cb| cb.dispatch(dimensions, pipeline, sets, constants)) - } - - /// Adds a command that enters a render pass. - /// - /// C must contain exactly one clear value for each attachment in the framebuffer. - /// - /// You must call this before you can add draw commands. - /// - /// [*Documentation adapted from the corresponding vulkano method.*](https://docs.rs/vulkano/latest/vulkano/command_buffer/struct.AutoCommandBufferBuilder.html) - pub fn begin_render_pass( - self, - framebuffer: F, - clear_values: C, - ) -> Result - where - F: vk::FramebufferAbstract - + vk::RenderPassDescClearValues - + Clone - + Send - + Sync - + 'static, - { - self.map_cb(move |cb| cb.begin_render_pass(framebuffer, false, clear_values)) - } - - /// Adds a command that jumps to the next subpass of the current render pass. - pub fn next_subpass(self) -> Result { - self.map_cb(move |cb| cb.next_subpass(false)) + /// The swap chain texture that will be the target for drawing this frame. + pub fn swap_chain_texture(&self) -> &wgpu::TextureView { + &self.swap_chain_texture } - /// Adds a command that ends the current render pass. - /// - /// This must be called after you went through all the subpasses and before you can add further - /// commands. - pub fn end_render_pass(self) -> Result { - self.map_cb(move |cb| cb.end_render_pass()) + /// The texture format of the frame's swap chain texture. + pub fn texture_format(&self) -> wgpu::TextureFormat { + self.texture_format } - /// Adds a command that blits an image to another. - /// - /// A *blit* is similar to an image copy operation, except that the portion of the image that - /// is transferred can be resized. You choose an area of the source and an area of the - /// destination, and the implementation will resize the area of the source so that it matches - /// the size of the area of the destination before writing it. - /// - /// Blit operations have several restrictions: - /// - /// - Blit operations are only allowed on queue families that support graphics operations. - /// - The format of the source and destination images must support blit operations, which - /// depends on the Vulkan implementation. Vulkan guarantees that some specific formats must - /// always be supported. See tables 52 to 61 of the specifications. - /// - Only single-sampled images are allowed. - /// - You can only blit between two images whose formats belong to the same type. The types - /// are: floating-point, signed integers, unsigned integers, depth-stencil. - /// - If you blit between depth, stencil or depth-stencil images, the format of both images - /// must match exactly. - /// - If you blit between depth, stencil or depth-stencil images, only the `Nearest` filter is - /// allowed. - /// - For two-dimensional images, the Z coordinate must be 0 for the top-left offset and 1 for - /// the bottom-right offset. Same for the Y coordinate for one-dimensional images. - /// - For non-array images, the base array layer must be 0 and the number of layers must be 1. - /// - /// If `layer_count` is greater than 1, the blit will happen between each individual layer as - /// if they were separate images. - /// - /// # Panic - /// - /// - Panics if the source or the destination was not created with `device`. - /// - /// [*Documentation taken from the corresponding vulkano method.*](https://docs.rs/vulkano/latest/vulkano/command_buffer/struct.AutoCommandBufferBuilder.html) - pub fn blit_image( - self, - source: S, - source_top_left: [i32; 3], - source_bottom_right: [i32; 3], - source_base_array_layer: u32, - source_mip_level: u32, - destination: D, - destination_top_left: [i32; 3], - destination_bottom_right: [i32; 3], - destination_base_array_layer: u32, - destination_mip_level: u32, - layer_count: u32, - filter: vk::sampler::Filter, - ) -> Result - where - S: vk::ImageAccess + Send + Sync + 'static, - D: vk::ImageAccess + Send + Sync + 'static, - { - self.map_cb(move |cb| { - cb.blit_image( - source, - source_top_left, - source_bottom_right, - source_base_array_layer, - source_mip_level, - destination, - destination_top_left, - destination_bottom_right, - destination_base_array_layer, - destination_mip_level, - layer_count, - filter, - ) - }) - } - - /// Adds a command that copies an image to another. - /// - /// Copy operations have several restrictions: - /// - /// - Copy operations are only allowed on queue families that support transfer, graphics, or - /// compute operations. - /// - The number of samples in the source and destination images must be equal. - /// - The size of the uncompressed element format of the source image must be equal to the - /// compressed element format of the destination. - /// - If you copy between depth, stencil or depth-stencil images, the format of both images - /// must match exactly. - /// - For two-dimensional images, the Z coordinate must be 0 for the image offsets and 1 for - /// the extent. Same for the Y coordinate for one-dimensional images. - /// - For non-array images, the base array layer must be 0 and the number of layers must be 1. - /// - /// If layer_count is greater than 1, the copy will happen between each individual layer as if - /// they were separate images. - /// - /// # Panic - /// - /// - Panics if the source or the destination was not created with device. - /// - /// [*Documentation taken from the corresponding vulkano method.*](https://docs.rs/vulkano/latest/vulkano/command_buffer/struct.AutoCommandBufferBuilder.html) - pub fn copy_image( - self, - source: S, - source_offset: [i32; 3], - source_base_array_layer: u32, - source_mip_level: u32, - destination: D, - destination_offset: [i32; 3], - destination_base_array_layer: u32, - destination_mip_level: u32, - extent: [u32; 3], - layer_count: u32, - ) -> Result - // TODO: Expose error: https://github.com/vulkano-rs/vulkano/pull/1112 - where - S: vk::ImageAccess + Send + Sync + 'static, - D: vk::ImageAccess + Send + Sync + 'static, - { - self.map_cb(move |cb| { - cb.copy_image( - source, - source_offset, - source_base_array_layer, - source_mip_level, - destination, - destination_offset, - destination_base_array_layer, - destination_mip_level, - extent, - layer_count, - ) - }) - .map_err(|err| panic!("{}", err)) - } - - /// Adds a command that clears all the layers and mipmap levels of a color image with a - /// specific value. - /// - /// # Panic - /// - /// Panics if `color` is not a color value. - /// - /// [*Documentation taken from the corresponding vulkano method.*](https://docs.rs/vulkano/latest/vulkano/command_buffer/struct.AutoCommandBufferBuilder.html) - pub fn clear_color_image( - self, - image: I, - color: vk::ClearValue, - ) -> Result - where - I: vk::ImageAccess + Send + Sync + 'static, - { - self.map_cb(move |cb| cb.clear_color_image(image, color)) - } - - /// Adds a command that clears a color image with a specific value. - /// - /// # Panic - /// - /// Panics if color is not a color value. - pub fn clear_color_image_dimensions( - self, - image: I, - first_layer: u32, - num_layers: u32, - first_mipmap: u32, - num_mipmaps: u32, - color: vk::ClearValue, - ) -> Result - where - I: vk::ImageAccess + Send + Sync + 'static, - { - self.map_cb(move |cb| { - cb.clear_color_image_dimensions( - image, - first_layer, - num_layers, - first_mipmap, - num_mipmaps, - color, - ) - }) - } - - /// Adds a command that copies from a buffer to another. - /// - /// This command will copy from the source to the destination. If their size is not equal, then - /// the amount of data copied is equal to the smallest of the two. - pub fn copy_buffer(self, source: S, destination: D) -> Result - where - S: vk::TypedBufferAccess + Send + Sync + 'static, - D: vk::TypedBufferAccess + Send + Sync + 'static, - T: ?Sized, - { - self.map_cb(move |cb| cb.copy_buffer(source, destination)) - } - - /// Adds a command that copies from a buffer to an image. - pub fn copy_buffer_to_image( - self, - source: S, - destination: D, - ) -> Result - where - S: vk::TypedBufferAccess + Send + Sync + 'static, - D: vk::ImageAccess + Send + Sync + 'static, - vk::Format: vk::AcceptsPixels, - { - self.map_cb(move |cb| cb.copy_buffer_to_image(source, destination)) - } - - /// Adds a command that copies from a buffer to an image. - pub fn copy_buffer_to_image_dimensions( - self, - source: S, - destination: D, - offset: [u32; 3], - size: [u32; 3], - first_layer: u32, - num_layers: u32, - mipmap: u32, - ) -> Result - where - S: vk::TypedBufferAccess + Send + Sync + 'static, - D: vk::ImageAccess + Send + Sync + 'static, - vk::Format: vk::AcceptsPixels, - { - self.map_cb(move |cb| { - cb.copy_buffer_to_image_dimensions( - source, - destination, - offset, - size, - first_layer, - num_layers, - mipmap, - ) - }) - } - - /// Adds a command that copies from an image to a buffer. - pub fn copy_image_to_buffer( - self, - source: S, - destination: D, - ) -> Result - where - S: vk::ImageAccess + Send + Sync + 'static, - D: vk::TypedBufferAccess + Send + Sync + 'static, - vk::Format: vk::AcceptsPixels, - { - self.map_cb(move |cb| cb.copy_image_to_buffer(source, destination)) - } - - /// Adds a command that copies from an image to a buffer. - pub fn copy_image_to_buffer_dimensions( - self, - source: S, - destination: D, - offset: [u32; 3], - size: [u32; 3], - first_layer: u32, - num_layers: u32, - mipmap: u32, - ) -> Result - where - S: vk::ImageAccess + Send + Sync + 'static, - D: vk::TypedBufferAccess + Send + Sync + 'static, - vk::Format: vk::AcceptsPixels, - { - self.map_cb(move |cb| { - cb.copy_image_to_buffer_dimensions( - source, - destination, - offset, - size, - first_layer, - num_layers, - mipmap, - ) - }) - } - - /// Draw once, using the vertex_buffer. - /// - /// To use only some data in the buffer, wrap it in a `vk::BufferSlice`. - pub fn draw( - self, - pipeline: Gp, - dynamic: &DynamicState, - vertex_buffer: V, - sets: S, - constants: Pc, - ) -> Result - where - Gp: vk::GraphicsPipelineAbstract + vk::VertexSource + Send + Sync + 'static + Clone, - S: vk::DescriptorSetsCollection, - { - self.map_cb(move |cb| cb.draw(pipeline, dynamic, vertex_buffer, sets, constants)) - } - - /// Draw once, using the vertex_buffer and the index_buffer. - /// - /// To use only some data in a buffer, wrap it in a `vk::BufferSlice`. - pub fn draw_indexed( - self, - pipeline: Gp, - dynamic: &DynamicState, - vertex_buffer: V, - index_buffer: Ib, - sets: S, - constants: Pc, - ) -> Result - where - Gp: vk::GraphicsPipelineAbstract + vk::VertexSource + Send + Sync + 'static + Clone, - S: vk::DescriptorSetsCollection, - Ib: vk::BufferAccess + vk::TypedBufferAccess + Send + Sync + 'static, - I: Index + 'static, - { - self.map_cb(move |cb| { - cb.draw_indexed( - pipeline, - dynamic, - vertex_buffer, - index_buffer, - sets, - constants, - ) - }) - } - - /// Adds a command that writes the content of a buffer. - /// - /// This function is similar to the `memset` function in C. The `data` parameter is a number - /// that will be repeatedly written through the entire buffer. - /// - /// > **Note**: This function is technically safe because buffers can only contain integers or - /// > floating point numbers, which are always valid whatever their memory representation is. - /// > But unless your buffer actually contains only 32-bits integers, you are encouraged to use - /// > this function only for zeroing the content of a buffer by passing `0` for the data. - pub fn fill_buffer(self, buffer: B, data: u32) -> Result - where - B: vk::BufferAccess + Send + Sync + 'static, - { - self.map_cb(move |cb| cb.fill_buffer(buffer, data)) - } - - /// Adds a command that writes data to a buffer. - /// - /// If data is larger than the buffer, only the part of data that fits is written. If the - /// buffer is larger than data, only the start of the buffer is written. - pub fn update_buffer(self, buffer: B, data: D) -> Result - where - B: vk::TypedBufferAccess + Send + Sync + 'static, - D: Send + Sync + 'static, - { - self.map_cb(move |cb| cb.update_buffer(buffer, data)) + /// The queue on which the swap chain was created and which will be used to submit the + /// **RawFrame**'s encoded commands. + pub fn queue(&self) -> &wgpu::Queue { + self.queue } } diff --git a/src/io.rs b/src/io.rs index 70aa42755..18bea3130 100644 --- a/src/io.rs +++ b/src/io.rs @@ -48,10 +48,10 @@ impl Error for FileError where E: Error, { - fn description(&self) -> &str { + fn cause(&self) -> Option<&dyn Error> { match *self { - FileError::Io(ref err) => err.description(), - FileError::Format(ref err) => err.description(), + FileError::Io(ref err) => Some(err), + FileError::Format(ref err) => Some(err), } } } @@ -61,7 +61,10 @@ where E: Error, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.description()) + match *self { + FileError::Io(ref err) => fmt::Display::fmt(err, f), + FileError::Format(ref err) => fmt::Display::fmt(err, f), + } } } diff --git a/src/lib.rs b/src/lib.rs index 7470d24e5..07365db79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,20 +14,16 @@ //! nannou applications are structured and how the API works. pub use conrod_core; -pub use conrod_vulkano; pub use conrod_winit; pub use daggy; pub use find_folder; pub use lyon; use serde_derive; -pub use vulkano; -pub use vulkano_shaders; -pub use vulkano_win; pub use winit; pub use self::event::Event; pub use self::frame::Frame; -pub use self::ui::Ui; +//pub use self::ui::Ui; pub use crate::app::{App, LoopMode}; pub use crate::draw::Draw; @@ -48,8 +44,8 @@ pub mod rand; pub mod state; pub mod text; pub mod time; -pub mod ui; -pub mod vk; +//pub mod ui; +pub mod wgpu; pub mod window; /// Begin building the `App`. diff --git a/src/prelude.rs b/src/prelude.rs index 078c5335a..d2c651f52 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -12,7 +12,7 @@ pub use crate::event::{ AxisMotion, Event, Key, MouseButton, MouseScrollDelta, TouchEvent, TouchPhase, TouchpadPressure, Update, WindowEvent, }; -pub use crate::frame::{Frame, RawFrame, ViewFbo, ViewFramebufferObject}; +pub use crate::frame::{Frame, RawFrame}; pub use crate::geom::{ self, pt2, pt3, vec2, vec3, vec4, Cuboid, Point2, Point3, Rect, Vector2, Vector3, Vector4, }; @@ -26,8 +26,7 @@ pub use crate::math::{ pub use crate::rand::{random, random_f32, random_f64, random_range}; pub use crate::text::{self, text}; pub use crate::time::DurationF64; -pub use crate::ui; -pub use crate::vk::{self, DeviceOwned as VulkanDeviceOwned, DynamicStateBuilder, GpuFuture}; +//pub use crate::ui; pub use crate::window::{self, Id as WindowId}; // The following constants have "regular" names for the `DefaultScalar` type and type suffixes for diff --git a/src/text/font.rs b/src/text/font.rs index 3ed4cf99f..6cae49631 100644 --- a/src/text/font.rs +++ b/src/text/font.rs @@ -210,10 +210,10 @@ impl From for Error { } impl std::error::Error for Error { - fn description(&self) -> &str { + fn cause(&self) -> Option<&dyn std::error::Error> { match *self { - Error::Io(ref e) => std::error::Error::description(e), - Error::NoFont => "No `Font` found in the loaded `FontCollection`.", + Error::Io(ref e) => Some(e), + Error::NoFont => None, } } } @@ -222,7 +222,7 @@ impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { match *self { Error::Io(ref e) => std::fmt::Display::fmt(e, f), - _ => write!(f, "{}", std::error::Error::description(self)), + Error::NoFont => write!(f, "No `Font` found in the loaded `FontCollection`."), } } } diff --git a/src/ui.rs b/src/ui.rs index d2b069a43..3ed5f33d9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -9,7 +9,7 @@ pub use self::conrod_core::{ Range, Rect, Scalar, Sizeable, Theme, UiCell, Widget, }; pub use crate::conrod_core; -pub use crate::conrod_vulkano; +pub use crate::conrod_wgpu; pub use crate::conrod_winit; /// Simplify inclusion of common traits with a `nannou::ui::prelude` module. @@ -25,7 +25,6 @@ pub mod prelude { } use self::conrod_core::text::rt::gpu_cache::CacheWriteErr; -use self::conrod_vulkano::RendererCreationError; use crate::frame::{Frame, ViewFbo}; use crate::text::{font, Font}; use crate::window::{self, Window}; @@ -676,13 +675,13 @@ pub fn draw_primitives( } mod conrod_winit_conv { - conrod_winit::conversion_fns!(); + conrod_winit::v021::conversion_fns!(); } /// Convert the given window event to a UI Input. /// /// Returns `None` if there's no associated UI Input for the given event. -pub fn winit_window_event_to_input(event: winit::WindowEvent, window: &Window) -> Option { +pub fn winit_window_event_to_input(event: winit::window::WindowEvent, window: &Window) -> Option { conrod_winit_conv::convert_window_event(event, window) } @@ -692,72 +691,18 @@ impl From for BuildError { } } -impl From for BuildError { - fn from(err: RendererCreationError) -> Self { - BuildError::RendererCreation(err) - } -} - impl From for BuildError { fn from(err: text::font::Error) -> Self { BuildError::FailedToLoadFont(err) } } -impl From for RenderTargetCreationError { - fn from(err: vk::ImageCreationError) -> Self { - RenderTargetCreationError::ImageCreation(err) - } -} - -impl From for RenderTargetCreationError { - fn from(err: vk::RenderPassCreationError) -> Self { - RenderTargetCreationError::RenderPassCreation(err) - } -} - -impl From for DrawToFrameError { - fn from(err: vk::ImageCreationError) -> Self { - DrawToFrameError::ImageCreation(err) - } -} - -impl From for DrawToFrameError { - fn from(err: vk::FramebufferCreationError) -> Self { - DrawToFrameError::FramebufferCreation(err) - } -} - impl From for DrawToFrameError { fn from(err: CacheWriteErr) -> Self { DrawToFrameError::RendererFill(err) } } -impl From for DrawToFrameError { - fn from(err: vk::memory::DeviceMemoryAllocError) -> Self { - DrawToFrameError::GlyphPixelDataUpload(err) - } -} - -impl From for DrawToFrameError { - fn from(err: vk::command_buffer::CopyBufferImageError) -> Self { - DrawToFrameError::CopyBufferImageCommand(err) - } -} - -impl From for DrawToFrameError { - fn from(err: conrod_vulkano::DrawError) -> Self { - DrawToFrameError::RendererDraw(err) - } -} - -impl From for DrawToFrameError { - fn from(err: vk::command_buffer::DrawError) -> Self { - DrawToFrameError::DrawCommand(err) - } -} - impl StdError for BuildError { fn description(&self) -> &str { match *self { diff --git a/src/vk.rs b/src/vk.rs deleted file mode 100644 index ede715866..000000000 --- a/src/vk.rs +++ /dev/null @@ -1,775 +0,0 @@ -//! Items related to Vulkan and the Rust API used by Nannou called Vulkano. -//! -//! This module re-exports the entire `vulkano` crate along with all of its documentation while -//! also adding some additional helper types. -//! -//! Individual items from throughout the vulkano crate have been re-exported within this module for -//! ease of access via the `vk::` prefix, removing the need for a lot of boilerplate when coding. -//! However, as a result, the documentation for this module is quite noisey! You can find cleaner -//! information about how the different areas of Vulkan interoperate by checking out the -//! module-level documentation for that area. For example, read about Framebuffers and RenderPasses -//! in the `nannou::vk::framebuffer` module, or read about the CommandBuffer within the -//! `nannou::vk::command_buffer` module. -//! -//! For more information on extensions to the vulkano crate added by nannou, scroll past the -//! "Re-exports" items below. - -// Re-export `vulkano` along with its docs under this short-hand `vk` module. -#[doc(inline)] -pub use vulkano::*; - -// Re-export type and trait names whose meaning are still obvious outside of their module. -pub use vulkano::{ - buffer::cpu_pool::{CpuBufferPoolChunk, CpuBufferPoolSubbuffer}, - buffer::{ - BufferAccess, BufferCreationError, BufferInner, BufferSlice, BufferUsage, BufferView, - BufferViewRef, CpuAccessibleBuffer, CpuBufferPool, DeviceLocalBuffer, ImmutableBuffer, - TypedBufferAccess, - }, - command_buffer::{ - AutoCommandBuffer, AutoCommandBufferBuilder, AutoCommandBufferBuilderContextError, - CommandBuffer, CommandBufferExecFuture, DispatchIndirectCommand, DrawIndirectCommand, - DynamicState, ExecuteCommandsError, StateCacherOutcome, UpdateBufferError, - }, - descriptor::descriptor::{ - DescriptorBufferDesc, DescriptorDesc, DescriptorDescSupersetError, DescriptorDescTy, - DescriptorImageDesc, DescriptorImageDescArray, DescriptorImageDescDimensions, - DescriptorType, ShaderStages, ShaderStagesSupersetError, - }, - descriptor::descriptor_set::{ - DescriptorPool, DescriptorPoolAlloc, DescriptorPoolAllocError, DescriptorSetDesc, - DescriptorSetsCollection, DescriptorWrite, DescriptorsCount, FixedSizeDescriptorSet, - FixedSizeDescriptorSetBuilder, FixedSizeDescriptorSetBuilderArray, - FixedSizeDescriptorSetsPool, PersistentDescriptorSet, PersistentDescriptorSetBuf, - PersistentDescriptorSetBufView, PersistentDescriptorSetBuildError, - PersistentDescriptorSetBuilder, PersistentDescriptorSetBuilderArray, - PersistentDescriptorSetError, PersistentDescriptorSetImg, PersistentDescriptorSetSampler, - StdDescriptorPool, StdDescriptorPoolAlloc, UnsafeDescriptorPool, - UnsafeDescriptorPoolAllocIter, UnsafeDescriptorSet, UnsafeDescriptorSetLayout, - }, - descriptor::pipeline_layout::{ - EmptyPipelineDesc, PipelineLayout, PipelineLayoutCreationError, PipelineLayoutDesc, - PipelineLayoutDescPcRange, PipelineLayoutDescUnion, PipelineLayoutLimitsError, - PipelineLayoutNotSupersetError, PipelineLayoutPushConstantsCompatible, - PipelineLayoutSetsCompatible, PipelineLayoutSuperset, PipelineLayoutSys, - RuntimePipelineDesc, RuntimePipelineDescError, - }, - descriptor::{DescriptorSet, PipelineLayoutAbstract}, - device::{ - Device, DeviceCreationError, DeviceExtensions, DeviceOwned, Queue, QueuesIter, - RawDeviceExtensions, - }, - format::{ - AcceptsPixels, ClearValue, ClearValuesTuple, Format, FormatDesc, FormatTy, - PossibleCompressedFormatDesc, PossibleDepthFormatDesc, PossibleDepthStencilFormatDesc, - PossibleFloatFormatDesc, PossibleFloatOrCompressedFormatDesc, PossibleSintFormatDesc, - PossibleStencilFormatDesc, PossibleUintFormatDesc, StrongStorage, - }, - framebuffer::{ - AttachmentDescription, AttachmentsList, Framebuffer, FramebufferAbstract, - FramebufferBuilder, FramebufferCreationError, FramebufferSys, - IncompatibleRenderPassAttachmentError, LoadOp, PassDependencyDescription, PassDescription, - RenderPass, RenderPassAbstract, RenderPassCompatible, RenderPassCreationError, - RenderPassDesc, RenderPassDescAttachments, RenderPassDescClearValues, - RenderPassDescDependencies, RenderPassDescSubpasses, RenderPassSubpassInterface, - RenderPassSys, StoreOp, Subpass, SubpassContents, - }, - image::immutable::ImmutableImageInitialization, - image::traits::{ - AttachmentImageView, ImageAccessFromUndefinedLayout, ImageClearValue, ImageContent, - }, - image::{ - AttachmentImage, ImageAccess, ImageCreationError, ImageDimensions, ImageInner, ImageLayout, - ImageUsage, ImageViewAccess, ImmutableImage, MipmapsCount, StorageImage, SwapchainImage, - }, - instance::{ - ApplicationInfo, Instance, InstanceCreationError, InstanceExtensions, Limits, - PhysicalDevice, PhysicalDeviceType, PhysicalDevicesIter, QueueFamiliesIter, QueueFamily, - RawInstanceExtensions, Version, - }, - pipeline::blend::{AttachmentBlend, AttachmentsBlend, Blend, BlendFactor, BlendOp, LogicOp}, - pipeline::depth_stencil::{DepthBounds, DepthStencil, Stencil, StencilOp}, - pipeline::vertex::{ - AttributeInfo, BufferlessDefinition, BufferlessVertices, IncompatibleVertexDefinitionError, - OneVertexOneInstanceDefinition, SingleBufferDefinition, SingleInstanceBufferDefinition, - TwoBuffersDefinition, Vertex, VertexDefinition, VertexMember, VertexMemberInfo, - VertexMemberTy, VertexSource, - }, - pipeline::viewport::{Scissor, Viewport, ViewportsState}, - pipeline::{ - ComputePipeline, ComputePipelineAbstract, ComputePipelineCreationError, ComputePipelineSys, - GraphicsPipeline, GraphicsPipelineAbstract, GraphicsPipelineBuilder, - GraphicsPipelineCreationError, GraphicsPipelineSys, - }, - query::{ - OcclusionQueriesPool, QueryPipelineStatisticFlags, QueryPoolCreationError, QueryType, - UnsafeQueriesRange, UnsafeQuery, UnsafeQueryPool, - }, - sampler::{ - Compare as DepthStencilCompare, Sampler, SamplerAddressMode, SamplerCreationError, - UnnormalizedSamplerAddressMode, - }, - swapchain::{Surface, Swapchain, SwapchainAcquireFuture, SwapchainCreationError}, - sync::{ - Fence, FenceSignalFuture, GpuFuture, JoinFuture, NowFuture, Semaphore, - SemaphoreSignalFuture, - }, -}; -pub use vulkano_shaders as shaders; -pub use vulkano_win as win; - -use crate::vk; -use crate::vk::instance::debug::{ - DebugCallback, DebugCallbackCreationError, Message, MessageSeverity, MessageType, -}; -use crate::vk::instance::loader::{FunctionPointers, Loader}; -use std::borrow::Cow; -use std::ops::{self, Range}; -use std::panic::RefUnwindSafe; -use std::sync::Arc; - -#[cfg(all(target_os = "macos", not(test)))] -use moltenvk_deps; -#[cfg(all(target_os = "macos", not(test)))] -use vulkano::instance::loader::DynamicLibraryLoader; - -/// The default application name used with the default `ApplicationInfo`. -pub const DEFAULT_APPLICATION_NAME: &'static str = "nannou-app"; - -/// The default application info -pub const DEFAULT_APPLICATION_INFO: ApplicationInfo<'static> = ApplicationInfo { - application_name: Some(Cow::Borrowed(DEFAULT_APPLICATION_NAME)), - application_version: None, - engine_name: None, - engine_version: None, -}; - -/// The **FramebufferObject** or **Fbo** type for easy management of a framebuffer. -/// -/// Creating and maintaining a framebuffer and ensuring it is up to date with the given renderpass -/// and images can be a tedious task that requires a lot of boilerplate code. This type simplifies -/// the process with a single `update` method that creates or recreates the framebuffer if any of -/// the following conditions are met: -/// - The `update` method is called for the first time. -/// - The given render pass is different to that which was used to create the existing framebuffer. -/// - The dimensions of the framebuffer don't match the dimensions of the images. -#[derive(Default)] -pub struct FramebufferObject { - framebuffer: Option>, -} - -/// Shorthand for the **FramebufferObject** type. -pub type Fbo = FramebufferObject; - -/// Shorthand for the builder result type expected by the function given to `Fbo::update`. -pub type FramebufferBuilderResult = - Result, FramebufferCreationError>; - -/// A builder struct that makes the process of building an instance more modular. -#[derive(Default)] -pub struct InstanceBuilder { - pub app_info: Option>, - pub extensions: Option, - pub layers: Vec, - pub loader: Option>>, -} - -/// A builder struct that makes the process of building a debug callback more modular. -#[derive(Default)] -pub struct DebugCallbackBuilder { - pub message_type: Option, - pub message_severity: Option, - pub user_callback: Option, -} - -/// A builder struct that makes the process of building a **Sampler** more modular. -#[derive(Clone, Debug, Default, PartialEq)] -pub struct SamplerBuilder { - pub mag_filter: Option, - pub min_filter: Option, - pub mipmap_mode: Option, - pub address_u: Option, - pub address_v: Option, - pub address_w: Option, - pub mip_lod_bias: Option, - pub max_anisotropy: Option, - pub min_lod: Option, - pub max_lod: Option, -} - -/// A builder struct that makes the process of building a **Viewport** more modular. -#[derive(Clone, Debug, Default, PartialEq)] -pub struct ViewportBuilder { - pub origin: Option<[f32; 2]>, - pub depth_range: Option>, -} - -/// A simple trait that extends the `DynamicState` type with builder methods. -/// -/// This fills a similar role to the `SamplerBuilder` and `ViewportBuilder` structs, but seeing as -/// `DynamicState` already uses `Option`s for all its fields, a trait allows for a simpler -/// approach. -/// -/// This trait is included within the `nannou::prelude` for ease of access. -pub trait DynamicStateBuilder { - fn line_width(self, line_width: f32) -> Self; - fn viewports(self, viewports: Vec) -> Self; - fn scissors(self, scissors: Vec) -> Self; -} - -impl DynamicStateBuilder for DynamicState { - fn line_width(mut self, line_width: f32) -> Self { - self.line_width = Some(line_width); - self - } - fn viewports(mut self, viewports: Vec) -> Self { - self.viewports = Some(viewports); - self - } - fn scissors(mut self, scissors: Vec) -> Self { - self.scissors = Some(scissors); - self - } -} - -// The user vulkan debug callback allocated on the heap to avoid complicated type params. -type BoxedUserCallback = Box; - -impl FramebufferObject { - /// Access the inner framebuffer trait object. - pub fn inner(&self) -> &Option> { - &self.framebuffer - } - - /// Ensure the framebuffer is up to date with the given dimensions and render pass. - pub fn update( - &mut self, - render_pass: R, - dimensions: [u32; 3], - builder: F, - ) -> Result<(), FramebufferCreationError> - where - R: 'static + vk::framebuffer::RenderPassAbstract + Send + Sync, - F: FnOnce(FramebufferBuilder) -> FramebufferBuilderResult, - A: 'static + vk::framebuffer::AttachmentsList + Send + Sync, - { - let needs_creation = self.framebuffer.is_none() - || !self.dimensions_match(dimensions) - || !self.render_passes_match(&render_pass); - if needs_creation { - let builder = builder(Framebuffer::start(render_pass))?; - let fb = builder.build()?; - self.framebuffer = Some(Arc::new(fb)); - } - Ok(()) - } - - /// Expects that there is a inner framebuffer object instantiated and returns it. - /// - /// **panic!**s if the `update` method has not yet been called. - /// - /// This method is shorthand for `fbo.as_ref().expect("inner framebuffer was None").clone()`. - pub fn expect_inner(&self) -> Arc { - self.framebuffer - .as_ref() - .expect("inner framebuffer was `None` - you must call the `update` method first") - .clone() - } - - /// Whether or not the given renderpass matches the framebuffer's render pass. - pub fn render_passes_match(&self, render_pass: R) -> bool - where - R: vk::framebuffer::RenderPassAbstract, - { - self.framebuffer - .as_ref() - .map(|fb| vk::framebuffer::RenderPassAbstract::inner(fb).internal_object()) - .map(|obj| obj == render_pass.inner().internal_object()) - .unwrap_or(false) - } - - /// Whether or not the given dimensions match the current dimensions. - pub fn dimensions_match(&self, dimensions: [u32; 3]) -> bool { - self.framebuffer - .as_ref() - .map(|fb| fb.dimensions() == dimensions) - .unwrap_or(false) - } -} - -impl InstanceBuilder { - /// Begin building a vulkano instance. - pub fn new() -> Self { - Default::default() - } - - /// Specify the application info with which the instance should be created. - pub fn app_info(mut self, app_info: ApplicationInfo<'static>) -> Self { - self.app_info = Some(app_info); - self - } - - /// Specify the exact extensions to enable for the instance. - pub fn extensions(mut self, extensions: InstanceExtensions) -> Self { - self.extensions = Some(extensions); - self - } - - /// Add the given extensions to the set of existing extensions within the builder. - /// - /// Unlike the `extensions` method, this does not disable pre-existing extensions. - pub fn add_extensions(mut self, ext: InstanceExtensions) -> Self { - self.extensions = self - .extensions - .take() - .map(|mut e| { - // TODO: Remove this when `InstanceExtensions::union` gets merged. - e.khr_surface |= ext.khr_surface; - e.khr_display |= ext.khr_display; - e.khr_xlib_surface |= ext.khr_xlib_surface; - e.khr_xcb_surface |= ext.khr_xcb_surface; - e.khr_wayland_surface |= ext.khr_wayland_surface; - e.khr_android_surface |= ext.khr_android_surface; - e.khr_win32_surface |= ext.khr_win32_surface; - e.ext_debug_utils |= ext.ext_debug_utils; - e.mvk_ios_surface |= ext.mvk_ios_surface; - e.mvk_macos_surface |= ext.mvk_macos_surface; - e.mvk_moltenvk |= ext.mvk_moltenvk; - e.nn_vi_surface |= ext.nn_vi_surface; - e.ext_swapchain_colorspace |= ext.ext_swapchain_colorspace; - e.khr_get_physical_device_properties2 |= ext.khr_get_physical_device_properties2; - e - }) - .or(Some(ext)); - self - } - - /// Specify the exact layers to enable for the instance. - pub fn layers(mut self, layers: L) -> Self - where - L: IntoIterator, - L::Item: Into, - { - self.layers = layers.into_iter().map(Into::into).collect(); - self - } - - /// Extend the existing list of layers with the given layers. - pub fn add_layers(mut self, layers: L) -> Self - where - L: IntoIterator, - L::Item: Into, - { - self.layers.extend(layers.into_iter().map(Into::into)); - self - } - - /// Add custom vulkan loader - pub fn add_loader(mut self, loader: FunctionPointers>) -> Self { - self.loader = Some(loader); - self - } - - /// Build the vulkan instance with the existing parameters. - pub fn build(self) -> Result, InstanceCreationError> { - let InstanceBuilder { - app_info, - extensions, - layers, - loader, - } = self; - - let app_info = app_info.unwrap_or(DEFAULT_APPLICATION_INFO); - let extensions = extensions.unwrap_or_else(required_windowing_extensions); - let layers = layers.iter().map(|s| &s[..]); - match loader { - None => Instance::new(Some(&app_info), &extensions, layers), - Some(loader) => Instance::with_loader(loader, Some(&app_info), &extensions, layers), - } - } -} - -impl DebugCallbackBuilder { - /// Begin building a vulkan debug callback. - pub fn new() -> Self { - Default::default() - } - - /// The severity of messages emitted via the debug callback. - /// - /// If unspecified, nannou will use `MessageType::errors_and_warnings`. - pub fn message_severity(mut self, msg_sev: MessageSeverity) -> Self { - self.message_severity = Some(msg_sev); - self - } - - /// The message types to be emitted to the debug callback. - /// - /// If unspecified, nannou will use `MessageType::errors_and_warnings`. - pub fn message_type(mut self, msg_ty: MessageType) -> Self { - self.message_type = Some(msg_ty); - self - } - - /// The function that will be called for handling messages. - /// - /// If unspecified, nannou will use a function that prints to `stdout` and `stderr`. - pub fn user_callback(mut self, cb: F) -> Self - where - F: Fn(&Message) + 'static + Send + RefUnwindSafe, - { - self.user_callback = Some(Box::new(cb) as Box<_>); - self - } - - /// Build the debug callback builder for the given vulkan instance. - pub fn build( - self, - instance: &Arc, - ) -> Result { - let DebugCallbackBuilder { - message_severity, - message_type, - user_callback, - } = self; - let message_severity = - message_severity.unwrap_or_else(MessageSeverity::errors_and_warnings); - let message_type = message_type.unwrap_or_else(MessageType::all); - let user_callback = move |msg: &Message| { - match user_callback { - Some(ref cb) => (**cb)(msg), - None => { - let sev = if msg.severity.error { - "error" - } else if msg.severity.warning { - "warning" - } else if msg.severity.information { - "information" - } else if msg.severity.verbose { - "verbose" - } else { - "" - }; - - let ty = if msg.ty.general { - "general" - } else if msg.ty.validation { - "validation" - } else if msg.ty.performance { - "performance" - } else { - "unknown type" - }; - - println!( - "[vulkan] {} {} {}: {}", - msg.layer_prefix, ty, sev, msg.description - ); - } - }; - }; - DebugCallback::new(instance, message_severity, message_type, user_callback) - } -} - -impl SamplerBuilder { - pub const DEFAULT_MAG_FILTER: vk::sampler::Filter = vk::sampler::Filter::Linear; - pub const DEFAULT_MIN_FILTER: vk::sampler::Filter = vk::sampler::Filter::Linear; - pub const DEFAULT_MIPMAP_MODE: vk::sampler::MipmapMode = vk::sampler::MipmapMode::Nearest; - pub const DEFAULT_ADDRESS_U: SamplerAddressMode = SamplerAddressMode::ClampToEdge; - pub const DEFAULT_ADDRESS_V: SamplerAddressMode = SamplerAddressMode::ClampToEdge; - pub const DEFAULT_ADDRESS_W: SamplerAddressMode = SamplerAddressMode::ClampToEdge; - pub const DEFAULT_MIP_LOD_BIAS: f32 = 0.0; - pub const DEFAULT_MAX_ANISOTROPY: f32 = 1.0; - pub const DEFAULT_MIN_LOD: f32 = 0.0; - pub const DEFAULT_MAX_LOD: f32 = 1.0; - - /// Begin building a new vulkan **Sampler**. - pub fn new() -> Self { - Self::default() - } - - /// How the implementation should sample from the image when it is respectively larger than the - /// original. - pub fn mag_filter(mut self, filter: vk::sampler::Filter) -> Self { - self.mag_filter = Some(filter); - self - } - - /// How the implementation should sample from the image when it is respectively smaller than - /// the original. - pub fn min_filter(mut self, filter: vk::sampler::Filter) -> Self { - self.min_filter = Some(filter); - self - } - - /// How the implementation should choose which mipmap to use. - pub fn mipmap_mode(mut self, mode: vk::sampler::MipmapMode) -> Self { - self.mipmap_mode = Some(mode); - self - } - - /// How the implementation should behave when sampling outside of the texture coordinates range - /// [0.0, 1.0]. - pub fn address_u(mut self, mode: SamplerAddressMode) -> Self { - self.address_u = Some(mode); - self - } - - /// How the implementation should behave when sampling outside of the texture coordinates range - /// [0.0, 1.0]. - pub fn address_v(mut self, mode: SamplerAddressMode) -> Self { - self.address_v = Some(mode); - self - } - - /// How the implementation should behave when sampling outside of the texture coordinates range - /// [0.0, 1.0]. - pub fn address_w(mut self, mode: SamplerAddressMode) -> Self { - self.address_w = Some(mode); - self - } - - /// Level of detail bias. - pub fn mip_lod_bias(mut self, bias: f32) -> Self { - self.mip_lod_bias = Some(bias); - self - } - - /// Must be greater than oro equal to 1.0. - /// - /// If greater than 1.0, the implementation will use anisotropic filtering. Using a value - /// greater than 1.0 requires the sampler_anisotropy feature to be enabled when creating the - /// device. - pub fn max_anisotropy(mut self, max: f32) -> Self { - self.max_anisotropy = Some(max); - self - } - - /// The minimum mipmap level to use. - pub fn min_lod(mut self, lod: f32) -> Self { - self.min_lod = Some(lod); - self - } - - /// The maximum mipmap level to use. - pub fn max_lod(mut self, lod: f32) -> Self { - self.max_lod = Some(lod); - self - } - - /// Build the sampler with the givenn behaviour. - pub fn build(self, device: Arc) -> Result, SamplerCreationError> { - let SamplerBuilder { - mag_filter, - min_filter, - mipmap_mode, - address_u, - address_v, - address_w, - mip_lod_bias, - max_anisotropy, - min_lod, - max_lod, - } = self; - Sampler::new( - device, - mag_filter.unwrap_or(Self::DEFAULT_MAG_FILTER), - min_filter.unwrap_or(Self::DEFAULT_MIN_FILTER), - mipmap_mode.unwrap_or(Self::DEFAULT_MIPMAP_MODE), - address_u.unwrap_or(Self::DEFAULT_ADDRESS_U), - address_v.unwrap_or(Self::DEFAULT_ADDRESS_V), - address_w.unwrap_or(Self::DEFAULT_ADDRESS_W), - mip_lod_bias.unwrap_or(Self::DEFAULT_MIP_LOD_BIAS), - max_anisotropy.unwrap_or(Self::DEFAULT_MAX_ANISOTROPY), - min_lod.unwrap_or(Self::DEFAULT_MIN_LOD), - max_lod.unwrap_or(Self::DEFAULT_MAX_LOD), - ) - } -} - -impl ViewportBuilder { - pub const DEFAULT_ORIGIN: [f32; 2] = [0.0; 2]; - pub const DEFAULT_DEPTH_RANGE: Range = 0.0..1.0; - - /// Begin building a new **Viewport**. - pub fn new() -> Self { - Self::default() - } - - /// Coordinates in pixels of the top-left hand corner of the viewport. - /// - /// By default this is `ViewportDefault::DEFAULT_ORIGIN`. - pub fn origin(mut self, origin: [f32; 2]) -> Self { - self.origin = Some(origin); - self - } - - /// Minimum and maximum values of the depth. - /// - /// The values `0.0` to `1.0` of each vertex's Z coordinate will be mapped to this - /// `depth_range` before being compared to the existing depth value. - /// - /// This is equivalents to `glDepthRange` in OpenGL, except that OpenGL uses the Z coordinate - /// range from `-1.0` to `1.0` instead. - /// - /// By default this is `ViewportDefault::DEFAULT_DEPTH_RANGE`. - pub fn depth_range(mut self, range: Range) -> Self { - self.depth_range = Some(range); - self - } - - /// Construct the viewport with its dimensions in pixels. - pub fn build(self, dimensions: [f32; 2]) -> Viewport { - let ViewportBuilder { - origin, - depth_range, - } = self; - Viewport { - origin: origin.unwrap_or(Self::DEFAULT_ORIGIN), - depth_range: depth_range.unwrap_or(Self::DEFAULT_DEPTH_RANGE), - dimensions, - } - } -} - -impl ops::Deref for FramebufferObject { - type Target = Option>; - fn deref(&self) -> &Self::Target { - &self.framebuffer - } -} - -/// The default set of required extensions used by Nannou. -/// -/// This is the same as calling `vk::win::required_extensions()`. -pub fn required_windowing_extensions() -> InstanceExtensions { - vulkano_win::required_extensions() -} - -/// Whether or not the format is sRGB. -pub fn format_is_srgb(format: Format) -> bool { - use crate::vk::format::Format::*; - match format { - R8Srgb - | R8G8Srgb - | R8G8B8Srgb - | B8G8R8Srgb - | R8G8B8A8Srgb - | B8G8R8A8Srgb - | A8B8G8R8SrgbPack32 - | BC1_RGBSrgbBlock - | BC1_RGBASrgbBlock - | BC2SrgbBlock - | BC3SrgbBlock - | BC7SrgbBlock - | ETC2_R8G8B8SrgbBlock - | ETC2_R8G8B8A1SrgbBlock - | ETC2_R8G8B8A8SrgbBlock - | ASTC_4x4SrgbBlock - | ASTC_5x4SrgbBlock - | ASTC_5x5SrgbBlock - | ASTC_6x5SrgbBlock - | ASTC_6x6SrgbBlock - | ASTC_8x5SrgbBlock - | ASTC_8x6SrgbBlock - | ASTC_8x8SrgbBlock - | ASTC_10x5SrgbBlock - | ASTC_10x6SrgbBlock - | ASTC_10x8SrgbBlock - | ASTC_10x10SrgbBlock - | ASTC_12x10SrgbBlock - | ASTC_12x12SrgbBlock => true, - _ => false, - } -} - -/// Given a slice of depth format candidates in order of preference, return the first that is -/// supported by the given device. -pub fn find_supported_depth_image_format( - device: Arc, - candidates: &[Format], -) -> Option { - // NOTE: This is a hack because it's not possible to just query supported formats with vulkano - // right now. - for &format in candidates { - match format.ty() { - vk::FormatTy::Depth | vk::FormatTy::DepthStencil => { - let dims = [1; 2]; - if vk::AttachmentImage::new(device.clone(), dims, format).is_ok() { - return Some(format); - } - } - _ => (), - } - } - None -} - -/// Given some target MSAA samples, limit it by the capabilities of the given `physical_device`. -/// -/// This is useful for attempting a specific multisampling sample count but falling back to a -/// supported count in the case that the desired count is unsupported. -/// -/// Specifically, this function limits the given `target_msaa_samples` to the minimum of the color -/// and depth sample count limits. -pub fn msaa_samples_limited(physical_device: &PhysicalDevice, target_msaa_samples: u32) -> u32 { - let color_limit = physical_device.limits().framebuffer_color_sample_counts(); - let depth_limit = physical_device.limits().framebuffer_depth_sample_counts(); - let msaa_limit = std::cmp::min(color_limit, depth_limit); - std::cmp::min(msaa_limit, target_msaa_samples) -} - -#[cfg(all(target_os = "macos", not(test)))] -pub fn check_moltenvk( - vulkan_builder: InstanceBuilder, - settings: Option, -) -> InstanceBuilder { - let settings = match settings { - Some(s) => s, - None => Default::default(), - }; - let path = match moltenvk_deps::check_or_install(settings) { - Err(moltenvk_deps::Error::ResetEnvVars(p)) => Some(p), - Err(moltenvk_deps::Error::NonDefaultDir) => None, - Err(moltenvk_deps::Error::ChoseNotToInstall) => { - panic!("Moltenvk is required for Nannou on MacOS") - } - Err(e) => panic!("Moltenvk installation failed {:?}", e), - Ok(p) => Some(p), - }; - let loader = path.map(|p| unsafe { DynamicLibraryLoader::new(p) }); - match loader { - Some(Ok(l)) => { - let loader: FunctionPointers> = - FunctionPointers::new(Box::new(l)); - let required_extensions = required_extensions_with_loader(&loader); - vulkan_builder - .extensions(required_extensions) - .add_loader(loader) - } - _ => vulkan_builder.extensions(required_windowing_extensions()), - } -} - -pub fn required_extensions_with_loader(ptrs: &FunctionPointers) -> InstanceExtensions -where - L: Loader, -{ - let ideal = InstanceExtensions { - khr_surface: true, - khr_xlib_surface: true, - khr_xcb_surface: true, - khr_wayland_surface: true, - khr_android_surface: true, - khr_win32_surface: true, - mvk_ios_surface: true, - mvk_macos_surface: true, - ..InstanceExtensions::none() - }; - - match InstanceExtensions::supported_by_core_with_loader(ptrs) { - Ok(supported) => supported.intersection(&ideal), - Err(_) => InstanceExtensions::none(), - } -} diff --git a/src/wgpu/mod.rs b/src/wgpu/mod.rs new file mode 100644 index 000000000..c68c18552 --- /dev/null +++ b/src/wgpu/mod.rs @@ -0,0 +1,80 @@ +//! Items related to WGPU and the Rust API used by nannou to access the GPU. +//! +//! This module re-exports the entire `wgpu` crate along with all of its documentation while also +//! adding some additional helper items. + +mod texture_format_converter; + +// Re-export all of `wgpu` along with its documentation. +#[doc(inline)] +pub use wgpu::*; + +pub use self::texture_format_converter::TextureFormatConverter; + +/// The default set of options used to request a `wgpu::Adapter` when creating windows. +pub const DEFAULT_ADAPTER_REQUEST_OPTIONS: RequestAdapterOptions = RequestAdapterOptions { + power_preference: PowerPreference::Default, + backends: BackendBit::PRIMARY, +}; + +/// The default set of `Extensions` used within the `default_device_descriptor()` function. +pub const DEFAULT_EXTENSIONS: Extensions = Extensions { + anisotropic_filtering: true, +}; + +/// The default device descriptor used to instantiate a logical device when creating windows. +pub fn default_device_descriptor() -> DeviceDescriptor { + let extensions = DEFAULT_EXTENSIONS; + let limits = Limits::default(); + DeviceDescriptor { extensions, limits } +} + +/// Adds a simple render pass command to the given encoder that resolves the given multisampled +/// `src_texture` to the given non-multisampled `dst_texture`. +/// +/// Both the `src_texture` and `dst_texture` must have: +/// +/// - `TextureUsage::OUTPUT_ATTACHMENT` enabled. +/// - The same dimensions. +/// - The same `TextureFormat`. +pub fn resolve_texture( + src_texture: &wgpu::TextureView, + dst_texture: &wgpu::TextureView, + encoder: &mut wgpu::CommandEncoder, +) { + let color_attachment = wgpu::RenderPassColorAttachmentDescriptor { + attachment: src_texture, + resolve_target: Some(dst_texture), + load_op: wgpu::LoadOp::Load, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color::TRANSPARENT, + }; + let render_pass_desc = wgpu::RenderPassDescriptor { + color_attachments: &[color_attachment], + depth_stencil_attachment: None, + }; + let _render_pass = encoder.begin_render_pass(&render_pass_desc); +} + +/// Adds a simple render pass command to the given encoder that simply clears the given texture +/// with the given colour. +/// +/// The given `texture` must have `TextureUsage::OUTPUT_ATTACHMENT` enabled. +pub fn clear_texture( + texture: &wgpu::TextureView, + clear_color: wgpu::Color, + encoder: &mut wgpu::CommandEncoder, +) { + let color_attachment = wgpu::RenderPassColorAttachmentDescriptor { + attachment: texture, + resolve_target: None, + load_op: wgpu::LoadOp::Clear, + store_op: wgpu::StoreOp::Store, + clear_color, + }; + let render_pass_desc = wgpu::RenderPassDescriptor { + color_attachments: &[color_attachment], + depth_stencil_attachment: None, + }; + let _render_pass = encoder.begin_render_pass(&render_pass_desc); +} diff --git a/src/wgpu/texture_format_converter/mod.rs b/src/wgpu/texture_format_converter/mod.rs new file mode 100644 index 000000000..4b3ca1b30 --- /dev/null +++ b/src/wgpu/texture_format_converter/mod.rs @@ -0,0 +1,235 @@ +/// Writes a texture to another texture of the same dimensions but with a different format. +/// +/// The `src_texture` must have the `TextureUsage::SAMPLED` enabled. +/// +/// The `dst_texture` must have the `TextureUsage::OUTPUT_ATTACHMENT` enabled. +/// +/// Both `src_texture` and `dst_texture` must have the same dimensions. +/// +/// Both textures should **not** be multisampled. *Note: Please open an issue if you would like +/// support for multisampled source textures as it should be quite trivial to add.* +#[derive(Debug)] +pub struct TextureFormatConverter { + _vs_mod: wgpu::ShaderModule, + _fs_mod: wgpu::ShaderModule, + bind_group_layout: wgpu::BindGroupLayout, + bind_group: wgpu::BindGroup, + render_pipeline: wgpu::RenderPipeline, + sampler: wgpu::Sampler, + vertex_buffer: wgpu::Buffer, +} + +impl TextureFormatConverter { + /// Construct a new `TextureFormatConverter`. + pub fn new( + device: &wgpu::Device, + src_texture: &wgpu::TextureView, + dst_format: wgpu::TextureFormat, + ) -> Self { + // Load shader modules. + let vs = include_bytes!("shaders/vert.spv"); + let vs_spirv = wgpu::read_spirv(std::io::Cursor::new(&vs[..])) + .expect("failed to read hard-coded SPIRV"); + let vs_mod = device.create_shader_module(&vs_spirv); + let fs = include_bytes!("shaders/frag.spv"); + let fs_spirv = wgpu::read_spirv(std::io::Cursor::new(&fs[..])) + .expect("failed to read hard-coded SPIRV"); + let fs_mod = device.create_shader_module(&fs_spirv); + + // Create the sampler for sampling from the source texture. + let sampler_desc = sampler_desc(); + let sampler = device.create_sampler(&sampler_desc); + + // Create the render pipeline. + let bind_group_layout = bind_group_layout(device); + let pipeline_layout = pipeline_layout(device, &bind_group_layout); + let render_pipeline = + render_pipeline(device, &pipeline_layout, &vs_mod, &fs_mod, dst_format); + + // Create the bind group. + let bind_group = bind_group(device, &bind_group_layout, src_texture, &sampler); + + // Create the vertex buffer. + let vertex_buffer = device + .create_buffer_mapped(VERTICES.len(), wgpu::BufferUsage::VERTEX) + .fill_from_slice(&VERTICES[..]); + + TextureFormatConverter { + _vs_mod: vs_mod, + _fs_mod: fs_mod, + bind_group_layout, + bind_group, + render_pipeline, + sampler, + vertex_buffer, + } + } + + /// Given an encoder, submits a render pass command for writing the source texture to the + /// destination texture. + pub fn encode_render_pass( + &self, + dst_texture: &wgpu::TextureView, + encoder: &mut wgpu::CommandEncoder, + ) { + let vertex_range = 0..VERTICES.len() as u32; + let instance_range = 0..1; + let render_pass_desc = wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: dst_texture, + resolve_target: None, + load_op: wgpu::LoadOp::Clear, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color::TRANSPARENT, + }], + depth_stencil_attachment: None, + }; + let mut render_pass = encoder.begin_render_pass(&render_pass_desc); + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_vertex_buffers(0, &[(&self.vertex_buffer, 0)]); + render_pass.set_bind_group(0, &self.bind_group, &[]); + render_pass.draw(vertex_range, instance_range); + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] +struct Vertex { + pub position: [f32; 2], +} + +const VERTICES: [Vertex; 4] = [ + Vertex { + position: [-1.0, -1.0], + }, + Vertex { + position: [-1.0, 1.0], + }, + Vertex { + position: [1.0, -1.0], + }, + Vertex { + position: [1.0, 1.0], + }, +]; + +fn vertex_attrs() -> [wgpu::VertexAttributeDescriptor; 1] { + [wgpu::VertexAttributeDescriptor { + format: wgpu::VertexFormat::Float2, + offset: 0, + shader_location: 0, + }] +} + +fn sampler_desc() -> wgpu::SamplerDescriptor { + wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + lod_min_clamp: -100.0, + lod_max_clamp: 100.0, + compare_function: wgpu::CompareFunction::Always, + } +} + +fn bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { + let texture_binding = wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::SampledTexture { + multisampled: false, + dimension: wgpu::TextureViewDimension::D2, + }, + }; + let sampler_binding = wgpu::BindGroupLayoutBinding { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler, + }; + let bindings = &[texture_binding, sampler_binding]; + let desc = wgpu::BindGroupLayoutDescriptor { bindings }; + device.create_bind_group_layout(&desc) +} + +fn bind_group( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + texture: &wgpu::TextureView, + sampler: &wgpu::Sampler, +) -> wgpu::BindGroup { + let texture_binding = wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture), + }; + let sampler_binding = wgpu::Binding { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }; + let bindings = &[texture_binding, sampler_binding]; + let desc = wgpu::BindGroupDescriptor { layout, bindings }; + device.create_bind_group(&desc) +} + +fn pipeline_layout( + device: &wgpu::Device, + bind_group_layout: &wgpu::BindGroupLayout, +) -> wgpu::PipelineLayout { + let desc = wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&bind_group_layout], + }; + device.create_pipeline_layout(&desc) +} + +fn render_pipeline( + device: &wgpu::Device, + layout: &wgpu::PipelineLayout, + vs_mod: &wgpu::ShaderModule, + fs_mod: &wgpu::ShaderModule, + dst_format: wgpu::TextureFormat, +) -> wgpu::RenderPipeline { + let vs_desc = wgpu::ProgrammableStageDescriptor { + module: &vs_mod, + entry_point: "main", + }; + let fs_desc = wgpu::ProgrammableStageDescriptor { + module: &fs_mod, + entry_point: "main", + }; + let raster_desc = 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, + }; + let color_state_desc = wgpu::ColorStateDescriptor { + format: dst_format, + color_blend: wgpu::BlendDescriptor::REPLACE, + alpha_blend: wgpu::BlendDescriptor::REPLACE, + write_mask: wgpu::ColorWrite::ALL, + }; + let vertex_attrs = vertex_attrs(); + let vertex_buffer_desc = wgpu::VertexBufferDescriptor { + stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &vertex_attrs[..], + }; + let desc = wgpu::RenderPipelineDescriptor { + layout, + vertex_stage: vs_desc, + fragment_stage: Some(fs_desc), + rasterization_state: Some(raster_desc), + primitive_topology: wgpu::PrimitiveTopology::TriangleStrip, + color_states: &[color_state_desc], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[vertex_buffer_desc], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }; + device.create_render_pipeline(&desc) +} diff --git a/src/wgpu/texture_format_converter/shaders/frag.spv b/src/wgpu/texture_format_converter/shaders/frag.spv new file mode 100644 index 000000000..242ab8df2 Binary files /dev/null and b/src/wgpu/texture_format_converter/shaders/frag.spv differ diff --git a/src/wgpu/texture_format_converter/shaders/shader.frag b/src/wgpu/texture_format_converter/shaders/shader.frag new file mode 100644 index 000000000..a4eabcfed --- /dev/null +++ b/src/wgpu/texture_format_converter/shaders/shader.frag @@ -0,0 +1,17 @@ +// NOTE: This shader requires being manually compiled to SPIR-V in order to +// avoid having downstream users require building shaderc and compiling the +// shader themselves. If you update this shader, be sure to also re-compile it +// and update `frag.spv`. You can do so using `glslangValidator` with the +// following command: `glslangValidator -V -o frag.spv shader.frag` + +#version 450 + +layout(location = 0) in vec2 tex_coords; +layout(location = 0) out vec4 f_color; + +layout(set = 0, binding = 0) uniform texture2D tex; +layout(set = 0, binding = 1) uniform sampler tex_sampler; + +void main() { + f_color = texture(sampler2D(tex, tex_sampler), tex_coords); +} diff --git a/src/wgpu/texture_format_converter/shaders/shader.vert b/src/wgpu/texture_format_converter/shaders/shader.vert new file mode 100644 index 000000000..de241b46a --- /dev/null +++ b/src/wgpu/texture_format_converter/shaders/shader.vert @@ -0,0 +1,15 @@ +// NOTE: This shader requires being manually compiled to SPIR-V in order to +// avoid having downstream users require building shaderc and compiling the +// shader themselves. If you update this shader, be sure to also re-compile it +// and update `vert.spv`. You can do so using `glslangValidator` with the +// following command: `glslangValidator -V -o vert.spv shader.vert` + +#version 450 + +layout(location = 0) in vec2 position; +layout(location = 0) out vec2 tex_coords; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); + tex_coords = position + vec2(0.5); +} diff --git a/src/wgpu/texture_format_converter/shaders/vert.spv b/src/wgpu/texture_format_converter/shaders/vert.spv new file mode 100644 index 000000000..8a5466678 Binary files /dev/null and b/src/wgpu/texture_format_converter/shaders/vert.spv differ diff --git a/src/window.rs b/src/window.rs index 91cfa166c..3dfb83f5b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -8,21 +8,19 @@ use crate::event::{ use crate::frame::{self, Frame, RawFrame}; use crate::geom; use crate::geom::{Point2, Vector2}; -use crate::vk::{self, win::VkSurfaceBuild, VulkanObject}; +use crate::wgpu; use crate::App; use std::any::Any; -use std::error::Error as StdError; use std::path::PathBuf; use std::sync::atomic::AtomicBool; -use std::sync::{Arc, Mutex}; -use std::{cmp, env, fmt, ops}; +use std::sync::Arc; +use std::{env, fmt}; use winit::dpi::LogicalSize; -use winit::{self, MonitorId, MouseCursor}; -pub use winit::WindowId as Id; +pub use winit::window::WindowId as Id; /// The default dimensions used for a window in the case that none are specified. -pub const DEFAULT_DIMENSIONS: LogicalSize = LogicalSize { +pub const DEFAULT_DIMENSIONS: LogicalSize = LogicalSize { width: 1024.0, height: 768.0, }; @@ -30,12 +28,11 @@ pub const DEFAULT_DIMENSIONS: LogicalSize = LogicalSize { /// A context for building a window. pub struct Builder<'app> { app: &'app App, - vk_physical_device: Option>, - vk_device_extensions: Option, - vk_device_queue: Option>, - window: winit::WindowBuilder, + window: winit::window::WindowBuilder, title_was_set: bool, - swapchain_builder: SwapchainBuilder, + swap_chain_builder: SwapChainBuilder, + request_adapter_opts: Option, + device_desc: Option, user_functions: UserFunctions, msaa_samples: Option, } @@ -71,7 +68,7 @@ pub type ViewFn = fn(&App, &Model, &Frame); /// The user function type for drawing their model to the surface of a single window. /// -/// Unlike the `ViewFn`, the `RawViewFn` is designed for drawing directly to a window's swapchain +/// Unlike the `ViewFn`, the `RawViewFn` is designed for drawing directly to a window's swap chain /// images rather than to a convenient intermediary image. pub type RawViewFn = fn(&App, &Model, &RawFrame); @@ -89,7 +86,7 @@ pub(crate) enum View { } /// A function for processing raw winit window events. -pub type RawEventFn = fn(&App, &mut Model, winit::WindowEvent); +pub type RawEventFn = fn(&App, &mut Model, &winit::event::WindowEvent); /// A function for processing window events. pub type EventFn = fn(&App, &mut Model, WindowEvent); @@ -148,6 +145,13 @@ pub type UnfocusedFn = fn(&App, &mut Model); /// A function for processing window closed events. pub type ClosedFn = fn(&App, &mut Model); +/// Errors that might occur while building the window. +#[derive(Debug)] +pub enum BuildError { + NoAvailableAdapter, + WinitOsError(winit::error::OsError), +} + // A macro for generating a handle to a function that can be stored within the Window without // requiring a type param. $TFn is the function pointer type that will be wrapped by $TFnAny. macro_rules! fn_any { @@ -210,403 +214,116 @@ fn_any!(ClosedFn, ClosedFnAny); /// A nannou window. /// -/// The `Window` acts as a wrapper around the `winit::Window` and `vulkano::Surface` types and -/// manages the associated swapchain, providing a more nannou-friendly API. +/// The `Window` acts as a wrapper around the `winit::window::Window` and `vulkano::Surface` types and +/// manages the associated swap chain, providing a more nannou-friendly API. #[derive(Debug)] pub struct Window { - pub(crate) queue: Arc, - pub(crate) surface: Arc, + pub(crate) window: winit::window::Window, + pub(crate) surface: wgpu::Surface, + pub(crate) device: wgpu::Device, + pub(crate) queue: wgpu::Queue, msaa_samples: u32, - pub(crate) swapchain: Arc, - // Data for rendering a `Frame`'s intermediary image to a swapchain image. + pub(crate) swap_chain: WindowSwapChain, + // Data for rendering a `Frame`'s intermediary image to a swap chain image. pub(crate) frame_render_data: Option, pub(crate) frame_count: u64, pub(crate) user_functions: UserFunctions, - // If the user specified one of the following parameters, use these when recreating the - // swapchain rather than our heuristics. - pub(crate) user_specified_present_mode: Option, - pub(crate) user_specified_image_count: Option, } -/// The surface type associated with a winit window. -pub type Surface = vk::swapchain::Surface; - -/// The swapchain type associated with a winit window surface. -pub type Swapchain = vk::swapchain::Swapchain; - -/// The vulkan image type associated with a winit window surface. -pub type SwapchainImage = vk::image::swapchain::SwapchainImage; - -/// The future representing the moment that the GPU will have access to the swapchain image. -pub type SwapchainAcquireFuture = vk::swapchain::SwapchainAcquireFuture; - -/// A swapchain and its images associated with a single window. -pub(crate) struct WindowSwapchain { - // Tracks whether or not the swapchain needs recreation due to resizing, etc. +/// A swap_chain and its images associated with a single window. +pub(crate) struct WindowSwapChain { + // Tracks whether or not the swap chain needs recreation due to resizing, etc. pub(crate) needs_recreation: AtomicBool, - // The index of the frame at which this swapchain was first presented. - // - // This is necessary for allowing the user to determine whether or not they need to recreate - // framebuffers in the case that the swapchain has recently been recreated. - pub(crate) frame_created: u64, - pub(crate) swapchain: Arc, - pub(crate) images: Vec>, - // In the application loop we are going to submit commands to the GPU. Submitting a command - // produces an object that implements the `GpuFuture` trait, which holds the resources for as - // long as they are in use by the GPU. - // - // Destroying the `GpuFuture` blocks until the GPU is finished executing it. In order to avoid - // that, we store the submission of the previous frame here. - pub(crate) previous_frame_end: Mutex>>>, + // The descriptor used to create the original swap chain. Useful for recreation. + pub(crate) descriptor: wgpu::SwapChainDescriptor, + // This is an `Option` in order to allow for separating ownership of the swapchain from the + // window during a `RedrawRequest`. Other than during `RedrawRequest`, this should always be + // `Some`. + pub(crate) swap_chain: Option, } -/// Swapchain building parameters for which Nannou will provide a default if unspecified. +/// SwapChain building parameters for which Nannou will provide a default if unspecified. /// /// See the builder methods for more details on each parameter. -/// -/// Valid parameters can be determined prior to building by checking the result of -/// [vk::swapchain::Surface::capabilities](https://docs.rs/vulkano/latest/vulkano/swapchain/struct.Surface.html#method.capabilities). -#[derive(Clone, Debug, Default, PartialEq)] -pub struct SwapchainBuilder { - pub format: Option, - pub color_space: Option, - pub layers: Option, - pub present_mode: Option, - pub composite_alpha: Option, - pub clipped: Option, - pub image_count: Option, - pub surface_transform: Option, +#[derive(Clone, Debug, Default)] +pub struct SwapChainBuilder { + pub usage: Option, + pub format: Option, + pub present_mode: Option, } -/// A helper type for managing framebuffers associated with a window's swapchain images. -/// -/// Creating the swapchain image framebuffers manually and maintaining them throughout the duration -/// of a program can be a tedious task that requires a lot of boilerplate code. This type -/// simplifies the process with a single `update` method that creates or recreates the framebuffers -/// if any of the following conditions are met: -/// - The given render pass is different to that which was used to create the existing -/// framebuffers. -/// - There are less framebuffers than the given frame's swapchain image index indicates are -/// required. -/// - The `frame.swapchain_image_is_new()` method indicates that the swapchain or its images have -/// recently been recreated and the framebuffers should be recreated accordingly. -#[derive(Default)] -pub struct SwapchainFramebuffers { - framebuffers: Vec>, -} - -pub type SwapchainFramebufferBuilder = - vk::FramebufferBuilder, A>; -pub type FramebufferBuildResult = - Result, vk::FramebufferCreationError>; +impl SwapChainBuilder { + pub const DEFAULT_USAGE: wgpu::TextureUsage = wgpu::TextureUsage::OUTPUT_ATTACHMENT; + pub const DEFAULT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; + pub const DEFAULT_PRESENT_MODE: wgpu::PresentMode = wgpu::PresentMode::Vsync; -impl SwapchainFramebuffers { - /// Ensure the framebuffers are up to date with the render pass and frame's swapchain image. - pub fn update( - &mut self, - frame: &RawFrame, - render_pass: Arc, - builder: F, - ) -> Result<(), vk::FramebufferCreationError> - where - F: Fn(SwapchainFramebufferBuilder<()>, Arc) -> FramebufferBuildResult, - A: 'static + vk::AttachmentsList + Send + Sync, - { - let mut just_created = false; - while frame.swapchain_image_index() >= self.framebuffers.len() { - let builder = builder( - vk::Framebuffer::start(render_pass.clone()), - frame.swapchain_image().clone(), - )?; - let fb = builder.build()?; - self.framebuffers.push(Arc::new(fb)); - just_created = true; - } - - // If the dimensions for the current framebuffer do not match, recreate it. - let old_rp = - vk::RenderPassAbstract::inner(&self.framebuffers[frame.swapchain_image_index()]) - .internal_object(); - let new_rp = render_pass.inner().internal_object(); - if !just_created && (frame.swapchain_image_is_new() || old_rp != new_rp) { - let fb = &mut self.framebuffers[frame.swapchain_image_index()]; - let builder = builder( - vk::Framebuffer::start(render_pass.clone()), - frame.swapchain_image().clone(), - )?; - let new_fb = builder.build()?; - *fb = Arc::new(new_fb); - } - Ok(()) - } -} - -impl ops::Deref for SwapchainFramebuffers { - type Target = [Arc]; - fn deref(&self) -> &Self::Target { - &self.framebuffers - } -} - -/// The errors that might occur while constructing a `Window`. -#[derive(Debug)] -pub enum BuildError { - SurfaceCreation(vk::win::CreationError), - DeviceCreation(vk::DeviceCreationError), - SwapchainCreation(vk::SwapchainCreationError), - SwapchainCapabilities(vk::swapchain::CapabilitiesError), - RenderDataCreation(frame::RenderDataCreationError), - SurfaceDoesNotSupportCompositeAlphaOpaque, -} - -impl SwapchainBuilder { - pub const DEFAULT_CLIPPED: bool = true; - pub const DEFAULT_COLOR_SPACE: vk::swapchain::ColorSpace = - vk::swapchain::ColorSpace::SrgbNonLinear; - pub const DEFAULT_COMPOSITE_ALPHA: vk::swapchain::CompositeAlpha = - vk::swapchain::CompositeAlpha::Opaque; - pub const DEFAULT_LAYERS: u32 = 1; - pub const DEFAULT_SURFACE_TRANSFORM: vk::swapchain::SurfaceTransform = - vk::swapchain::SurfaceTransform::Identity; - - /// A new empty **SwapchainBuilder** with all parameters set to `None`. + /// A new empty **SwapChainBuilder** with all parameters set to `None`. pub fn new() -> Self { Default::default() } - /// Create a **SwapchainBuilder** from an existing swapchain. + /// Create a **SwapChainBuilder** from an existing descriptor. /// - /// The resulting swapchain parameters will match that of the given `Swapchain`. - /// - /// Note that `sharing_mode` will be `None` regardless of how the given `Swapchain` was built, - /// as there is no way to determine this via the vulkano swapchain API. - pub fn from_swapchain(swapchain: &Swapchain) -> Self { - SwapchainBuilder::new() - .format(swapchain.format()) - .image_count(swapchain.num_images()) - .layers(swapchain.layers()) - .surface_transform(swapchain.transform()) - .composite_alpha(swapchain.composite_alpha()) - .present_mode(swapchain.present_mode()) - .clipped(swapchain.clipped()) - } - - /// Specify the pixel format for the swapchain. - /// - /// By default, nannou attempts to use the first format valid for the `SrgbNonLinear` color - /// space. - /// - /// See the [vulkano docs](https://docs.rs/vulkano/latest/vulkano/format/enum.Format.html). - pub fn format(mut self, format: vk::Format) -> Self { - self.format = Some(format); - self + /// The resulting swap chain parameters will match that of the given `SwapChainDescriptor`. + pub fn from_descriptor(desc: &wgpu::SwapChainDescriptor) -> Self { + SwapChainBuilder::new() + .usage(desc.usage) + .format(desc.format) + .present_mode(desc.present_mode) } - /// If `format` is `None`, will attempt to find the first available `Format` that supports this - /// `ColorSpace`. - /// - /// If `format` is `Some`, this parameter is ignored. - /// - /// By default, nannou attempts to use the first format valid for the `SrgbNonLinear` color - /// space. - /// - /// See the [vulkano docs](https://docs.rs/vulkano/latest/vulkano/swapchain/enum.ColorSpace.html). - pub fn color_space(mut self, color_space: vk::swapchain::ColorSpace) -> Self { - self.color_space = Some(color_space); + /// Specify the texture usage for the swap chain. + pub fn usage(mut self, usage: wgpu::TextureUsage) -> Self { + self.usage = Some(usage); self } - /// How the alpha values of the pixels of the window are treated. - /// - /// By default, nannou uses `CompositeAlpha::Opaque`. - /// - /// See the [vulkano docs](https://docs.rs/vulkano/latest/vulkano/swapchain/enum.CompositeAlpha.html). - pub fn composite_alpha(mut self, composite_alpha: vk::swapchain::CompositeAlpha) -> Self { - self.composite_alpha = Some(composite_alpha); + /// Specify the texture format for the swap chain. + pub fn format(mut self, format: wgpu::TextureFormat) -> Self { + self.format = Some(format); self } - /// The way in which swapchain images are presented to the display. + /// The way in which swap chain images are presented to the display. /// /// By default, nannou will attempt to select the ideal present mode depending on the current - /// app `LoopMode`. If the current loop mode is `Wait` or `Rate`, nannou will attempt to use - /// the `Mailbox` present mode with an `image_count` of `3`. If the current loop mode is - /// `RefreshSync`, nannou will use the `Fifo` present m ode with an `image_count` of `2`. - /// - /// See the [vulkano docs](https://docs.rs/vulkano/latest/vulkano/swapchain/enum.PresentMode.html). - pub fn present_mode(mut self, present_mode: vk::swapchain::PresentMode) -> Self { + /// app `LoopMode`. + pub fn present_mode(mut self, present_mode: wgpu::PresentMode) -> Self { self.present_mode = Some(present_mode); self } - /// The number of images used by the swapchain. - /// - /// By default, nannou will attempt to select the ideal image count depending on the current - /// app `LoopMode`. If the current loop mode is `Wait` or `Rate`, nannou will attempt to use - /// the `Mailbox` present mode with an `image_count` of `3`. If the current loop mode is - /// `RefreshSync`, nannou will use the `Fifo` present m ode with an `image_count` of `2`. - pub fn image_count(mut self, image_count: u32) -> Self { - self.image_count = Some(image_count); - self - } - - /// Whether the implementation is allowed to discard rendering operations that affect regions - /// of the surface which aren't visible. - /// - /// This is important to take into account if your fragment shader has side-effects or if you - /// want to read back the content of the image afterwards. - pub fn clipped(mut self, clipped: bool) -> Self { - self.clipped = Some(clipped); - self - } - - /// A transformation to apply to the image before showing it on the screen. - /// - /// See the [vulkano docs](https://docs.rs/vulkano/latest/vulkano/swapchain/enum.SurfaceTransform.html). - pub fn surface_transform(mut self, surface_transform: vk::swapchain::SurfaceTransform) -> Self { - self.surface_transform = Some(surface_transform); - self - } - - pub fn layers(mut self, layers: u32) -> Self { - self.layers = Some(layers); - self - } - - /// Build the swapchain. - /// - /// `fallback_dimensions` are dimensions to use in the case that the surface capabilities - /// `current_extent` field is `None`, which may happen if a surface's size is determined by the - /// swapchain's size. - pub(crate) fn build( + /// Build the swap chain. + pub(crate) fn build( self, - device: Arc, - surface: Arc, - sharing_mode: S, + device: &wgpu::Device, + surface: &wgpu::Surface, + [width, height]: [u32; 2], loop_mode: &LoopMode, - fallback_dimensions: Option<[u32; 2]>, - old_swapchain: Option<&Arc>, - ) -> Result<(Arc, Vec>), vk::SwapchainCreationError> - where - S: Into, - { - let capabilities = surface - .capabilities(device.physical_device()) - .expect("failed to retrieve surface capabilities"); - - let dimensions = capabilities - .current_extent - .or(fallback_dimensions) - .unwrap_or([ - DEFAULT_DIMENSIONS.width as _, - DEFAULT_DIMENSIONS.height as _, - ]); - - // Retrieve the format. - let format = match self.format { - Some(fmt) => fmt, - None => { - let color_space = self.color_space.unwrap_or(Self::DEFAULT_COLOR_SPACE); - - // First, try to pick an Srgb format. - capabilities - .supported_formats - .iter() - .filter(|&&(fmt, cs)| vk::format_is_srgb(fmt) && cs == color_space) - .next() - .or_else(|| { - // Otherwise just try and math the color space. - capabilities - .supported_formats - .iter() - .filter(|&&(_, cs)| cs == color_space) - .next() - }) - .map(|&(fmt, _cs)| fmt) - .ok_or(vk::SwapchainCreationError::UnsupportedFormat)? - } - }; - - // Determine the optimal present mode and image count based on the specified parameters and - // the current loop mode. - let (present_mode, image_count) = preferred_present_mode_and_image_count( - &loop_mode, - capabilities.min_image_count, - self.present_mode, - self.image_count, - &capabilities.present_modes, - ); - - // Attempt to retrieve the desired composite alpha. - let composite_alpha = match self.composite_alpha { - Some(alpha) => alpha, - None => match capabilities.supported_composite_alpha.opaque { - true => Self::DEFAULT_COMPOSITE_ALPHA, - false => return Err(vk::SwapchainCreationError::UnsupportedCompositeAlpha), - }, - }; - - let layers = self.layers.unwrap_or(Self::DEFAULT_LAYERS); - let clipped = self.clipped.unwrap_or(Self::DEFAULT_CLIPPED); - let surface_transform = self - .surface_transform - .unwrap_or(Self::DEFAULT_SURFACE_TRANSFORM); - - Swapchain::new( - device, - surface, - image_count, + ) -> (wgpu::SwapChain, wgpu::SwapChainDescriptor) { + let usage = self.usage.unwrap_or(Self::DEFAULT_USAGE); + let format = self.format.unwrap_or(Self::DEFAULT_FORMAT); + let present_mode = self + .present_mode + .unwrap_or_else(|| preferred_present_mode(loop_mode)); + let desc = wgpu::SwapChainDescriptor { + usage, format, - dimensions, - layers, - capabilities.supported_usage_flags, - sharing_mode, - surface_transform, - composite_alpha, + width, + height, present_mode, - clipped, - old_swapchain, - ) + }; + let swap_chain = device.create_swap_chain(surface, &desc); + (swap_chain, desc) } } -/// Determine the optimal present mode and image count for the given loop mode. +/// Determine the optimal present mode for the given loop mode. /// -/// If a specific present mode or image count is desired, they may be optionally specified. -pub fn preferred_present_mode_and_image_count( - loop_mode: &LoopMode, - min_image_count: u32, - present_mode: Option, - image_count: Option, - supported_present_modes: &vk::swapchain::SupportedPresentModes, -) -> (vk::swapchain::PresentMode, u32) { - match (present_mode, image_count) { - (Some(pm), Some(ic)) => (pm, ic), - (None, _) => match *loop_mode { - LoopMode::RefreshSync { .. } => { - let image_count = image_count.unwrap_or_else(|| cmp::max(min_image_count, 2)); - (vk::swapchain::PresentMode::Fifo, image_count) - } - LoopMode::Wait { .. } | LoopMode::Rate { .. } | LoopMode::NTimes { .. } => { - if supported_present_modes.mailbox { - let image_count = image_count.unwrap_or_else(|| cmp::max(min_image_count, 3)); - (vk::swapchain::PresentMode::Mailbox, image_count) - } else { - let image_count = image_count.unwrap_or_else(|| cmp::max(min_image_count, 2)); - (vk::swapchain::PresentMode::Fifo, image_count) - } - } - }, - (Some(present_mode), None) => { - let image_count = match present_mode { - vk::swapchain::PresentMode::Immediate => min_image_count, - vk::swapchain::PresentMode::Mailbox => cmp::max(min_image_count, 3), - vk::swapchain::PresentMode::Fifo => cmp::max(min_image_count, 2), - vk::swapchain::PresentMode::Relaxed => cmp::max(min_image_count, 2), - }; - (present_mode, image_count) - } - } +/// TODO: Currently this always assumes `Vsync`. Do we want to provide `NoVsync` option for *any* +/// loop modes? +pub fn preferred_present_mode(_loop_mode: &LoopMode) -> wgpu::PresentMode { + wgpu::PresentMode::Vsync } impl<'app> Builder<'app> { @@ -614,76 +331,55 @@ impl<'app> Builder<'app> { pub fn new(app: &'app App) -> Self { Builder { app, - vk_physical_device: None, - vk_device_extensions: None, - vk_device_queue: None, - window: winit::WindowBuilder::new(), + window: winit::window::WindowBuilder::new(), title_was_set: false, - swapchain_builder: Default::default(), + swap_chain_builder: Default::default(), + request_adapter_opts: None, + device_desc: None, user_functions: Default::default(), msaa_samples: None, } } /// Build the window with some custom window parameters. - pub fn window(mut self, window: winit::WindowBuilder) -> Self { + pub fn window(mut self, window: winit::window::WindowBuilder) -> Self { self.window = window; self } - /// The physical device to associate with the window surface's swapchain. - pub fn vk_physical_device(mut self, device: vk::PhysicalDevice<'app>) -> Self { - self.vk_physical_device = Some(device); + /// Specify a set of parameters for building the window surface swap chain. + pub fn swap_chain_builder(mut self, swap_chain_builder: SwapChainBuilder) -> Self { + self.swap_chain_builder = swap_chain_builder; self } - /// Specify a set of required extensions. - /// - /// The device associated with the window's swapchain *must* always have the `khr_swapchain` - /// feature enabled, so it will be implicitly enabled whether or not it is specified in this - /// given set of extensions. - pub fn vk_device_extensions(mut self, extensions: vk::DeviceExtensions) -> Self { - self.vk_device_extensions = Some(extensions); + /// Specify a custom set of options to request an adapter with. This is useful for describing a + /// set of desired properties for the requested physical device. + pub fn request_adapter_options(mut self, opts: wgpu::RequestAdapterOptions) -> Self { + self.request_adapter_opts = Some(opts); self } - /// Specify the vulkan device queue for this window. - /// - /// This queue is used as the `SharingMode` for the swapchain, and is also used for - /// constructing the `Frame`'s intermediary image. - /// - /// Once the window is built, this queue can be accessed via the `window.swapchain_queue()` - /// method. - /// - /// Note: If this builder method is called, previous calls to `vk_physical_device` and - /// `vk_device_extensions` will be ignored as specifying the queue for the sharing mode - /// implies which logical device is desired. - pub fn vk_device_queue(mut self, queue: Arc) -> Self { - self.vk_device_queue = Some(queue); - self - } - - /// Specify a set of parameters for building the window surface swapchain. - pub fn swapchain_builder(mut self, swapchain_builder: SwapchainBuilder) -> Self { - self.swapchain_builder = swapchain_builder; + /// Specify a device descriptor to use when requesting the logical device from the adapter. + /// This allows for specifying custom wgpu device extensions. + pub fn device_descriptor(mut self, device_desc: wgpu::DeviceDescriptor) -> Self { + self.device_desc = Some(device_desc); self } /// Specify the number of samples per pixel for the multisample anti-aliasing render pass. /// /// If `msaa_samples` is unspecified, the first default value that nannou will attempt to use - /// can be found via the `Frame::DEFAULT_MSAA_SAMPLES` constant. If however this value is not - /// supported by the window's swapchain, nannou will fallback to the next smaller power of 2 - /// that is supported. If MSAA is not supported at all, then the default will be 1. + /// can be found via the `Frame::DEFAULT_MSAA_SAMPLES` constant. /// /// **Note:** This parameter has no meaning if the window uses a **raw_view** function for /// rendering graphics to the window rather than a **view** function. This is because the - /// **raw_view** function provides a **RawFrame** with direct access to the swapchain image + /// **raw_view** function provides a **RawFrame** with direct access to the swap chain image /// itself and thus must manage their own MSAA pass. /// /// On the other hand, the `view` function provides the `Frame` type which allows the user to /// render to a multisampled intermediary image allowing Nannou to take care of resolving the - /// multisampled image to the swapchain image. In order to avoid confusion, The `Window::build` + /// multisampled image to the swap chain image. In order to avoid confusion, The `Window::build` /// method will `panic!` if the user tries to specify `msaa_samples` as well as a `raw_view` /// method. /// @@ -719,7 +415,7 @@ impl<'app> Builder<'app> { /// surface of the window on your display. /// /// Unlike the **ViewFn**, the **RawViewFn** provides a **RawFrame** that is designed for - /// drawing directly to a window's swapchain images, rather than to a convenient intermediary + /// drawing directly to a window's swap chain images, rather than to a convenient intermediary /// image. pub fn raw_view(mut self, raw_view_fn: RawViewFn) -> Self where @@ -756,7 +452,7 @@ impl<'app> Builder<'app> { self } - /// The same as the `event` method, but allows for processing raw `winit::WindowEvent`s rather + /// The same as the `event` method, but allows for processing raw `winit::event::WindowEvent`s rather /// than Nannou's simplified `event::WindowEvent`s. /// /// ## Event Function Call Order @@ -938,12 +634,11 @@ impl<'app> Builder<'app> { pub fn build(self) -> Result { let Builder { app, - vk_physical_device, - vk_device_extensions, - vk_device_queue, mut window, title_was_set, - swapchain_builder, + swap_chain_builder, + request_adapter_opts, + device_desc, user_functions, msaa_samples, } = self; @@ -960,143 +655,75 @@ impl<'app> Builder<'app> { } } - // Retrieve dimensions to use as a fallback in case vulkano swapchain capabilities - // `current_extent` is `None`. This happens when the window size is determined by the size - // of the swapchain. - let initial_swapchain_dimensions = window - .window - .dimensions - .or_else(|| { - window - .window - .fullscreen - .as_ref() - .map(|monitor| monitor.get_dimensions().to_logical(1.0)) - }) - .unwrap_or_else(|| { - let mut dim = DEFAULT_DIMENSIONS; - if let Some(min) = window.window.min_dimensions { - dim.width = dim.width.max(min.width); - dim.height = dim.height.max(min.height); - } - if let Some(max) = window.window.max_dimensions { - dim.width = dim.width.min(max.width); - dim.height = dim.height.min(max.height); - } - dim - }); - - // Use the `initial_swapchain_dimensions` as the default dimensions for the window if none - // were specified. - if window.window.dimensions.is_none() && window.window.fullscreen.is_none() { - window.window.dimensions = Some(initial_swapchain_dimensions); - } - - // Build the vulkan surface. - let surface = window.build_vk_surface(&app.events_loop, app.vk_instance.clone())?; - - // The logical device queue to use as the swapchain sharing mode. - // This queue will also be used for constructing the `Frame`'s intermediary image. - let queue = match vk_device_queue { - Some(queue) => queue, - None => { - // Retrieve the physical, vulkan-supported device to use. - let physical_device = vk_physical_device - .or_else(|| app.default_vk_physical_device()) - .unwrap_or_else(|| unimplemented!()); - - // Select the queue family to use. Default to the first graphics-supporting queue. - let queue_family = physical_device - .queue_families() - .find(|&q| q.supports_graphics() && surface.is_supported(q).unwrap_or(false)) - .unwrap_or_else(|| unimplemented!("couldn't find a graphical queue family")); - - // We only have one queue, so give an arbitrary priority. - let queue_priority = 0.5; - - // The required device extensions. - let mut device_ext = - vk_device_extensions.unwrap_or_else(vk::DeviceExtensions::none); - device_ext.khr_swapchain = true; - - // Enable all supported device features. - let features = physical_device.supported_features(); - - // Construct the logical device and queues. - let (_device, mut queues) = vk::Device::new( - physical_device, - features, - &device_ext, - [(queue_family, queue_priority)].iter().cloned(), - )?; - - // Since it is possible to request multiple queues, the queues variable returned by - // the function is in fact an iterator. In this case this iterator contains just - // one element, so let's extract it. - let queue = queues.next().expect("expected a single device queue"); - queue - } + // Build the window. + let window = { + let window_target = app + .event_loop_window_target + .as_ref() + .expect("unexpected invalid App.event_loop_window_target state - please report") + .as_ref(); + window.build(window_target)? }; - let user_specified_present_mode = swapchain_builder.present_mode; - let user_specified_image_count = swapchain_builder.image_count; - - // Build the swapchain used for displaying the window contents. - let (swapchain, images) = { - // Set the dimensions of the swapchain to that of the surface. - let fallback_dimensions = [ - initial_swapchain_dimensions.width as _, - initial_swapchain_dimensions.height as _, - ]; - - swapchain_builder.build( - queue.device().clone(), - surface.clone(), - &queue, - &app.loop_mode(), - Some(fallback_dimensions), - None, - )? - }; - - // If we're using an intermediary image for rendering frames to swapchain images, create + // Build the wgpu surface. + let surface = wgpu::Surface::create(&window); + + // Request the adapter. + let request_adapter_opts = + request_adapter_opts.unwrap_or(wgpu::DEFAULT_ADAPTER_REQUEST_OPTIONS); + let adapter = + wgpu::Adapter::request(&request_adapter_opts).ok_or(BuildError::NoAvailableAdapter)?; + + // Instantiate the logical device. + let device_desc = device_desc.unwrap_or_else(wgpu::default_device_descriptor); + let (device, queue) = adapter.request_device(&device_desc); + + // Build the swapchain. + // TODO: Check, do we really want logical here? + let win_dims: [u32; 2] = window + .inner_size() + .to_logical::(window.scale_factor()) + .into(); + let (swap_chain, swap_chain_desc) = + swap_chain_builder.build(&device, &surface, win_dims, &app.loop_mode()); + + // If we're using an intermediary image for rendering frames to swap_chain images, create // the necessary render data. let (frame_render_data, msaa_samples) = match user_functions.view { Some(View::WithModel(_)) | Some(View::Sketch(_)) | None => { - let target_msaa_samples = msaa_samples.unwrap_or(Frame::DEFAULT_MSAA_SAMPLES); - let physical_device = queue.device().physical_device(); - let msaa_samples = vk::msaa_samples_limited(&physical_device, target_msaa_samples); + let msaa_samples = msaa_samples.unwrap_or(Frame::DEFAULT_MSAA_SAMPLES); + // TODO: Verity that requested sample count is valid?? + let swap_chain_dims = [swap_chain_desc.width, swap_chain_desc.height]; let render_data = frame::RenderData::new( - queue.device().clone(), - swapchain.dimensions(), + &device, + swap_chain_dims, + swap_chain_desc.format, msaa_samples, - )?; + ); (Some(render_data), msaa_samples) } Some(View::WithModelRaw(_)) => (None, 1), }; - let window_id = surface.window().id(); + let window_id = window.id(); let needs_recreation = AtomicBool::new(false); - let previous_frame_end = Mutex::new(None); let frame_count = 0; - let swapchain = Arc::new(WindowSwapchain { + let swap_chain = WindowSwapChain { needs_recreation, - frame_created: frame_count, - swapchain, - images, - previous_frame_end, - }); + descriptor: swap_chain_desc, + swap_chain: Some(swap_chain), + }; + let window = Window { - queue, + window, surface, + device, + queue, msaa_samples, - swapchain, + swap_chain, frame_render_data, frame_count, user_functions, - user_specified_present_mode, - user_specified_image_count, }; app.windows.borrow_mut().insert(window_id, window); @@ -1110,28 +737,26 @@ impl<'app> Builder<'app> { fn map_window(self, map: F) -> Self where - F: FnOnce(winit::WindowBuilder) -> winit::WindowBuilder, + F: FnOnce(winit::window::WindowBuilder) -> winit::window::WindowBuilder, { let Builder { app, - vk_physical_device, - vk_device_extensions, - vk_device_queue, window, title_was_set, - swapchain_builder, + device_desc, + request_adapter_opts, + swap_chain_builder, user_functions, msaa_samples, } = self; let window = map(window); Builder { app, - vk_physical_device, - vk_device_extensions, - vk_device_queue, window, title_was_set, - swapchain_builder, + device_desc, + request_adapter_opts, + swap_chain_builder, user_functions, msaa_samples, } @@ -1139,19 +764,31 @@ impl<'app> Builder<'app> { // Window builder methods. - /// Requests the window to be specific dimensions pixels. - pub fn with_dimensions(self, width: u32, height: u32) -> Self { - self.map_window(|w| w.with_dimensions((width, height).into())) + /// Requests the window to be specific dimensions in points. + /// + /// This is short-hand for `with_inner_size_points`. + pub fn with_size(self, width: u32, height: u32) -> Self { + self.with_inner_size_points(width, height) + } + + /// Requests the window to be specific dimensions in points. + pub fn with_inner_size_points(self, width: u32, height: u32) -> Self { + self.map_window(|w| w.with_inner_size(winit::dpi::LogicalSize { width, height })) + } + + /// Requests the window to be specific dimensions in pixels. + pub fn with_inner_size_pixels(self, width: u32, height: u32) -> Self { + self.map_window(|w| w.with_inner_size(winit::dpi::PhysicalSize { width, height })) } /// Set the minimum dimensions in pixels for the window. - pub fn with_min_dimensions(self, width: u32, height: u32) -> Self { - self.map_window(|w| w.with_min_dimensions((width, height).into())) + pub fn with_min_inner_size_pixels(self, width: u32, height: u32) -> Self { + self.map_window(|w| w.with_min_inner_size(winit::dpi::PhysicalSize { width, height })) } /// Set the maximum dimensions in pixels for the window. - pub fn with_max_dimensions(self, width: u32, height: u32) -> Self { - self.map_window(|w| w.with_max_dimensions((width, height).into())) + pub fn with_max_inner_size_pixels(self, width: u32, height: u32) -> Self { + self.map_window(|w| w.with_max_inner_size(winit::dpi::PhysicalSize { width, height })) } /// Requests a specific title for the window. @@ -1167,8 +804,8 @@ impl<'app> Builder<'app> { /// /// None means a normal window, Some(MonitorId) means a fullscreen window on that specific /// monitor. - pub fn with_fullscreen(self, monitor: Option) -> Self { - self.map_window(|w| w.with_fullscreen(monitor)) + pub fn with_fullscreen(self, fullscreen: Option) -> Self { + self.map_window(|w| w.with_fullscreen(fullscreen)) } /// Requests maximized mode. @@ -1177,36 +814,29 @@ impl<'app> Builder<'app> { } /// Sets whether the window will be initially hidden or visible. - pub fn with_visibility(self, visible: bool) -> Self { - self.map_window(|w| w.with_visibility(visible)) + pub fn with_visible(self, visible: bool) -> Self { + self.map_window(|w| w.with_visible(visible)) } /// Sets whether the background of the window should be transparent. - pub fn with_transparency(self, transparent: bool) -> Self { - self.map_window(|w| w.with_transparency(transparent)) + pub fn with_transparent(self, transparent: bool) -> Self { + self.map_window(|w| w.with_transparent(transparent)) } /// Sets whether the window should have a border, a title bar, etc. pub fn with_decorations(self, decorations: bool) -> Self { self.map_window(|w| w.with_decorations(decorations)) } - - /// Enables multitouch. - pub fn with_multitouch(self) -> Self { - self.map_window(|w| w.with_multitouch()) - } } impl Window { - const NO_LONGER_EXISTS: &'static str = "the window no longer exists"; - - // `winit::Window` methods. + // `winit::window::Window` methods. /// Modifies the title of the window. /// /// This is a no-op if the window has already been closed. pub fn set_title(&self, title: &str) { - self.surface.window().set_title(title); + self.window.set_title(title); } /// Shows the window if it was hidden. @@ -1214,8 +844,9 @@ impl Window { /// ## Platform-specific /// /// Has no effect on Android. + #[deprecated(note = "please use `set_visible(true)` instead")] pub fn show(&self) { - self.surface.window().show() + self.set_visible(true) } /// Hides the window if it was visible. @@ -1223,8 +854,20 @@ impl Window { /// ## Platform-specific /// /// Has no effect on Android. + #[deprecated(note = "please use `set_visible(false)` instead")] pub fn hide(&self) { - self.surface.window().hide() + self.set_visible(false) + } + + /// Set the visibility of the window. + /// + /// ## Platform-specific + /// + /// - Android: Has no effect. + /// - iOS: Can only be called on the main thread. + /// - Web: Has no effect. + pub fn set_visible(&self, visible: bool) { + self.window.set_visible(visible) } /// The position of the top-left hand corner of the window relative to the top-left hand corner @@ -1236,19 +879,16 @@ impl Window { /// /// The coordinates can be negative if the top-left hand corner of the window is outside of the /// visible screen region. - pub fn position(&self) -> (i32, i32) { - self.surface - .window() - .get_position() - .expect(Self::NO_LONGER_EXISTS) - .into() + pub fn outer_position_pixels(&self) -> Result<(i32, i32), winit::error::NotSupportedError> { + self.window.outer_position().map(Into::into) } /// Modifies the position of the window. /// - /// See `get_position` for more information about the returned coordinates. - pub fn set_position(&self, x: i32, y: i32) { - self.surface.window().set_position((x, y).into()) + /// See `position` for more information about the returned coordinates. + pub fn set_outer_position_pixels(&self, x: i32, y: i32) { + self.window + .set_outer_position(winit::dpi::PhysicalPosition { x, y }) } /// The size in pixels of the client area of the window. @@ -1257,15 +897,7 @@ impl Window { /// are the dimensions of the frame buffer, and the dimensions that you should use when you /// call glViewport. pub fn inner_size_pixels(&self) -> (u32, u32) { - self.surface - .window() - .get_inner_size() - .map(|logical_px| { - let hidpi_factor = self.surface.window().get_hidpi_factor(); - logical_px.to_physical(hidpi_factor) - }) - .expect(Self::NO_LONGER_EXISTS) - .into() + self.window.inner_size().into() } /// The size in points of the client area of the window. @@ -1273,15 +905,12 @@ impl Window { /// The client area is the content of the window, excluding the title bar and borders. To get /// the dimensions of the frame buffer when calling `glViewport`, multiply with hidpi factor. /// - /// This is the same as dividing the result of `inner_size_pixels()` by `hidpi_factor()`. + /// This is the same as dividing the result of `inner_size_pixels()` by `scale_factor()`. pub fn inner_size_points(&self) -> (geom::scalar::Default, geom::scalar::Default) { - let size = self - .surface - .window() - .get_inner_size() - .expect(Self::NO_LONGER_EXISTS); - let (w, h): (f64, f64) = size.into(); - (w as _, h as _) + self.window + .inner_size() + .to_logical::(self.window.scale_factor()) + .into() } /// The size of the window in pixels. @@ -1289,15 +918,7 @@ impl Window { /// These dimensions include title bar and borders. If you don't want these, you should use /// `inner_size_pixels` instead. pub fn outer_size_pixels(&self) -> (u32, u32) { - self.surface - .window() - .get_outer_size() - .map(|logical_px| { - let hidpi_factor = self.surface.window().get_hidpi_factor(); - logical_px.to_physical(hidpi_factor) - }) - .expect(Self::NO_LONGER_EXISTS) - .into() + self.window.outer_size().into() } /// The size of the window in points. @@ -1305,46 +926,47 @@ impl Window { /// These dimensions include title bar and borders. If you don't want these, you should use /// `inner_size_points` instead. /// - /// This is the same as dividing the result of `outer_size_pixels()` by `hidpi_factor()`. + /// This is the same as dividing the result of `outer_size_pixels()` by `scale_factor()`. pub fn outer_size_points(&self) -> (f32, f32) { - let size = self - .surface - .window() - .get_outer_size() - .expect(Self::NO_LONGER_EXISTS); - let (w, h): (f64, f64) = size.into(); - (w as _, h as _) + self.window + .outer_size() + .to_logical::(self.window.scale_factor()) + .into() } /// Modifies the inner size of the window. /// /// See the `inner_size` methods for more informations about the values. pub fn set_inner_size_pixels(&self, width: u32, height: u32) { - self.surface.window().set_inner_size((width, height).into()) + self.window + .set_inner_size(winit::dpi::PhysicalSize { width, height }) } /// Modifies the inner size of the window using point values. /// - /// Internally, the given width and height are multiplied by the `hidpi_factor` to get the + /// Internally, the given width and height are multiplied by the `scale_factor` to get the /// values in pixels before calling `set_inner_size_pixels` internally. pub fn set_inner_size_points(&self, width: f32, height: f32) { - let hidpi_factor = self.hidpi_factor(); - let w_px = (width * hidpi_factor) as _; - let h_px = (height * hidpi_factor) as _; - self.set_inner_size_pixels(w_px, h_px); + self.window + .set_inner_size(winit::dpi::LogicalSize { width, height }) } /// The ratio between the backing framebuffer resolution and the window size in screen pixels. /// /// This is typically `1.0` for a normal display, `2.0` for a retina display and higher on more /// modern displays. - pub fn hidpi_factor(&self) -> geom::scalar::Default { - self.surface.window().get_hidpi_factor() as _ + pub fn scale_factor(&self) -> geom::scalar::Default { + self.window.scale_factor() as _ } - /// Changes the position of the cursor in window coordinates. - pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), String> { - self.surface.window().set_cursor_position((x, y).into()) + /// Changes the position of the cursor in logical window coordinates. + pub fn set_cursor_position_points( + &self, + x: f32, + y: f32, + ) -> Result<(), winit::error::ExternalError> { + self.window + .set_cursor_position(winit::dpi::LogicalPosition { x, y }) } /// Modifies the mouse cursor of the window. @@ -1352,8 +974,8 @@ impl Window { /// ## Platform-specific /// /// Has no effect on Android. - pub fn set_cursor(&self, state: MouseCursor) { - self.surface.window().set_cursor(state); + pub fn set_cursor_icon(&self, state: winit::window::CursorIcon) { + self.window.set_cursor_icon(state); } /// Grabs the cursor, preventing it from leaving the window. @@ -1364,11 +986,11 @@ impl Window { /// awkward. /// /// This has no effect on Android or iOS. - pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { - self.surface.window().grab_cursor(grab) + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), winit::error::ExternalError> { + self.window.set_cursor_grab(grab) } - /// Hides the cursor, making it invisible but still usable. + /// Hides the cursor with `false`, making it invisible but still usable. /// /// ## Platform-specific /// @@ -1378,69 +1000,73 @@ impl Window { /// outside of the window. /// /// This has no effect on Android or iOS. - pub fn hide_cursor(&self, hide: bool) { - self.surface.window().hide_cursor(hide) + pub fn set_cursor_visible(&self, hide: bool) { + self.window.set_cursor_visible(hide) } /// Sets the window to maximized or back. pub fn set_maximized(&self, maximized: bool) { - self.surface.window().set_maximized(maximized) + self.window.set_maximized(maximized) } - /// Set the window to fullscreen on the monitor associated with the given `Id`. + /// Set the window to fullscreen. /// /// Call this method again with `None` to revert back from fullscreen. /// /// ## Platform-specific /// - /// Has no effect on Android. - pub fn set_fullscreen(&self, monitor: Option) { - self.surface.window().set_fullscreen(monitor) + /// - macOS: `Fullscreen::Exclusive` provides true exclusive mode with a video mode change. + /// Caveat! macOS doesn't provide task switching (or spaces!) while in exclusive fullscreen + /// mode. This mode should be used when a video mode change is desired, but for a better user + /// experience, borderless fullscreen might be preferred. + /// + /// `Fullscreen::Borderless` provides a borderless fullscreen window on a separate space. + /// This is the idiomatic way for fullscreen games to work on macOS. See + /// WindowExtMacOs::set_simple_fullscreen if separate spaces are not preferred. + /// + /// The dock and the menu bar are always disabled in fullscreen mode. + /// + /// - iOS: Can only be called on the main thread. + /// - Wayland: Does not support exclusive fullscreen mode. + /// - Windows: Screen saver is disabled in fullscreen mode. + pub fn set_fullscreen(&self, monitor: Option) { + self.window.set_fullscreen(monitor) } /// The current monitor that the window is on or the primary monitor if nothing matches. - pub fn current_monitor(&self) -> MonitorId { - self.surface.window().get_current_monitor() + pub fn current_monitor(&self) -> winit::monitor::MonitorHandle { + self.window.current_monitor() } /// A unique identifier associated with this window. pub fn id(&self) -> Id { - self.surface.window().id() + self.window.id() } // Access to vulkano API. - /// Returns a reference to the window's Vulkan swapchain surface. - pub fn surface(&self) -> &Surface { + /// Returns a reference to the window's Vulkan swap chain surface. + pub fn surface(&self) -> &wgpu::Surface { &self.surface } - /// The swapchain associated with this window's vulkan surface. - pub fn swapchain(&self) -> &Swapchain { - &self.swapchain.swapchain + /// The descriptor for the swap chain associated with this window's vulkan surface. + pub fn swap_chain_descriptor(&self) -> &wgpu::SwapChainDescriptor { + &self.swap_chain.descriptor } - /// The vulkan logical device on which the window's swapchain is running. + /// The vulkan logical device on which the window's swap chain is running. /// - /// This is shorthand for `DeviceOwned::device(window.swapchain())`. - pub fn swapchain_device(&self) -> &Arc { - vk::DeviceOwned::device(self.swapchain()) + /// This is shorthand for `DeviceOwned::device(window.swap_chain())`. + pub fn swap_chain_device(&self) -> &wgpu::Device { + &self.device } - /// The vulkan graphics queue on which the window swapchain work is run. - pub fn swapchain_queue(&self) -> &Arc { + /// The vulkan graphics queue on which the window swap chain work is run. + pub fn swap_chain_queue(&self) -> &wgpu::Queue { &self.queue } - /// The vulkan images associated with the window's swapchain. - /// - /// This method is exposed in order to allow for interop with low-level vulkano code (e.g. - /// framebuffer creation). We recommend that you avoid storing these images as the swapchain - /// and its images may be recreated at any moment in time. - pub fn swapchain_images(&self) -> &[Arc] { - &self.swapchain.images - } - /// The number of samples used in the MSAA for the image associated with the `view` function's /// `Frame` type. /// @@ -1452,26 +1078,26 @@ impl Window { // Custom methods. - // A utility function to simplify the recreation of a swapchain. - pub(crate) fn replace_swapchain( + // A utility function to simplify the recreation of a swap_chain. + pub(crate) fn replace_swap_chain( &mut self, - new_swapchain: Arc, - new_images: Vec>, + new_descriptor: wgpu::SwapChainDescriptor, + new_swap_chain: wgpu::SwapChain, ) { - let previous_frame_end = self - .swapchain - .previous_frame_end - .lock() - .expect("failed to lock `previous_frame_end`") - .take(); - self.swapchain = Arc::new(WindowSwapchain { + self.swap_chain = WindowSwapChain { needs_recreation: AtomicBool::new(false), - frame_created: self.frame_count, - swapchain: new_swapchain, - images: new_images, - previous_frame_end: Mutex::new(previous_frame_end), - }); - // TODO: Update frame_render_data? + descriptor: new_descriptor, + swap_chain: Some(new_swap_chain), + }; + // TODO: Update frame_render_data? Should recreate `msaa_texture`s with new sc descriptor. + unimplemented!(); + + // let swap_chain_dims = [new_descriptor.width, new_descriptor.height]; + // self.frame_render_data = frame::RenderData::new( + // &self.device, + // swap_chain_dims, + // self.msaa_samples + // ); } /// Attempts to determine whether or not the window is currently fullscreen. @@ -1484,7 +1110,7 @@ impl Window { /// complicated quite quickly. pub fn is_fullscreen(&self) -> bool { let (w, h) = self.outer_size_pixels(); - let (mw, mh): (u32, u32) = self.current_monitor().get_dimensions().into(); + let (mw, mh): (u32, u32) = self.current_monitor().size().into(); w == mw && h == mh } @@ -1522,73 +1148,29 @@ impl fmt::Debug for View { // Deref implementations. -impl ops::Deref for WindowSwapchain { - type Target = Arc; - fn deref(&self) -> &Self::Target { - &self.swapchain - } -} - -impl fmt::Debug for WindowSwapchain { +impl fmt::Debug for WindowSwapChain { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "WindowSwapchain ( swapchain: {:?}, swapchain_images: {:?} )", - self.swapchain, - self.images.len(), + "WindowSwapChain ( descriptor: {:?}, swap_chain: {:?} )", + self.descriptor, self.swap_chain, ) } } // Error implementations. -impl StdError for BuildError { - fn description(&self) -> &str { - match *self { - BuildError::SurfaceCreation(ref err) => err.description(), - BuildError::DeviceCreation(ref err) => err.description(), - BuildError::SwapchainCreation(ref err) => err.description(), - BuildError::SwapchainCapabilities(ref err) => err.description(), - BuildError::RenderDataCreation(ref err) => err.description(), - BuildError::SurfaceDoesNotSupportCompositeAlphaOpaque => { - "`CompositeAlpha::Opaque` not supported by window surface" - } - } - } -} - impl fmt::Display for BuildError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.description()) - } -} - -impl From for BuildError { - fn from(e: vk::win::CreationError) -> Self { - BuildError::SurfaceCreation(e) - } -} - -impl From for BuildError { - fn from(e: vk::DeviceCreationError) -> Self { - BuildError::DeviceCreation(e) - } -} - -impl From for BuildError { - fn from(e: vk::swapchain::SwapchainCreationError) -> Self { - BuildError::SwapchainCreation(e) - } -} - -impl From for BuildError { - fn from(e: vk::swapchain::CapabilitiesError) -> Self { - BuildError::SwapchainCapabilities(e) + match *self { + BuildError::NoAvailableAdapter => write!(f, "no available wgpu adapter detected"), + BuildError::WinitOsError(ref e) => e.fmt(f), + } } } -impl From for BuildError { - fn from(e: frame::RenderDataCreationError) -> Self { - BuildError::RenderDataCreation(e) +impl From for BuildError { + fn from(e: winit::error::OsError) -> Self { + BuildError::WinitOsError(e) } }