From 0b547fe6296290eea99684a5f31be212994fdc9c Mon Sep 17 00:00:00 2001 From: hakolao Date: Thu, 16 Jun 2022 17:51:41 -0400 Subject: [PATCH 01/22] Init vulkano util library --- Cargo.toml | 2 +- vulkano-util/Cargo.toml | 15 +++ vulkano-util/LICENSE-APACHE | 201 +++++++++++++++++++++++++++++ vulkano-util/LICENSE-MIT | 25 ++++ vulkano-util/src/context.rs | 249 ++++++++++++++++++++++++++++++++++++ vulkano-util/src/lib.rs | 2 + 6 files changed, 493 insertions(+), 1 deletion(-) create mode 100644 vulkano-util/Cargo.toml create mode 100644 vulkano-util/LICENSE-APACHE create mode 100644 vulkano-util/LICENSE-MIT create mode 100644 vulkano-util/src/context.rs create mode 100644 vulkano-util/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 21ccc89948..b902d288a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["examples", "vulkano", "vulkano-shaders", "vulkano-win"] +members = ["examples", "vulkano", "vulkano-shaders", "vulkano-win", "vulkano-util"] exclude = ["www"] diff --git a/vulkano-util/Cargo.toml b/vulkano-util/Cargo.toml new file mode 100644 index 0000000000..2fe3688f02 --- /dev/null +++ b/vulkano-util/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "vulkano-util" +version = "0.29.0" +edition = "2021" +authors = ["The vulkano contributors"] +repository = "https://github.com/vulkano-rs/vulkano" +description = "Utility functionality to make usage of Vulkano easier" +license = "MIT/Apache-2.0" +documentation = "https://docs.rs/vulkano" +homepage = "https://vulkano.rs" +keywords = ["vulkan", "bindings", "graphics", "gpu", "rendering"] +categories = ["rendering::graphics-api"] + +[dependencies] +vulkano = { version = "0.29.0", path = "../vulkano" } diff --git a/vulkano-util/LICENSE-APACHE b/vulkano-util/LICENSE-APACHE new file mode 100644 index 0000000000..11069edd79 --- /dev/null +++ b/vulkano-util/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vulkano-util/LICENSE-MIT b/vulkano-util/LICENSE-MIT new file mode 100644 index 0000000000..d9e1b8fa45 --- /dev/null +++ b/vulkano-util/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2016 The Vulkano Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vulkano-util/src/context.rs b/vulkano-util/src/context.rs new file mode 100644 index 0000000000..3b89fa0ce6 --- /dev/null +++ b/vulkano-util/src/context.rs @@ -0,0 +1,249 @@ +use std::sync::Arc; +use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType}; +use vulkano::device::{ + Device, DeviceCreateInfo, DeviceExtensions, Features, Queue, QueueCreateInfo, +}; +use vulkano::instance::debug::{DebugUtilsMessenger, DebugUtilsMessengerCreateInfo}; +use vulkano::instance::{Instance, InstanceCreateInfo}; +use vulkano::Version; + +pub struct VulkanoConfig { + instance_create_info: InstanceCreateInfo, + debug_create_info: Option, + device_priority_fn: fn(device_type: PhysicalDeviceType) -> u32, + device_extensions: DeviceExtensions, + device_features: Features, + print_device_name: bool, +} + +impl Default for VulkanoConfig { + fn default() -> Self { + VulkanoConfig { + instance_create_info: InstanceCreateInfo { + application_version: Version::V1_2, + ..Default::default() + }, + debug_create_info: None, + device_priority_fn: |p| match p { + PhysicalDeviceType::DiscreteGpu => 1, + PhysicalDeviceType::IntegratedGpu => 2, + PhysicalDeviceType::VirtualGpu => 3, + PhysicalDeviceType::Cpu => 4, + PhysicalDeviceType::Other => 5, + }, + print_device_name: true, + device_extensions: DeviceExtensions::none(), + device_features: Features::none(), + } + } +} + +/// VulkanoContext is a utility struct to create and access Vulkano device(s), queues and so on. +/// ## Example +/// +/// ``` +/// use vulkano_util::context::{VulkanoConfig, VulkanoContext}; +/// +/// #[test] +/// fn test() { +/// let context = VulkanoContext::new(VulkanoConfig::default()); +/// } +/// ``` +pub struct VulkanoContext { + instance: Arc, + _debug_utils_messenger: Option, + device: Arc, + graphics_queue: Arc, + compute_queue: Arc, +} + +unsafe impl Sync for VulkanoContext {} + +unsafe impl Send for VulkanoContext {} + +impl Default for VulkanoContext { + fn default() -> Self { + VulkanoContext::new(VulkanoConfig::default()) + } +} + +impl VulkanoContext { + /// Creates a new `VulkanoContext`. + /// + /// # Panics + /// + /// - Panics where the underlying Vulkano struct creations fail + pub fn new(mut config: VulkanoConfig) -> Self { + // Create instance + let instance = create_instance(config.instance_create_info); + // Create debug callback + let _debug_utils_messenger = if let Some(dbg_create_info) = config.debug_create_info.take() + { + Some(unsafe { + DebugUtilsMessenger::new(instance.clone(), dbg_create_info) + .expect("Failed to create debug callback") + }) + } else { + None + }; + // Get prioritized device + let physical_device = PhysicalDevice::enumerate(&instance) + .min_by_key(|p| (config.device_priority_fn)(p.properties().device_type)) + .expect("Failed to create physical device"); + // Print used device + if config.print_device_name { + println!( + "Using device {}, type: {:?}", + physical_device.properties().device_name, + physical_device.properties().device_type, + ); + } + + // Create device + let (device, graphics_queue, compute_queue) = Self::create_device( + physical_device, + config.device_extensions, + config.device_features, + ); + + Self { + instance, + _debug_utils_messenger, + device, + graphics_queue, + compute_queue, + } + } + + /// Creates vulkan device with required queue families and required extensions. Returns an optional secondary queue for compute. + /// However, typically you can just use the gfx queue for that. + fn create_device( + physical: PhysicalDevice, + device_extensions: DeviceExtensions, + features: Features, + ) -> (Arc, Arc, Arc) { + let (gfx_index, queue_family_graphics) = physical + .queue_families() + .enumerate() + .find(|&(_i, q)| q.supports_graphics()) + .expect("Could not find a queue that supports graphics"); + let compute_family_data = physical + .queue_families() + .enumerate() + .find(|&(i, q)| i != gfx_index && q.supports_compute()); + + // If we have an extra compute queue: + if let Some((_compute_index, queue_family_compute)) = compute_family_data { + let (device, mut queues) = { + Device::new( + physical, + DeviceCreateInfo { + enabled_extensions: physical + .required_extensions() + .union(&device_extensions), + enabled_features: features, + queue_create_infos: vec![ + QueueCreateInfo::family(queue_family_graphics), + QueueCreateInfo::family(queue_family_compute), + ], + ..Default::default() + }, + ) + .expect("Failed to create device") + }; + let gfx_queue = queues.next().unwrap(); + let compute_queue = queues.next().unwrap(); + (device, gfx_queue, compute_queue) + } + // And if we do not have an extra compute queue, just use the same queue for gfx and compute + else { + let (device, mut queues) = { + Device::new( + physical, + DeviceCreateInfo { + enabled_extensions: physical + .required_extensions() + .union(&device_extensions), + enabled_features: features, + queue_create_infos: vec![QueueCreateInfo::family(queue_family_graphics)], + ..Default::default() + }, + ) + .expect("Failed to create device") + }; + let gfx_queue = queues.next().unwrap(); + let compute_queue = gfx_queue.clone(); + (device, gfx_queue, compute_queue) + } + } + + /// Check device name + pub fn device_name(&self) -> &str { + &self.device.physical_device().properties().device_name + } + + /// Check device type + pub fn device_type(&self) -> PhysicalDeviceType { + self.device.physical_device().properties().device_type + } + + /// Check device memory count + pub fn max_memory(&self) -> u32 { + self.device + .physical_device() + .properties() + .max_memory_allocation_count as u32 + } + + /// Access instance + pub fn instance(&self) -> Arc { + self.instance.clone() + } + + /// Access device + pub fn device(&self) -> Arc { + self.device.clone() + } + + /// Access rendering queue + pub fn graphics_queue(&self) -> Arc { + self.graphics_queue.clone() + } + + /// Access compute queue. Depending on your device, this might be the same as graphics queue. + pub fn compute_queue(&self) -> Arc { + self.compute_queue.clone() + } +} + +/// Create instance, but remind user to install vulkan SDK on mac os if loading error is received on that platform. +fn create_instance(instance_create_info: InstanceCreateInfo) -> Arc { + #[cfg(target_os = "macos")] + { + match Instance::new(instance_create_info) { + Err(e) => match e { + InstanceCreationError::LoadingError(le) => { + Err(le).expect("Failed to create instance. Did you install vulkanSDK from https://vulkan.lunarg.com/sdk/home ?") + } + _ => Err(e).expect("Failed to create instance"), + }, + Ok(i) => i, + } + } + #[cfg(not(target_os = "macos"))] + { + Instance::new(instance_create_info).expect("Failed to create instance") + } +} + +#[cfg(test)] +mod tests { + use crate::context::{VulkanoConfig, VulkanoContext}; + + // Simply test creation of the context... + #[test] + fn test_creation() { + let context = VulkanoContext::new(VulkanoConfig::default()); + assert_ne!(context.max_memory(), 0); + } +} diff --git a/vulkano-util/src/lib.rs b/vulkano-util/src/lib.rs new file mode 100644 index 0000000000..75a6a671c8 --- /dev/null +++ b/vulkano-util/src/lib.rs @@ -0,0 +1,2 @@ +#![allow(dead_code)] +pub mod context; From 8ea653e873ab67174c33160bb3362840079264be Mon Sep 17 00:00:00 2001 From: hakolao Date: Mon, 20 Jun 2022 16:35:52 -0400 Subject: [PATCH 02/22] Add window & renderer structs --- vulkano-util/Cargo.toml | 2 + vulkano-util/src/context.rs | 9 + vulkano-util/src/lib.rs | 12 ++ vulkano-util/src/renderer.rs | 340 ++++++++++++++++++++++++++++++ vulkano-util/src/window.rs | 392 +++++++++++++++++++++++++++++++++++ 5 files changed, 755 insertions(+) create mode 100644 vulkano-util/src/renderer.rs create mode 100644 vulkano-util/src/window.rs diff --git a/vulkano-util/Cargo.toml b/vulkano-util/Cargo.toml index 2fe3688f02..27f654cb46 100644 --- a/vulkano-util/Cargo.toml +++ b/vulkano-util/Cargo.toml @@ -13,3 +13,5 @@ categories = ["rendering::graphics-api"] [dependencies] vulkano = { version = "0.29.0", path = "../vulkano" } +vulkano-win = { version = "0.29.0", path = "../vulkano-win" } + winit = { version = "0.26" } \ No newline at end of file diff --git a/vulkano-util/src/context.rs b/vulkano-util/src/context.rs index 3b89fa0ce6..f191641631 100644 --- a/vulkano-util/src/context.rs +++ b/vulkano-util/src/context.rs @@ -1,3 +1,12 @@ +// Copyright (c) 2022 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + use std::sync::Arc; use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType}; use vulkano::device::{ diff --git a/vulkano-util/src/lib.rs b/vulkano-util/src/lib.rs index 75a6a671c8..08fd9290ff 100644 --- a/vulkano-util/src/lib.rs +++ b/vulkano-util/src/lib.rs @@ -1,2 +1,14 @@ +// Copyright (c) 2022 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + #![allow(dead_code)] + pub mod context; +pub mod renderer; +pub mod window; diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs new file mode 100644 index 0000000000..9b28a9e667 --- /dev/null +++ b/vulkano-util/src/renderer.rs @@ -0,0 +1,340 @@ +// Copyright (c) 2022 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::collections::HashMap; +use std::sync::Arc; + +use crate::context::VulkanoContext; +use crate::window::WindowDescriptor; +use vulkano::device::Device; +use vulkano::image::{ImageCreateFlags, ImageDimensions, ImageUsage, StorageImage, SwapchainImage}; +use vulkano::{ + device::Queue, + format::Format, + image::{view::ImageView, ImageAccess, ImageViewAbstract}, + swapchain, + swapchain::{AcquireError, Surface, Swapchain, SwapchainCreateInfo, SwapchainCreationError}, + sync, + sync::{FlushError, GpuFuture}, +}; +use vulkano_win::create_surface_from_winit; +use winit::window::Window; + +/// Final render target to which you draw and then display on the window +pub type SwapchainImageView = Arc>>; +/// Multipurpose image view +pub type DeviceImageView = Arc>; + +/// Most common image format +pub const DEFAULT_IMAGE_FORMAT: Format = Format::R8G8B8A8_UNORM; + +pub struct VulkanoWindowRenderer { + surface: Arc>, + graphics_queue: Arc, + compute_queue: Arc, + swap_chain: Arc>, + final_views: Vec, + /// Additional image views that you can add which are resized with the window. + additional_image_views: HashMap, + recreate_swapchain: bool, + previous_frame_end: Option>, + image_index: usize, +} + +unsafe impl Sync for VulkanoWindowRenderer {} + +unsafe impl Send for VulkanoWindowRenderer {} + +impl VulkanoWindowRenderer { + /// Creates a new `VulkanoWindowRenderer` which is used to orchestrate your rendering with Vulkano. + pub fn new( + vulkano_context: &VulkanoContext, + window: winit::window::Window, + descriptor: &WindowDescriptor, + swapchain_create_info_overrides: SwapchainCreateInfo, + ) -> VulkanoWindowRenderer { + // Create rendering surface from window + let surface = create_surface_from_winit(window, vulkano_context.instance()).unwrap(); + + // Create swap chain & frame(s) to which we'll render + let (swap_chain, final_views) = Self::create_swap_chain( + vulkano_context.device(), + surface.clone(), + SwapchainCreateInfo { + present_mode: descriptor.present_mode, + ..swapchain_create_info_overrides + }, + ); + + let previous_frame_end = Some(sync::now(vulkano_context.device()).boxed()); + + VulkanoWindowRenderer { + surface, + graphics_queue: vulkano_context.graphics_queue(), + compute_queue: vulkano_context.compute_queue(), + swap_chain, + final_views, + additional_image_views: HashMap::default(), + recreate_swapchain: false, + previous_frame_end, + image_index: 0, + } + } + + /// Creates the swapchain and its images + fn create_swap_chain( + device: Arc, + surface: Arc>, + swapchain_create_info_overrides: SwapchainCreateInfo, + ) -> (Arc>, Vec) { + let surface_capabilities = device + .physical_device() + .surface_capabilities(&surface, Default::default()) + .unwrap(); + let image_format = Some( + device + .physical_device() + .surface_formats(&surface, Default::default()) + .unwrap()[0] + .0, + ); + let image_extent = surface.window().inner_size().into(); + let (swapchain, images) = Swapchain::new( + device, + surface, + SwapchainCreateInfo { + min_image_count: surface_capabilities.min_image_count, + image_format, + image_extent, + image_usage: ImageUsage::color_attachment(), + composite_alpha: surface_capabilities + .supported_composite_alpha + .iter() + .next() + .unwrap(), + ..swapchain_create_info_overrides + }, + ) + .unwrap(); + let images = images + .into_iter() + .map(|image| ImageView::new_default(image).unwrap()) + .collect::>(); + (swapchain, images) + } + + /// Return swapchain image format + pub fn swapchain_format(&self) -> Format { + self.final_views[self.image_index].format().unwrap() + } + + /// Returns the index of last swapchain image that is the next render target + pub fn image_index(&self) -> usize { + self.image_index + } + + /// Graphics queue of this window. You also can access this through VulkanoContext + pub fn graphics_queue(&self) -> Arc { + self.graphics_queue.clone() + } + + /// Compute queue of this window. You can also access this through VulkanoContext + pub fn compute_queue(&self) -> Arc { + self.compute_queue.clone() + } + + /// Render target surface + pub fn surface(&self) -> Arc> { + self.surface.clone() + } + + /// Winit window (you can manipulate window through this). + pub fn window(&self) -> &Window { + self.surface.window() + } + + /// Size of the physical window + pub fn window_size(&self) -> [f32; 2] { + let size = self.window().inner_size(); + [size.width as f32, size.height as f32] + } + + /// Size of the final swapchain image (surface) + pub fn swapchain_image_size(&self) -> [u32; 2] { + self.final_views[0].image().dimensions().width_height() + } + + /// Return the current swapchain image view + pub fn swapchain_image_view(&self) -> SwapchainImageView { + self.final_views[self.image_index].clone() + } + + /// Return scale factor accounted window size + pub fn resolution(&self) -> [f32; 2] { + let size = self.window().inner_size(); + let scale_factor = self.window().scale_factor(); + [ + (size.width as f64 / scale_factor) as f32, + (size.height as f64 / scale_factor) as f32, + ] + } + + pub fn aspect_ratio(&self) -> f32 { + let dims = self.window_size(); + dims[0] / dims[1] + } + + /// Resize swapchain and camera view images at the beginning of next frame + pub fn resize(&mut self) { + self.recreate_swapchain = true; + } + + /// Add interim image view that resizes with window + pub fn add_additional_image_view(&mut self, key: usize, format: Format, usage: ImageUsage) { + let size = self.swapchain_image_size(); + let image = + create_device_image_with_usage(self.graphics_queue.clone(), size, format, usage); + self.additional_image_views.insert(key, image); + } + + /// Get additional image view by key + pub fn get_additional_image_view(&mut self, key: usize) -> DeviceImageView { + self.additional_image_views.get(&key).unwrap().clone() + } + + /// Remove additional image by key + pub fn remove_additional_image_view(&mut self, key: usize) { + self.additional_image_views.remove(&key); + } + + /// This is the first to call in render orchestration. + /// Returns a gpu future representing the time after which the swapchain image has been acquired + /// and previous frame ended. + /// After calling this, you should execute your command buffers and pass your last future to `finish_frame`. + pub fn start_frame(&mut self) -> std::result::Result, AcquireError> { + // Recreate swap chain if needed (when resizing of window occurs or swapchain is outdated) + // Also resize render views if needed + if self.recreate_swapchain { + self.recreate_swapchain_and_views(); + } + + // Acquire next image in the swapchain + let (image_num, suboptimal, acquire_future) = + match swapchain::acquire_next_image(self.swap_chain.clone(), None) { + Ok(r) => r, + Err(AcquireError::OutOfDate) => { + self.recreate_swapchain = true; + return Err(AcquireError::OutOfDate); + } + Err(e) => panic!("Failed to acquire next image: {:?}", e), + }; + if suboptimal { + self.recreate_swapchain = true; + } + // Update our image index + self.image_index = image_num; + + let future = self.previous_frame_end.take().unwrap().join(acquire_future); + + Ok(future.boxed()) + } + + /// Finishes render by presenting the swapchain. Pass your last future as an input to this function. + pub fn finish_frame(&mut self, after_future: Box) { + let future = after_future + .then_swapchain_present( + self.graphics_queue.clone(), + self.swap_chain.clone(), + self.image_index, + ) + .then_signal_fence_and_flush(); + match future { + Ok(future) => { + // Prevent OutOfMemory error on Nvidia :( + // https://github.com/vulkano-rs/vulkano/issues/627. + // Maybe there's some way to prevent this with synchronization... + match future.wait(None) { + Ok(x) => x, + Err(err) => println!("{:?}", err), + } + self.previous_frame_end = Some(future.boxed()); + } + Err(FlushError::OutOfDate) => { + self.recreate_swapchain = true; + self.previous_frame_end = + Some(sync::now(self.graphics_queue.device().clone()).boxed()); + } + Err(e) => { + println!("Failed to flush future: {:?}", e); + self.previous_frame_end = + Some(sync::now(self.graphics_queue.device().clone()).boxed()); + } + } + } + + /// Recreates swapchain images and image views which follow the window size + fn recreate_swapchain_and_views(&mut self) { + let dimensions: [u32; 2] = self.window().inner_size().into(); + let (new_swapchain, new_images) = match self.swap_chain.recreate(SwapchainCreateInfo { + image_extent: dimensions, + ..self.swap_chain.create_info() + }) { + Ok(r) => r, + Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, + Err(e) => panic!("Failed to recreate swapchain: {:?}", e), + }; + + self.swap_chain = new_swapchain; + let new_images = new_images + .into_iter() + .map(|image| ImageView::new_default(image).unwrap()) + .collect::>(); + self.final_views = new_images; + // Resize images that follow swapchain size + let resizable_views = self + .additional_image_views + .iter() + .map(|c| *c.0) + .collect::>(); + for i in resizable_views { + let format = self.get_additional_image_view(i).format().unwrap(); + let usage = self.get_additional_image_view(i).usage().clone(); + self.remove_additional_image_view(i); + self.add_additional_image_view(i, format, usage); + } + self.recreate_swapchain = false; + } +} + +/// A simple utility function to create storage image views with usage +pub fn create_device_image_with_usage( + queue: Arc, + size: [u32; 2], + format: Format, + usage: ImageUsage, +) -> DeviceImageView { + let dims = ImageDimensions::Dim2d { + width: size[0], + height: size[1], + array_layers: 1, + }; + let flags = ImageCreateFlags::none(); + ImageView::new_default( + StorageImage::with_usage( + queue.device().clone(), + dims, + format, + usage, + flags, + Some(queue.family()), + ) + .unwrap(), + ) + .unwrap() +} diff --git a/vulkano-util/src/window.rs b/vulkano-util/src/window.rs new file mode 100644 index 0000000000..56a6544757 --- /dev/null +++ b/vulkano-util/src/window.rs @@ -0,0 +1,392 @@ +// Mostly a copy from https://github.com/bevyengine/bevy/tree/main/crates/bevy_window, modified to fit Vulkano. + +// Copyright (c) 2022 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::context::VulkanoContext; +use crate::renderer::VulkanoWindowRenderer; +use std::collections::HashMap; +use vulkano::swapchain::{PresentMode, SwapchainCreateInfo}; +use winit::dpi::LogicalSize; + +/// A struct organizing windows and their corresponding renderers. This makes it easy to handle multiple windows. +/// +/// ## Example +///``` +/// use vulkano_util::context::{VulkanoConfig, VulkanoContext}; +/// use winit::event_loop::EventLoop; +/// use vulkano_util::window::VulkanoWindows; +/// +/// #[test] +/// fn test() { +/// let context = VulkanoContext::new(VulkanoConfig::default()); +/// let event_loop = EventLoop::new(); +/// let mut vulkano_windows = VulkanoWindows::default(); +/// vulkano_windows.create_window(&event_loop, &context, &Default::default(), Default::default()); +/// vulkano_windows.create_window(&event_loop, &context, &Default::default(), Default::default()); +/// // You should now have two windows +/// } +/// ``` +#[derive(Default)] +pub struct VulkanoWindows { + windows: HashMap, + primary: Option, +} + +impl VulkanoWindows { + pub fn create_window( + &mut self, + event_loop: &winit::event_loop::EventLoopWindowTarget<()>, + vulkano_context: &VulkanoContext, + window_descriptor: &WindowDescriptor, + swapchain_create_info_overriders: SwapchainCreateInfo, + ) { + #[cfg(target_os = "windows")] + let mut winit_window_builder = { + use winit::platform::windows::WindowBuilderExtWindows; + winit::window::WindowBuilder::new().with_drag_and_drop(false) + }; + + #[cfg(not(target_os = "windows"))] + let mut winit_window_builder = winit::window::WindowBuilder::new(); + + winit_window_builder = match window_descriptor.mode { + WindowMode::BorderlessFullscreen => winit_window_builder.with_fullscreen(Some( + winit::window::Fullscreen::Borderless(event_loop.primary_monitor()), + )), + WindowMode::Fullscreen => { + winit_window_builder.with_fullscreen(Some(winit::window::Fullscreen::Exclusive( + get_best_videomode(&event_loop.primary_monitor().unwrap()), + ))) + } + WindowMode::SizedFullscreen => winit_window_builder.with_fullscreen(Some( + winit::window::Fullscreen::Exclusive(get_fitting_videomode( + &event_loop.primary_monitor().unwrap(), + window_descriptor.width as u32, + window_descriptor.height as u32, + )), + )), + _ => { + let WindowDescriptor { + width, + height, + position, + scale_factor_override, + .. + } = window_descriptor; + + if let Some(position) = position { + if let Some(sf) = scale_factor_override { + winit_window_builder = winit_window_builder.with_position( + winit::dpi::LogicalPosition::new( + position[0] as f64, + position[1] as f64, + ) + .to_physical::(*sf), + ); + } else { + winit_window_builder = + winit_window_builder.with_position(winit::dpi::LogicalPosition::new( + position[0] as f64, + position[1] as f64, + )); + } + } + if let Some(sf) = scale_factor_override { + winit_window_builder.with_inner_size( + winit::dpi::LogicalSize::new(*width, *height).to_physical::(*sf), + ) + } else { + winit_window_builder + .with_inner_size(winit::dpi::LogicalSize::new(*width, *height)) + } + } + .with_resizable(window_descriptor.resizable) + .with_decorations(window_descriptor.decorations) + .with_transparent(window_descriptor.transparent), + }; + + let constraints = window_descriptor.resize_constraints.check_constraints(); + let min_inner_size = LogicalSize { + width: constraints.min_width, + height: constraints.min_height, + }; + let max_inner_size = LogicalSize { + width: constraints.max_width, + height: constraints.max_height, + }; + + let winit_window_builder = + if constraints.max_width.is_finite() && constraints.max_height.is_finite() { + winit_window_builder + .with_min_inner_size(min_inner_size) + .with_max_inner_size(max_inner_size) + } else { + winit_window_builder.with_min_inner_size(min_inner_size) + }; + + #[allow(unused_mut)] + let mut winit_window_builder = winit_window_builder.with_title(&window_descriptor.title); + + let winit_window = winit_window_builder.build(event_loop).unwrap(); + + if window_descriptor.cursor_locked { + match winit_window.set_cursor_grab(true) { + Ok(_) => {} + Err(winit::error::ExternalError::NotSupported(_)) => {} + Err(err) => Err(err).unwrap(), + } + } + + winit_window.set_cursor_visible(window_descriptor.cursor_visible); + + if self.primary.is_none() { + self.primary = Some(winit_window.id()); + } + + self.windows.insert( + winit_window.id(), + VulkanoWindowRenderer::new( + vulkano_context, + winit_window, + window_descriptor, + swapchain_create_info_overriders, + ), + ); + } + + /// Get a mutable reference to the primary window's renderer + pub fn get_primary_renderer_mut(&mut self) -> Option<&mut VulkanoWindowRenderer> { + if self.primary.is_some() { + self.get_renderer_mut(self.primary.unwrap()) + } else { + None + } + } + + /// Get a reference to the primary window's renderer + pub fn get_primary_renderer(&self) -> Option<&VulkanoWindowRenderer> { + if self.primary.is_some() { + self.get_renderer(self.primary.unwrap()) + } else { + None + } + } + + /// Get a reference to the primary winit window + pub fn get_primary_window(&self) -> Option<&winit::window::Window> { + if self.primary.is_some() { + self.get_window(self.primary.unwrap()) + } else { + None + } + } + + /// Get a mutable reference to the renderer by winit window id + pub fn get_renderer_mut( + &mut self, + id: winit::window::WindowId, + ) -> Option<&mut VulkanoWindowRenderer> { + self.windows.get_mut(&id).and_then(|v| Some(v)) + } + + /// Get a reference to the renderer by winit window id + pub fn get_renderer(&self, id: winit::window::WindowId) -> Option<&VulkanoWindowRenderer> { + self.windows.get(&id).and_then(|v| Some(v)) + } + + /// Get a reference to the winit window by winit window id + pub fn get_window(&self, id: winit::window::WindowId) -> Option<&winit::window::Window> { + self.windows + .get(&id) + .and_then(|v_window| Some(v_window.window())) + } +} + +pub fn get_fitting_videomode( + monitor: &winit::monitor::MonitorHandle, + width: u32, + height: u32, +) -> winit::monitor::VideoMode { + let mut modes = monitor.video_modes().collect::>(); + + fn abs_diff(a: u32, b: u32) -> u32 { + if a > b { + return a - b; + } + b - a + } + + modes.sort_by(|a, b| { + use std::cmp::Ordering::*; + match abs_diff(a.size().width, width).cmp(&abs_diff(b.size().width, width)) { + Equal => { + match abs_diff(a.size().height, height).cmp(&abs_diff(b.size().height, height)) { + Equal => b.refresh_rate().cmp(&a.refresh_rate()), + default => default, + } + } + default => default, + } + }); + + modes.first().unwrap().clone() +} + +pub fn get_best_videomode(monitor: &winit::monitor::MonitorHandle) -> winit::monitor::VideoMode { + let mut modes = monitor.video_modes().collect::>(); + modes.sort_by(|a, b| { + use std::cmp::Ordering::*; + match b.size().width.cmp(&a.size().width) { + Equal => match b.size().height.cmp(&a.size().height) { + Equal => b.refresh_rate().cmp(&a.refresh_rate()), + default => default, + }, + default => default, + } + }); + + modes.first().unwrap().clone() +} + +/// Defines the way a window is displayed. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum WindowMode { + /// Creates a window that uses the given size. + Windowed, + /// Creates a borderless window that uses the full size of the screen. + BorderlessFullscreen, + /// Creates a fullscreen window that will render at desktop resolution. + /// + /// The app will use the closest supported size from the given size and scale it to fit the screen. + SizedFullscreen, + /// Creates a fullscreen window that uses the maximum supported size. + Fullscreen, +} + +/// Describes the information needed for creating a window. +#[derive(Debug, Clone)] +pub struct WindowDescriptor { + /// The requested logical width of the window's client area. + /// + /// May vary from the physical width due to different pixel density on different monitors. + pub width: f32, + /// The requested logical height of the window's client area. + /// + /// May vary from the physical height due to different pixel density on different monitors. + pub height: f32, + /// The position on the screen that the window will be centered at. + /// + /// If set to `None`, some platform-specific position will be chosen. + pub position: Option<[f32; 2]>, + /// Sets minimum and maximum resize limits. + pub resize_constraints: WindowResizeConstraints, + /// Overrides the window's ratio of physical pixels to logical pixels. + /// + /// If there are some scaling problems on X11 try to set this option to `Some(1.0)`. + pub scale_factor_override: Option, + /// Sets the title that displays on the window top bar, on the system task bar and other OS specific places. + pub title: String, + /// The window's [`PresentMode`]. + /// + /// Used to select whether or not VSync is used + pub present_mode: PresentMode, + /// Sets whether the window is resizable. + pub resizable: bool, + /// Sets whether the window should have borders and bars. + pub decorations: bool, + /// Sets whether the cursor is visible when the window has focus. + pub cursor_visible: bool, + /// Sets whether the window locks the cursor inside its borders when the window has focus. + pub cursor_locked: bool, + /// Sets the [`WindowMode`](crate::WindowMode). + pub mode: WindowMode, + /// Sets whether the background of the window should be transparent. + pub transparent: bool, +} + +impl Default for WindowDescriptor { + fn default() -> Self { + WindowDescriptor { + title: "Vulkano App".to_string(), + width: 1280., + height: 720., + position: None, + resize_constraints: WindowResizeConstraints::default(), + scale_factor_override: None, + present_mode: PresentMode::Fifo, + resizable: true, + decorations: true, + cursor_locked: false, + cursor_visible: true, + mode: WindowMode::Windowed, + transparent: false, + } + } +} + +/// The size limits on a window. +/// +/// These values are measured in logical pixels, so the user's +/// scale factor does affect the size limits on the window. +/// Please note that if the window is resizable, then when the window is +/// maximized it may have a size outside of these limits. The functionality +/// required to disable maximizing is not yet exposed by winit. +#[derive(Debug, Clone, Copy)] +pub struct WindowResizeConstraints { + pub min_width: f32, + pub min_height: f32, + pub max_width: f32, + pub max_height: f32, +} + +impl Default for WindowResizeConstraints { + fn default() -> Self { + Self { + min_width: 180., + min_height: 120., + max_width: f32::INFINITY, + max_height: f32::INFINITY, + } + } +} + +impl WindowResizeConstraints { + #[must_use] + pub fn check_constraints(&self) -> Self { + let WindowResizeConstraints { + mut min_width, + mut min_height, + mut max_width, + mut max_height, + } = self; + min_width = min_width.max(1.); + min_height = min_height.max(1.); + if max_width < min_width { + println!( + "The given maximum width {} is smaller than the minimum width {}", + max_width, min_width + ); + max_width = min_width; + } + if max_height < min_height { + println!( + "The given maximum height {} is smaller than the minimum height {}", + max_height, min_height + ); + max_height = min_height; + } + WindowResizeConstraints { + min_width, + min_height, + max_width, + max_height, + } + } +} From d758186286d414acb97946776247c5751753502e Mon Sep 17 00:00:00 2001 From: hakolao Date: Mon, 20 Jun 2022 17:21:34 -0400 Subject: [PATCH 03/22] Update fractal to use vulkano utils --- examples/Cargo.toml | 1 + examples/src/bin/interactive_fractal/app.rs | 40 +- .../fractal_compute_pipeline.rs | 24 +- examples/src/bin/interactive_fractal/main.rs | 76 ++- .../interactive_fractal/place_over_frame.rs | 10 +- .../src/bin/interactive_fractal/renderer.rs | 472 ------------------ vulkano-util/src/context.rs | 6 +- vulkano-util/src/renderer.rs | 27 +- vulkano-util/src/window.rs | 8 +- 9 files changed, 116 insertions(+), 548 deletions(-) delete mode 100644 examples/src/bin/interactive_fractal/renderer.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a2cdcc06f7..2a9308b096 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -16,6 +16,7 @@ winit = "0.26" # The `vulkano_win` crate is the link between `vulkano` and `winit`. Vulkano doesn't know about winit, # and winit doesn't know about vulkano, so import a crate that will provide a link between the two. vulkano-win = { path = "../vulkano-win" } +vulkano-util = { path = "../vulkano-util" } bytemuck = { version = "1.7", features = ["derive", "extern_crate_std", "min_const_generics"] } cgmath = "0.18" diff --git a/examples/src/bin/interactive_fractal/app.rs b/examples/src/bin/interactive_fractal/app.rs index 13ca21380b..66947a4080 100644 --- a/examples/src/bin/interactive_fractal/app.rs +++ b/examples/src/bin/interactive_fractal/app.rs @@ -7,13 +7,16 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use crate::{ - fractal_compute_pipeline::FractalComputePipeline, - renderer::{InterimImageView, RenderOptions, Renderer}, -}; +use crate::fractal_compute_pipeline::FractalComputePipeline; +use crate::place_over_frame::RenderPassPlaceOverFrame; use cgmath::Vector2; +use std::sync::Arc; use std::time::Instant; +use vulkano::device::Queue; use vulkano::sync::GpuFuture; +use vulkano_util::renderer::{DeviceImageView, VulkanoWindowRenderer}; +use vulkano_util::window::WindowDescriptor; +use winit::window::Fullscreen; use winit::{ dpi::PhysicalPosition, event::{ @@ -29,6 +32,8 @@ const MOVE_SPEED: f32 = 0.5; pub struct FractalApp { /// Pipeline that computes Mandelbrot & Julia fractals and writes them to an image fractal_pipeline: FractalComputePipeline, + /// Our render pipeline (pass) + pub place_over_frame: RenderPassPlaceOverFrame, /// Toggle that flips between julia and mandelbrot pub is_julia: bool, /// Togglet thats stops the movement on Julia @@ -52,9 +57,10 @@ pub struct FractalApp { } impl FractalApp { - pub fn new(renderer: &Renderer) -> FractalApp { + pub fn new(gfx_queue: Arc, image_format: vulkano::format::Format) -> FractalApp { FractalApp { - fractal_pipeline: FractalComputePipeline::new(renderer.queue()), + fractal_pipeline: FractalComputePipeline::new(gfx_queue.clone()), + place_over_frame: RenderPassPlaceOverFrame::new(gfx_queue, image_format), is_julia: false, is_c_paused: false, c: Vector2::new(0.0, 0.0), @@ -87,7 +93,7 @@ Usage: } /// Run our compute pipeline and return a future of when the compute is finished - pub fn compute(&mut self, image_target: InterimImageView) -> Box { + pub fn compute(&mut self, image_target: DeviceImageView) -> Box { self.fractal_pipeline.compute( image_target, self.c, @@ -128,7 +134,7 @@ Usage: } /// Updates app state based on input state - pub fn update_state_after_inputs(&mut self, renderer: &mut Renderer) { + pub fn update_state_after_inputs(&mut self, renderer: &mut VulkanoWindowRenderer) { // Zoom in or out if self.input_state.scroll_delta > 0. { self.scale /= 1.05; @@ -182,12 +188,17 @@ Usage: } // Toggle full-screen if self.input_state.toggle_full_screen { - renderer.toggle_full_screen() + let is_full_screen = renderer.window().fullscreen().is_some(); + renderer.window().set_fullscreen(if !is_full_screen { + Some(Fullscreen::Borderless(renderer.window().current_monitor())) + } else { + None + }); } } /// Update input state - pub fn handle_input(&mut self, window_size: [u32; 2], event: &Event<()>) { + pub fn handle_input(&mut self, window_size: [f32; 2], event: &Event<()>) { self.input_state.handle_input(window_size, event); } @@ -208,7 +219,7 @@ fn state_is_pressed(state: ElementState) -> bool { /// Winit only has Pressed and Released events, thus continuous movement needs toggles. /// Panning is one of those where continuous movement feels better. struct InputState { - pub window_size: [u32; 2], + pub window_size: [f32; 2], pub pan_up: bool, pub pan_down: bool, pub pan_right: bool, @@ -227,7 +238,10 @@ struct InputState { impl InputState { fn new() -> InputState { InputState { - window_size: RenderOptions::default().window_size, + window_size: [ + WindowDescriptor::default().width, + WindowDescriptor::default().height, + ], pan_up: false, pan_down: false, pan_right: false, @@ -265,7 +279,7 @@ impl InputState { } } - fn handle_input(&mut self, window_size: [u32; 2], event: &Event<()>) { + fn handle_input(&mut self, window_size: [f32; 2], event: &Event<()>) { self.window_size = window_size; if let winit::event::Event::WindowEvent { event, .. } = event { match event { diff --git a/examples/src/bin/interactive_fractal/fractal_compute_pipeline.rs b/examples/src/bin/interactive_fractal/fractal_compute_pipeline.rs index d7aeea80bc..ad63e19b97 100644 --- a/examples/src/bin/interactive_fractal/fractal_compute_pipeline.rs +++ b/examples/src/bin/interactive_fractal/fractal_compute_pipeline.rs @@ -7,7 +7,6 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use crate::renderer::InterimImageView; use cgmath::Vector2; use rand::Rng; use std::sync::Arc; @@ -20,9 +19,10 @@ use vulkano::{ pipeline::{ComputePipeline, Pipeline, PipelineBindPoint}, sync::GpuFuture, }; +use vulkano_util::renderer::DeviceImageView; pub struct FractalComputePipeline { - gfx_queue: Arc, + queue: Arc, pipeline: Arc, palette: Arc>, palette_size: i32, @@ -30,7 +30,7 @@ pub struct FractalComputePipeline { } impl FractalComputePipeline { - pub fn new(gfx_queue: Arc) -> FractalComputePipeline { + pub fn new(queue: Arc) -> FractalComputePipeline { // Initial colors let colors = vec![ [1.0, 0.0, 0.0, 1.0], @@ -42,7 +42,7 @@ impl FractalComputePipeline { ]; let palette_size = colors.len() as i32; let palette = CpuAccessibleBuffer::from_iter( - gfx_queue.device().clone(), + queue.device().clone(), BufferUsage::all(), false, colors, @@ -51,9 +51,9 @@ impl FractalComputePipeline { let end_color = [0.0; 4]; let pipeline = { - let shader = cs::load(gfx_queue.device().clone()).unwrap(); + let shader = cs::load(queue.device().clone()).unwrap(); ComputePipeline::new( - gfx_queue.device().clone(), + queue.device().clone(), shader.entry_point("main").unwrap(), &(), None, @@ -62,7 +62,7 @@ impl FractalComputePipeline { .unwrap() }; FractalComputePipeline { - gfx_queue, + queue, pipeline, palette, palette_size, @@ -81,7 +81,7 @@ impl FractalComputePipeline { colors.push([r, g, b, a]); } self.palette = CpuAccessibleBuffer::from_iter( - self.gfx_queue.device().clone(), + self.queue.device().clone(), BufferUsage::all(), false, colors.into_iter(), @@ -91,7 +91,7 @@ impl FractalComputePipeline { pub fn compute( &mut self, - image: InterimImageView, + image: DeviceImageView, c: Vector2, scale: Vector2, translation: Vector2, @@ -111,8 +111,8 @@ impl FractalComputePipeline { ) .unwrap(); let mut builder = AutoCommandBufferBuilder::primary( - self.gfx_queue.device().clone(), - self.gfx_queue.family(), + self.queue.device().clone(), + self.queue.family(), CommandBufferUsage::OneTimeSubmit, ) .unwrap(); @@ -134,7 +134,7 @@ impl FractalComputePipeline { .dispatch([img_dims[0] / 8, img_dims[1] / 8, 1]) .unwrap(); let command_buffer = builder.build().unwrap(); - let finished = command_buffer.execute(self.gfx_queue.clone()).unwrap(); + let finished = command_buffer.execute(self.queue.clone()).unwrap(); finished.then_signal_fence_and_flush().unwrap().boxed() } } diff --git a/examples/src/bin/interactive_fractal/main.rs b/examples/src/bin/interactive_fractal/main.rs index f7db58c2f5..e3b69c480a 100644 --- a/examples/src/bin/interactive_fractal/main.rs +++ b/examples/src/bin/interactive_fractal/main.rs @@ -7,11 +7,13 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use crate::{ - app::FractalApp, - renderer::{image_over_frame_renderpass, RenderOptions, Renderer}, -}; +use crate::app::FractalApp; +use vulkano::image::ImageUsage; +use vulkano::swapchain::PresentMode; use vulkano::sync::GpuFuture; +use vulkano_util::context::{VulkanoConfig, VulkanoContext}; +use vulkano_util::renderer::{VulkanoWindowRenderer, DEFAULT_IMAGE_FORMAT}; +use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; use winit::{ event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -22,7 +24,6 @@ mod app; mod fractal_compute_pipeline; mod pixels_draw_pipeline; mod place_over_frame; -mod renderer; /// This is an example demonstrating an application with some more non-trivial functionality. /// It should get you more up to speed with how you can use Vulkano. @@ -36,21 +37,39 @@ mod renderer; fn main() { // Create event loop let mut event_loop = EventLoop::new(); - // Create a renderer with a window & render options - let mut renderer = Renderer::new( + let context = VulkanoContext::new(VulkanoConfig::default()); + let mut windows = VulkanoWindows::default(); + windows.create_window( &event_loop, - RenderOptions { - title: "Fractal", - ..RenderOptions::default() + &context, + &WindowDescriptor { + title: "Fractal".to_string(), + present_mode: PresentMode::Immediate, + ..Default::default() }, + |_| {}, ); + // Add our render target image onto which we'll be rendering our fractals. - // View size None here means renderer will keep resizing the image on resize let render_target_id = 0; - renderer.add_interim_image_view(render_target_id, None, renderer.image_format()); + let primary_window_renderer = windows.get_primary_renderer_mut().unwrap(); + // Make sure the image usage is correct (based on your pipeline). + primary_window_renderer.add_additional_image_view( + render_target_id, + DEFAULT_IMAGE_FORMAT, + ImageUsage { + sampled: true, + storage: true, + color_attachment: true, + transfer_dst: true, + ..ImageUsage::none() + }, + ); // Create app to hold the logic of our fractal explorer - let mut app = FractalApp::new(&renderer); + let gfx_queue = context.graphics_queue(); + // We intend to eventually render on our swapchain, thus we use that format when creating the app here. + let mut app = FractalApp::new(gfx_queue, primary_window_renderer.swapchain_format()); app.print_guide(); // Basic loop for our runtime @@ -60,24 +79,24 @@ fn main() { // 4. Reset input state // 5. Update time & title loop { - if !handle_events(&mut event_loop, &mut renderer, &mut app) { + if !handle_events(&mut event_loop, primary_window_renderer, &mut app) { break; } - match renderer.window_size() { + match primary_window_renderer.window_size() { [w, h] => { // Skip this frame when minimized - if w == 0 || h == 0 { + if w == 0.0 || h == 0.0 { continue; } } } - app.update_state_after_inputs(&mut renderer); - compute_then_render(&mut renderer, &mut app, render_target_id); + app.update_state_after_inputs(primary_window_renderer); + compute_then_render(primary_window_renderer, &mut app, render_target_id); app.reset_input_state(); app.update_time(); - renderer.window().set_title(&format!( + primary_window_renderer.window().set_title(&format!( "{} fps: {:.2} dt: {:.2}, Max Iterations: {}", if app.is_julia { "Julia" } else { "Mandelbrot" }, app.avg_fps(), @@ -90,7 +109,7 @@ fn main() { /// Handle events and return `bool` if we should quit fn handle_events( event_loop: &mut EventLoop<()>, - renderer: &mut Renderer, + renderer: &mut VulkanoWindowRenderer, app: &mut FractalApp, ) -> bool { let mut is_running = true; @@ -114,7 +133,11 @@ fn handle_events( } /// Orchestrate rendering here -fn compute_then_render(renderer: &mut Renderer, app: &mut FractalApp, target_image_id: usize) { +fn compute_then_render( + renderer: &mut VulkanoWindowRenderer, + app: &mut FractalApp, + target_image_id: usize, +) { // Start frame let before_pipeline_future = match renderer.start_frame() { Err(e) => { @@ -124,14 +147,13 @@ fn compute_then_render(renderer: &mut Renderer, app: &mut FractalApp, target_ima Ok(future) => future, }; // Retrieve target image - let target_image = renderer.get_interim_image_view(target_image_id); + let image = renderer.get_additional_image_view(target_image_id); // Compute our fractal (writes to target image). Join future with `before_pipeline_future`. - let after_compute = app - .compute(target_image.clone()) - .join(before_pipeline_future); - // Render target image over frame. Input previous future. + let after_compute = app.compute(image.clone()).join(before_pipeline_future); + // Render image over frame. Input previous future. Draw on swapchain image let after_renderpass_future = - image_over_frame_renderpass(renderer, after_compute, target_image); + app.place_over_frame + .render(after_compute, image, renderer.swapchain_image_view()); // Finish frame (which presents the view). Input last future renderer.finish_frame(after_renderpass_future); } diff --git a/examples/src/bin/interactive_fractal/place_over_frame.rs b/examples/src/bin/interactive_fractal/place_over_frame.rs index 47b619e619..a79ac8e2da 100644 --- a/examples/src/bin/interactive_fractal/place_over_frame.rs +++ b/examples/src/bin/interactive_fractal/place_over_frame.rs @@ -7,10 +7,7 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use crate::{ - pixels_draw_pipeline::PixelsDrawPipeline, - renderer::{FinalImageView, InterimImageView}, -}; +use crate::pixels_draw_pipeline::PixelsDrawPipeline; use std::sync::Arc; use vulkano::{ command_buffer::{ @@ -22,6 +19,7 @@ use vulkano::{ render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, sync::GpuFuture, }; +use vulkano_util::renderer::{DeviceImageView, SwapchainImageView}; /// A render pass which places an incoming image over frame filling it pub struct RenderPassPlaceOverFrame { @@ -61,8 +59,8 @@ impl RenderPassPlaceOverFrame { pub fn render( &mut self, before_future: F, - view: InterimImageView, - target: FinalImageView, + view: DeviceImageView, + target: SwapchainImageView, ) -> Box where F: GpuFuture + 'static, diff --git a/examples/src/bin/interactive_fractal/renderer.rs b/examples/src/bin/interactive_fractal/renderer.rs deleted file mode 100644 index 3cd576f457..0000000000 --- a/examples/src/bin/interactive_fractal/renderer.rs +++ /dev/null @@ -1,472 +0,0 @@ -// Copyright (c) 2021 The vulkano developers -// Licensed under the Apache License, Version 2.0 -// or the MIT -// license , -// at your option. All files in the project carrying such -// notice may not be copied, modified, or distributed except -// according to those terms. - -use crate::place_over_frame::RenderPassPlaceOverFrame; -use std::{collections::HashMap, sync::Arc}; -use vulkano::{ - device::{ - physical::{PhysicalDevice, PhysicalDeviceType}, - Device, DeviceCreateInfo, DeviceExtensions, Features, Queue, QueueCreateInfo, - }, - format::Format, - image::{ - view::ImageView, AttachmentImage, ImageAccess, ImageUsage, ImageViewAbstract, SampleCount, - SwapchainImage, - }, - instance::{Instance, InstanceCreateInfo, InstanceExtensions}, - swapchain::{ - acquire_next_image, AcquireError, PresentMode, Surface, Swapchain, SwapchainCreateInfo, - SwapchainCreationError, - }, - sync::{self, FlushError, GpuFuture}, -}; -use vulkano_win::VkSurfaceBuild; -use winit::{ - event_loop::EventLoop, - window::{Fullscreen, Window, WindowBuilder}, -}; - -/// Final render target (swapchain image) -pub type FinalImageView = Arc>>; -/// Other intermediate render targets -pub type InterimImageView = Arc>; - -/// A simple struct to organize renderpasses. -/// You could add more here. E.g. the `frame_system` -/// from the deferred examples... -pub struct RenderPasses { - pub place_over_frame: RenderPassPlaceOverFrame, -} - -#[derive(Debug, Copy, Clone)] -pub struct RenderOptions { - pub title: &'static str, - pub window_size: [u32; 2], - pub v_sync: bool, -} - -impl Default for RenderOptions { - fn default() -> Self { - RenderOptions { - title: "App", - window_size: [1920, 1080], - v_sync: false, - } - } -} - -pub struct Renderer { - _instance: Arc, - device: Arc, - surface: Arc>, - queue: Arc, - swapchain: Arc>, - image_index: usize, - final_views: Vec, - /// Image view that is to be rendered with our pipeline. - /// (bool refers to whether it should get resized with swapchain resize) - interim_image_views: HashMap, - recreate_swapchain: bool, - previous_frame_end: Option>, - render_passes: RenderPasses, - is_full_screen: bool, -} - -impl Renderer { - /// Creates a new GPU renderer for window with given parameters - pub fn new(event_loop: &EventLoop<()>, opts: RenderOptions) -> Self { - println!("Creating renderer for window size {:?}", opts.window_size); - // Add instance extensions based on needs - let instance_extensions = InstanceExtensions { - ..vulkano_win::required_extensions() - }; - // Create instance - let instance = Instance::new(InstanceCreateInfo { - enabled_extensions: instance_extensions, - ..Default::default() - }) - .expect("Failed to create instance"); - - // Get desired device - let physical_device = PhysicalDevice::enumerate(&instance) - .min_by_key(|p| match p.properties().device_type { - PhysicalDeviceType::DiscreteGpu => 0, - PhysicalDeviceType::IntegratedGpu => 1, - PhysicalDeviceType::VirtualGpu => 2, - PhysicalDeviceType::Cpu => 3, - PhysicalDeviceType::Other => 4, - }) - .unwrap(); - println!("Using device {}", physical_device.properties().device_name); - - // Create rendering surface along with window - let surface = WindowBuilder::new() - .with_inner_size(winit::dpi::LogicalSize::new( - opts.window_size[0], - opts.window_size[1], - )) - .with_title(opts.title) - .build_vk_surface(event_loop, instance.clone()) - .unwrap(); - println!("Window scale factor {}", surface.window().scale_factor()); - - // Create device - let (device, queue) = Self::create_device(physical_device, surface.clone()); - // Create swap chain & frame(s) to which we'll render - let (swapchain, final_images) = Self::create_swapchain( - surface.clone(), - physical_device, - device.clone(), - if opts.v_sync { - PresentMode::Fifo - } else { - PresentMode::Immediate - }, - ); - let previous_frame_end = Some(sync::now(device.clone()).boxed()); - let is_full_screen = swapchain.surface().window().fullscreen().is_some(); - let image_format = final_images.first().unwrap().format().unwrap(); - let render_passes = RenderPasses { - place_over_frame: RenderPassPlaceOverFrame::new(queue.clone(), image_format), - }; - - Renderer { - _instance: instance, - device, - surface, - queue, - swapchain, - image_index: 0, - final_views: final_images, - interim_image_views: HashMap::new(), - previous_frame_end, - recreate_swapchain: false, - render_passes, - is_full_screen, - } - } - - /// Creates vulkan device with required queue families and required extensions - fn create_device( - physical_device: PhysicalDevice, - surface: Arc>, - ) -> (Arc, Arc) { - let queue_family = physical_device - .queue_families() - .find(|&q| q.supports_graphics() && q.supports_surface(&surface).unwrap_or(false)) - .unwrap(); - - // Add device extensions based on needs, - let device_extensions = DeviceExtensions { - khr_swapchain: true, - ..DeviceExtensions::none() - }; - - // Add device features - let features = Features { - fill_mode_non_solid: true, - ..Features::none() - }; - let (device, mut queues) = Device::new( - physical_device, - DeviceCreateInfo { - enabled_extensions: physical_device - .required_extensions() - .union(&device_extensions), - enabled_features: features, - queue_create_infos: vec![QueueCreateInfo::family(queue_family)], - ..Default::default() - }, - ) - .unwrap(); - (device, queues.next().unwrap()) - } - - /// Creates swapchain and swapchain images - fn create_swapchain( - surface: Arc>, - physical: PhysicalDevice, - device: Arc, - present_mode: PresentMode, - ) -> (Arc>, Vec) { - let surface_capabilities = physical - .surface_capabilities(&surface, Default::default()) - .unwrap(); - let image_format = Some( - physical - .surface_formats(&surface, Default::default()) - .unwrap()[0] - .0, - ); - let image_extent = surface.window().inner_size().into(); - - let (swapchain, images) = Swapchain::new( - device, - surface, - SwapchainCreateInfo { - min_image_count: surface_capabilities.min_image_count, - image_format, - image_extent, - image_usage: ImageUsage::color_attachment(), - composite_alpha: surface_capabilities - .supported_composite_alpha - .iter() - .next() - .unwrap(), - present_mode, - ..Default::default() - }, - ) - .unwrap(); - let images = images - .into_iter() - .map(|image| ImageView::new_default(image).unwrap()) - .collect::>(); - (swapchain, images) - } - - /// Return default image format for images (swapchain format may differ) - pub fn image_format(&self) -> Format { - Format::R8G8B8A8_UNORM - } - - /// Return swapchain image format - #[allow(unused)] - pub fn swapchain_format(&self) -> Format { - self.final_views[self.image_index].format().unwrap() - } - - /// Returns the index of last swapchain image that is the next render target - /// All camera views will render onto their image at the same index - #[allow(unused)] - pub fn image_index(&self) -> usize { - self.image_index - } - - /// Access device - pub fn device(&self) -> Arc { - self.device.clone() - } - - /// Access rendering queue - pub fn queue(&self) -> Arc { - self.queue.clone() - } - - /// Render target surface - #[allow(unused)] - pub fn surface(&self) -> Arc> { - self.surface.clone() - } - - /// Winit window - pub fn window(&self) -> &Window { - self.surface.window() - } - - /// Winit window size - pub fn window_size(&self) -> [u32; 2] { - let size = self.window().inner_size(); - [size.width, size.height] - } - - /// Size of the final swapchain image (surface) - pub fn final_image_size(&self) -> [u32; 2] { - self.final_views[0].image().dimensions().width_height() - } - - /// Return final image which can be used as a render pipeline target - pub fn final_image(&self) -> FinalImageView { - self.final_views[self.image_index].clone() - } - - /// Return scale factor accounted window size - #[allow(unused)] - pub fn resolution(&self) -> [u32; 2] { - let size = self.window().inner_size(); - let scale_factor = self.window().scale_factor(); - [ - (size.width as f64 / scale_factor) as u32, - (size.height as f64 / scale_factor) as u32, - ] - } - - /// Add interim image view that can be used as a render target. - pub fn add_interim_image_view( - &mut self, - key: usize, - view_size: Option<[u32; 2]>, - format: Format, - ) { - let image = ImageView::new_default( - AttachmentImage::multisampled_with_usage( - self.device(), - if view_size.is_some() { - view_size.unwrap() - } else { - self.final_image_size() - }, - SampleCount::Sample1, - format, - ImageUsage { - sampled: true, - // So we can use the image as an input attachment - input_attachment: true, - // So we can write to the image in e.g. a compute shader - storage: true, - ..ImageUsage::none() - }, - ) - .unwrap(), - ) - .unwrap(); - self.interim_image_views - .insert(key, (image.clone(), !view_size.is_some())); - } - - /// Get interim image view by key - pub fn get_interim_image_view(&mut self, key: usize) -> InterimImageView { - self.interim_image_views.get(&key).unwrap().clone().0 - } - - /// Remove an interim image view from the renderer - pub fn remove_interim_image_view(&mut self, key: usize) { - self.interim_image_views.remove(&key); - } - - /// Toggles full-screen view - pub fn toggle_full_screen(&mut self) { - self.is_full_screen = !self.is_full_screen; - self.window().set_fullscreen(if self.is_full_screen { - Some(Fullscreen::Borderless(self.window().current_monitor())) - } else { - None - }); - } - - /// Resize swapchain and camera view images - pub fn resize(&mut self) { - self.recreate_swapchain = true - } - - /*================ - RENDERING - =================*/ - - /// Acquires next swapchain image and increments image index - /// This is the first to call in render orchestration. - /// Returns a gpu future representing the time after which the swapchain image has been acquired - /// and previous frame ended. - /// After this, execute command buffers and return future from them to `finish_frame`. - pub(crate) fn start_frame(&mut self) -> Result, AcquireError> { - // Recreate swap chain if needed (when resizing of window occurs or swapchain is outdated) - // Also resize render views if needed - if self.recreate_swapchain { - self.recreate_swapchain_and_views(); - } - - // Acquire next image in the swapchain - let (image_num, suboptimal, acquire_future) = - match acquire_next_image(self.swapchain.clone(), None) { - Ok(r) => r, - Err(AcquireError::OutOfDate) => { - self.recreate_swapchain = true; - return Err(AcquireError::OutOfDate); - } - Err(e) => panic!("Failed to acquire next image: {:?}", e), - }; - if suboptimal { - self.recreate_swapchain = true; - } - // Update our image index - self.image_index = image_num; - - let future = self.previous_frame_end.take().unwrap().join(acquire_future); - Ok(future.boxed()) - } - - /// Finishes render by presenting the swapchain - pub(crate) fn finish_frame(&mut self, after_future: Box) { - let future = after_future - .then_swapchain_present(self.queue.clone(), self.swapchain.clone(), self.image_index) - .then_signal_fence_and_flush(); - match future { - Ok(future) => { - // Prevent OutOfMemory error on Nvidia :( - // https://github.com/vulkano-rs/vulkano/issues/627 - match future.wait(None) { - Ok(x) => x, - Err(err) => println!("{:?}", err), - } - self.previous_frame_end = Some(future.boxed()); - } - Err(FlushError::OutOfDate) => { - self.recreate_swapchain = true; - self.previous_frame_end = Some(sync::now(self.device.clone()).boxed()); - } - Err(e) => { - println!("Failed to flush future: {:?}", e); - self.previous_frame_end = Some(sync::now(self.device.clone()).boxed()); - } - } - } - - /// Swapchain is recreated when resized. Interim image views that should follow swapchain - /// are also recreated - fn recreate_swapchain_and_views(&mut self) { - let (new_swapchain, new_images) = match self.swapchain.recreate(SwapchainCreateInfo { - image_extent: self.window().inner_size().into(), - ..self.swapchain.create_info() - }) { - Ok(r) => r, - Err(e @ SwapchainCreationError::ImageExtentNotSupported { .. }) => { - println!("{}", e); - return; - } - Err(e) => panic!("Failed to recreate swapchain: {:?}", e), - }; - - self.swapchain = new_swapchain; - let new_images = new_images - .into_iter() - .map(|image| ImageView::new_default(image).unwrap()) - .collect::>(); - self.final_views = new_images; - // Resize images that follow swapchain size - let resizable_views = self - .interim_image_views - .iter() - .filter(|(_, (_img, follow_swapchain))| *follow_swapchain) - .map(|c| *c.0) - .collect::>(); - for i in resizable_views { - self.remove_interim_image_view(i); - self.add_interim_image_view(i, None, self.image_format()); - } - self.recreate_swapchain = false; - } -} - -/// Between `start_frame` and `end_frame` use this pipeline to fill framebuffer with your interim image -pub fn image_over_frame_renderpass( - renderer: &mut Renderer, - before_pipeline_future: F, - image: InterimImageView, -) -> Box -where - F: GpuFuture + 'static, -{ - renderer - .render_passes - .place_over_frame - .render(before_pipeline_future, image, renderer.final_image()) - .then_signal_fence_and_flush() - .unwrap() - .boxed() -} diff --git a/vulkano-util/src/context.rs b/vulkano-util/src/context.rs index f191641631..f1954e0de1 100644 --- a/vulkano-util/src/context.rs +++ b/vulkano-util/src/context.rs @@ -30,6 +30,7 @@ impl Default for VulkanoConfig { VulkanoConfig { instance_create_info: InstanceCreateInfo { application_version: Version::V1_2, + enabled_extensions: vulkano_win::required_extensions(), ..Default::default() }, debug_create_info: None, @@ -41,7 +42,10 @@ impl Default for VulkanoConfig { PhysicalDeviceType::Other => 5, }, print_device_name: true, - device_extensions: DeviceExtensions::none(), + device_extensions: DeviceExtensions { + khr_swapchain: true, + ..DeviceExtensions::none() + }, device_features: Features::none(), } } diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs index 9b28a9e667..1aab670955 100644 --- a/vulkano-util/src/renderer.rs +++ b/vulkano-util/src/renderer.rs @@ -57,7 +57,7 @@ impl VulkanoWindowRenderer { vulkano_context: &VulkanoContext, window: winit::window::Window, descriptor: &WindowDescriptor, - swapchain_create_info_overrides: SwapchainCreateInfo, + swapchain_create_info_modify: fn(&mut SwapchainCreateInfo), ) -> VulkanoWindowRenderer { // Create rendering surface from window let surface = create_surface_from_winit(window, vulkano_context.instance()).unwrap(); @@ -66,10 +66,8 @@ impl VulkanoWindowRenderer { let (swap_chain, final_views) = Self::create_swap_chain( vulkano_context.device(), surface.clone(), - SwapchainCreateInfo { - present_mode: descriptor.present_mode, - ..swapchain_create_info_overrides - }, + descriptor, + swapchain_create_info_modify, ); let previous_frame_end = Some(sync::now(vulkano_context.device()).boxed()); @@ -91,7 +89,8 @@ impl VulkanoWindowRenderer { fn create_swap_chain( device: Arc, surface: Arc>, - swapchain_create_info_overrides: SwapchainCreateInfo, + window_descriptor: &WindowDescriptor, + swapchain_create_info_modify: fn(&mut SwapchainCreateInfo), ) -> (Arc>, Vec) { let surface_capabilities = device .physical_device() @@ -105,10 +104,8 @@ impl VulkanoWindowRenderer { .0, ); let image_extent = surface.window().inner_size().into(); - let (swapchain, images) = Swapchain::new( - device, - surface, - SwapchainCreateInfo { + let (swapchain, images) = Swapchain::new(device, surface, { + let mut create_info = SwapchainCreateInfo { min_image_count: surface_capabilities.min_image_count, image_format, image_extent, @@ -118,9 +115,13 @@ impl VulkanoWindowRenderer { .iter() .next() .unwrap(), - ..swapchain_create_info_overrides - }, - ) + ..Default::default() + }; + // Get present mode from window descriptor + create_info.present_mode = window_descriptor.present_mode; + swapchain_create_info_modify(&mut create_info); + create_info + }) .unwrap(); let images = images .into_iter() diff --git a/vulkano-util/src/window.rs b/vulkano-util/src/window.rs index 56a6544757..ba6f936717 100644 --- a/vulkano-util/src/window.rs +++ b/vulkano-util/src/window.rs @@ -28,8 +28,8 @@ use winit::dpi::LogicalSize; /// let context = VulkanoContext::new(VulkanoConfig::default()); /// let event_loop = EventLoop::new(); /// let mut vulkano_windows = VulkanoWindows::default(); -/// vulkano_windows.create_window(&event_loop, &context, &Default::default(), Default::default()); -/// vulkano_windows.create_window(&event_loop, &context, &Default::default(), Default::default()); +/// vulkano_windows.create_window(&event_loop, &context, &Default::default(), |_| {}); +/// vulkano_windows.create_window(&event_loop, &context, &Default::default(), |_| {}); /// // You should now have two windows /// } /// ``` @@ -45,7 +45,7 @@ impl VulkanoWindows { event_loop: &winit::event_loop::EventLoopWindowTarget<()>, vulkano_context: &VulkanoContext, window_descriptor: &WindowDescriptor, - swapchain_create_info_overriders: SwapchainCreateInfo, + swapchain_create_info_modify: fn(&mut SwapchainCreateInfo), ) { #[cfg(target_os = "windows")] let mut winit_window_builder = { @@ -156,7 +156,7 @@ impl VulkanoWindows { vulkano_context, winit_window, window_descriptor, - swapchain_create_info_overriders, + swapchain_create_info_modify, ), ); } From 9abd751b2388f9bcce591081c2e13747e9e19834 Mon Sep 17 00:00:00 2001 From: hakolao Date: Mon, 20 Jun 2022 22:15:11 -0400 Subject: [PATCH 04/22] Add general purpose image view generation to storage image --- CHANGELOG.md | 1 + .../vulkano_config.rs | 42 --- .../vulkano_context.rs | 338 ------------------ .../vulkano_window.rs | 329 ----------------- vulkano/src/image/storage.rs | 51 +++ vulkano/src/image/sys.rs | 34 +- vulkano/src/image/view.rs | 2 +- 7 files changed, 81 insertions(+), 716 deletions(-) delete mode 100644 examples/src/bin/multi_window_game_of_life/vulkano_config.rs delete mode 100644 examples/src/bin/multi_window_game_of_life/vulkano_context.rs delete mode 100644 examples/src/bin/multi_window_game_of_life/vulkano_window.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eba71803c..202262625c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ - Fixed bug in various Vulkan calls where the returned data might be incomplete. - Fixed bug that triggered an assert if a render pass had an attachment with `Undefined` initial layout. - Added an `is_signaled` method to `FenceSignalFuture`. +- Add a simple `general_purpose_image_view` method to `StorageImage` for less verbose image view creation for e.g. intermediary render targets. # Version 0.29.0 (2022-03-11) diff --git a/examples/src/bin/multi_window_game_of_life/vulkano_config.rs b/examples/src/bin/multi_window_game_of_life/vulkano_config.rs deleted file mode 100644 index 02abeabe1d..0000000000 --- a/examples/src/bin/multi_window_game_of_life/vulkano_config.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2022 The vulkano developers -// Licensed under the Apache License, Version 2.0 -// or the MIT -// license , -// at your option. All files in the project carrying such -// notice may not be copied, modified, or distributed except -// according to those terms. - -use vulkano::{ - device::{DeviceExtensions, Features}, - instance::InstanceExtensions, -}; - -/// A utility struct to configure vulkano context -pub struct VulkanoConfig { - pub return_from_run: bool, - pub add_primary_window: bool, - pub instance_extensions: InstanceExtensions, - pub device_extensions: DeviceExtensions, - pub features: Features, - pub layers: Vec, -} - -impl Default for VulkanoConfig { - fn default() -> Self { - VulkanoConfig { - return_from_run: false, - add_primary_window: true, - instance_extensions: InstanceExtensions { - ext_debug_utils: true, - ..vulkano_win::required_extensions() - }, - device_extensions: DeviceExtensions { - khr_swapchain: true, - ..DeviceExtensions::none() - }, - features: Features::none(), - layers: vec![], - } - } -} diff --git a/examples/src/bin/multi_window_game_of_life/vulkano_context.rs b/examples/src/bin/multi_window_game_of_life/vulkano_context.rs deleted file mode 100644 index 7c121fd952..0000000000 --- a/examples/src/bin/multi_window_game_of_life/vulkano_context.rs +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright (c) 2022 The vulkano developers -// Licensed under the Apache License, Version 2.0 -// or the MIT -// license , -// at your option. All files in the project carrying such -// notice may not be copied, modified, or distributed except -// according to those terms. - -use crate::vulkano_config::VulkanoConfig; -use std::{ - ffi::{CStr, CString}, - sync::Arc, -}; -use vulkano::{ - device::{ - physical::{PhysicalDevice, PhysicalDeviceType}, - Device, DeviceCreateInfo, DeviceExtensions, Features, Queue, QueueCreateInfo, - }, - image::{view::ImageView, ImageUsage, StorageImage, SwapchainImage}, - instance::{ - debug::{ - DebugUtilsMessageSeverity, DebugUtilsMessageType, DebugUtilsMessenger, - DebugUtilsMessengerCreateInfo, - }, - Instance, InstanceCreateInfo, InstanceExtensions, - }, - swapchain::{ - ColorSpace, FullScreenExclusive, PresentMode, Surface, SurfaceTransform, Swapchain, - SwapchainCreateInfo, - }, - Version, -}; -use winit::window::Window; - -/// Final render target onto which whole app is rendered (per window) -pub type FinalImageView = Arc>>; -/// Multipurpose image view -pub type DeviceImageView = Arc>; - -/// Vulkano context provides access to device, graphics queues and instance allowing you to separate -/// window handling and the context initiation. -/// The purpose of this struct is to allow you to focus on the graphics keep your code much less verbose -pub struct VulkanoContext { - _debug_callback: DebugUtilsMessenger, - instance: Arc, - device: Arc, - graphics_queue: Arc, - compute_queue: Arc, - device_name: String, - device_type: PhysicalDeviceType, - max_mem_gb: f32, -} - -unsafe impl Sync for VulkanoContext {} - -unsafe impl Send for VulkanoContext {} - -impl VulkanoContext { - pub fn new(config: &VulkanoConfig) -> Self { - let instance = create_vk_instance(config.instance_extensions, config.layers.clone()); - let is_debug = config.layers.iter().any(|layer| { - layer == "VK_LAYER_LUNARG_standard_validation" || layer == "VK_LAYER_KHRONOS_validation" - }); - let debug_callback = create_vk_debug_callback(instance.clone(), is_debug); - // Get desired device - let physical_device = PhysicalDevice::enumerate(&instance) - .min_by_key(|p| match p.properties().device_type { - PhysicalDeviceType::DiscreteGpu => 1, - PhysicalDeviceType::IntegratedGpu => 2, - PhysicalDeviceType::VirtualGpu => 3, - PhysicalDeviceType::Cpu => 4, - PhysicalDeviceType::Other => 5, - }) - .unwrap(); - let device_name = physical_device.properties().device_name.to_string(); - #[cfg(target_os = "windows")] - let max_mem_gb = physical_device.properties().max_memory_allocation_count as f32 * 9.31e-4; - #[cfg(not(target_os = "windows"))] - let max_mem_gb = physical_device.properties().max_memory_allocation_count as f32 * 9.31e-10; - println!( - "Using device {}, type: {:?}, mem: {:.2} gb", - physical_device.properties().device_name, - physical_device.properties().device_type, - max_mem_gb, - ); - let device_type = physical_device.properties().device_type; - - // Create device - let (device, graphics_queue, compute_queue) = Self::create_device( - physical_device, - config.device_extensions, - config.features.clone(), - ); - - Self { - instance, - _debug_callback: debug_callback, - device, - graphics_queue, - compute_queue, - device_name, - device_type, - max_mem_gb, - } - } - - /// Creates vulkan device with required queue families and required extensions - fn create_device( - physical: PhysicalDevice, - device_extensions: DeviceExtensions, - features: Features, - ) -> (Arc, Arc, Arc) { - // Choose a graphics queue family - let (gfx_index, queue_family_graphics) = physical - .queue_families() - .enumerate() - .find(|&(_i, q)| q.supports_graphics()) - .unwrap(); - // Choose compute queue family (separate from gfx) - let compute_family_data = physical - .queue_families() - .enumerate() - .find(|&(i, q)| i != gfx_index && q.supports_compute()); - - // If we can create a compute queue, do so. Else use same queue as graphics - if let Some((_compute_index, queue_family_compute)) = compute_family_data { - let (device, mut queues) = Device::new( - physical, - DeviceCreateInfo { - enabled_extensions: physical.required_extensions().union(&device_extensions), - enabled_features: features, - queue_create_infos: vec![ - QueueCreateInfo { - queues: vec![1.0], - ..QueueCreateInfo::family(queue_family_graphics) - }, - QueueCreateInfo { - queues: vec![0.5], - ..QueueCreateInfo::family(queue_family_compute) - }, - ], - ..Default::default() - }, - ) - .unwrap(); - let gfx_queue = queues.next().unwrap(); - let compute_queue = queues.next().unwrap(); - (device, gfx_queue, compute_queue) - } else { - let (device, mut queues) = Device::new( - physical, - DeviceCreateInfo { - enabled_extensions: physical.required_extensions().union(&device_extensions), - enabled_features: features, - queue_create_infos: vec![QueueCreateInfo::family(queue_family_graphics)], - ..Default::default() - }, - ) - .unwrap(); - let gfx_queue = queues.next().unwrap(); - let compute_queue = gfx_queue.clone(); - (device, gfx_queue, compute_queue) - } - } - - /// Creates swapchain and swapchain images - pub(crate) fn create_swapchain( - &self, - surface: Arc>, - present_mode: PresentMode, - ) -> (Arc>, Vec) { - let surface_capabilities = self - .device - .physical_device() - .surface_capabilities(&surface, Default::default()) - .unwrap(); - let image_format = Some( - self.device - .physical_device() - .surface_formats(&surface, Default::default()) - .unwrap()[0] - .0, - ); - let image_extent = surface.window().inner_size().into(); - - let (swapchain, images) = Swapchain::new( - self.device.clone(), - surface, - SwapchainCreateInfo { - min_image_count: surface_capabilities.min_image_count, - image_format, - image_extent, - image_usage: ImageUsage::color_attachment(), - composite_alpha: surface_capabilities - .supported_composite_alpha - .iter() - .next() - .unwrap(), - present_mode, - ..Default::default() - }, - ) - .unwrap(); - let images = images - .into_iter() - .map(|image| ImageView::new_default(image).unwrap()) - .collect::>(); - (swapchain, images) - } - - pub fn device_name(&self) -> &str { - &self.device_name - } - - pub fn device_type(&self) -> PhysicalDeviceType { - self.device_type - } - - pub fn max_mem_gb(&self) -> f32 { - self.max_mem_gb - } - - /// Access instance - pub fn instance(&self) -> Arc { - self.instance.clone() - } - - /// Access device - pub fn device(&self) -> Arc { - self.device.clone() - } - - /// Access rendering queue - pub fn graphics_queue(&self) -> Arc { - self.graphics_queue.clone() - } - - /// Access compute queue - pub fn compute_queue(&self) -> Arc { - self.compute_queue.clone() - } -} - -// Create vk instance with given layers -pub fn create_vk_instance( - instance_extensions: InstanceExtensions, - layers: Vec, -) -> Arc { - // Create instance. - let result = Instance::new(InstanceCreateInfo { - enabled_extensions: instance_extensions, - enabled_layers: layers, - ..Default::default() - }); - - // Handle errors. On mac os, it will ask you to install vulkan sdk if you have not done so. - #[cfg(target_os = "macos")] - let instance = { - use vulkano::instance::InstanceCreationError; - - match result { - Err(e) => match e { - InstanceCreationError::LoadingError(le) => { - println!( - "{:?}, Did you install vulkanSDK from https://vulkan.lunarg.com/sdk/home ?", - le - ); - Err(le).expect("") - } - _ => Err(e).expect("Failed to create instance"), - }, - Ok(i) => i, - } - }; - #[cfg(not(target_os = "macos"))] - let instance = result.expect("Failed to create instance"); - - instance -} - -// Create vk debug call back (to exists outside renderer) -pub fn create_vk_debug_callback(instance: Arc, is_debug: bool) -> DebugUtilsMessenger { - // Create debug callback for printing vulkan errors and warnings. This will do nothing unless the layers are enabled - unsafe { - DebugUtilsMessenger::new( - instance, - DebugUtilsMessengerCreateInfo { - message_severity: if is_debug { - DebugUtilsMessageSeverity { - error: true, - warning: true, - information: true, - verbose: true, - } - } else { - DebugUtilsMessageSeverity { - error: true, - ..DebugUtilsMessageSeverity::none() - } - }, - message_type: DebugUtilsMessageType::all(), - ..DebugUtilsMessengerCreateInfo::user_callback(Arc::new(|msg| { - let severity = if msg.severity.error { - "error" - } else if msg.severity.warning { - "warning" - } else if msg.severity.information { - "information" - } else if msg.severity.verbose { - "verbose" - } else { - panic!("no-impl"); - }; - - let ty = if msg.ty.general { - "general" - } else if msg.ty.validation { - "validation" - } else if msg.ty.performance { - "performance" - } else { - panic!("no-impl"); - }; - - println!( - "{} {} {}: {}", - msg.layer_prefix.unwrap_or("unknown"), - ty, - severity, - msg.description - ); - })) - }, - ) - .unwrap() - } -} diff --git a/examples/src/bin/multi_window_game_of_life/vulkano_window.rs b/examples/src/bin/multi_window_game_of_life/vulkano_window.rs deleted file mode 100644 index 1f0629a3a9..0000000000 --- a/examples/src/bin/multi_window_game_of_life/vulkano_window.rs +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright (c) 2022 The vulkano developers -// Licensed under the Apache License, Version 2.0 -// or the MIT -// license , -// at your option. All files in the project carrying such -// notice may not be copied, modified, or distributed except -// according to those terms. - -use crate::vulkano_context::{DeviceImageView, FinalImageView, VulkanoContext}; -use std::{collections::HashMap, sync::Arc}; -use vulkano::{ - device::{DeviceOwned, Queue}, - format::Format, - image::{ - view::ImageView, ImageAccess, ImageCreateFlags, ImageDimensions, ImageUsage, - ImageViewAbstract, StorageImage, - }, - swapchain, - swapchain::{ - AcquireError, PresentMode, Surface, Swapchain, SwapchainCreateInfo, SwapchainCreationError, - }, - sync, - sync::{FlushError, GpuFuture}, -}; -use vulkano_win::create_surface_from_winit; -use winit::window::Window; - -pub struct VulkanoWindow { - surface: Arc>, - graphics_queue: Arc, - swapchain: Arc>, - final_views: Vec, - /// Image view that is to be rendered with our pipeline. - /// (bool refers to whether it should get resized with swapchain resize) - image_views: HashMap, - recreate_swapchain: bool, - previous_frame_end: Option>, - image_index: usize, -} - -unsafe impl Sync for VulkanoWindow {} - -unsafe impl Send for VulkanoWindow {} - -impl VulkanoWindow { - /// Creates new `VulkanoWindow` that is used to orchestrate rendering on the window's swapchain. - /// Takes ownership of winit window - pub fn new( - vulkano_context: &VulkanoContext, - window: winit::window::Window, - vsync: bool, - ) -> VulkanoWindow { - // Create rendering surface from window - let surface = create_surface_from_winit(window, vulkano_context.instance()).unwrap(); - // Create swap chain & frame(s) to which we'll render - let (swapchain, final_views) = vulkano_context.create_swapchain( - surface.clone(), - if vsync { - PresentMode::Fifo - } else { - PresentMode::Immediate - }, - ); - - let previous_frame_end = Some(sync::now(vulkano_context.device()).boxed()); - - VulkanoWindow { - surface, - graphics_queue: vulkano_context.graphics_queue(), - swapchain, - final_views, - image_views: HashMap::default(), - recreate_swapchain: false, - previous_frame_end, - image_index: 0, - } - } - - /// Return swapchain image format - pub fn swapchain_format(&self) -> Format { - self.final_views[self.image_index].format().unwrap() - } - - /// Return default image format for images - pub fn default_image_format(&self) -> Format { - Format::R8G8B8A8_UNORM - } - - /// Returns the index of last swapchain image that is the next render target - pub fn image_index(&self) -> usize { - self.image_index - } - - /// Graphics queue of this window - pub fn graphics_queue(&self) -> Arc { - self.graphics_queue.clone() - } - - /// Render target surface - pub fn surface(&self) -> Arc> { - self.surface.clone() - } - - /// Winit window (you can manipulate window through this) - pub fn window(&self) -> &Window { - self.surface.window() - } - - pub fn window_size(&self) -> [u32; 2] { - let size = self.window().inner_size(); - [size.width, size.height] - } - - /// Size of the final swapchain image (surface) - pub fn final_image_size(&self) -> [u32; 2] { - self.final_views[0].image().dimensions().width_height() - } - - /// Return final image which can be used as a render pipeline target - pub fn final_image(&self) -> FinalImageView { - self.final_views[self.image_index].clone() - } - - /// Return scale factor accounted window size - pub fn resolution(&self) -> [u32; 2] { - let size = self.window().inner_size(); - let scale_factor = self.window().scale_factor(); - [ - (size.width as f64 / scale_factor) as u32, - (size.height as f64 / scale_factor) as u32, - ] - } - - pub fn aspect_ratio(&self) -> f32 { - let dims = self.window_size(); - dims[0] as f32 / dims[1] as f32 - } - - /// Resize swapchain and camera view images at the beginning of next frame - pub fn resize(&mut self) { - self.recreate_swapchain = true; - } - - /// Add an image view that can be used to render e.g. camera views or other views using - /// the render pipeline. Not giving a view size ensures the image view follows swapchain (window). - pub fn add_image_target(&mut self, key: usize, view_size: Option<[u32; 2]>, format: Format) { - let size = if let Some(s) = view_size { - s - } else { - self.final_image_size() - }; - let image = create_device_image(self.graphics_queue.clone(), size, format); - self.image_views.insert(key, (image, view_size.is_none())); - } - - /// Get interim image view by key - pub fn get_image_target(&mut self, key: usize) -> DeviceImageView { - self.image_views.get(&key).unwrap().clone().0 - } - - /// Get interim image view by key - pub fn has_image_target(&mut self, key: usize) -> bool { - self.image_views.get(&key).is_some() - } - - pub fn remove_image_target(&mut self, key: usize) { - self.image_views.remove(&key); - } - - /*================ - RENDERING - =================*/ - - /// Acquires next swapchain image and increments image index - /// This is the first to call in render orchestration. - /// Returns a gpu future representing the time after which the swapchain image has been acquired - /// and previous frame ended. - /// After this, execute command buffers and return future from them to `finish_frame`. - pub fn start_frame(&mut self) -> std::result::Result, AcquireError> { - // Recreate swap chain if needed (when resizing of window occurs or swapchain is outdated) - // Also resize render views if needed - if self.recreate_swapchain { - self.recreate_swapchain_and_views(); - } - - // Acquire next image in the swapchain - let (image_num, suboptimal, acquire_future) = - match swapchain::acquire_next_image(self.swapchain.clone(), None) { - Ok(r) => r, - Err(AcquireError::OutOfDate) => { - self.recreate_swapchain = true; - return Err(AcquireError::OutOfDate); - } - Err(e) => panic!("Failed to acquire next image: {:?}", e), - }; - if suboptimal { - self.recreate_swapchain = true; - } - // Update our image index - self.image_index = image_num; - - let future = self.previous_frame_end.take().unwrap().join(acquire_future); - - Ok(future.boxed()) - } - - /// Finishes render by presenting the swapchain - pub fn finish_frame(&mut self, after_future: Box) { - let future = after_future - .then_swapchain_present( - self.graphics_queue.clone(), - self.swapchain.clone(), - self.image_index, - ) - .then_signal_fence_and_flush(); - match future { - Ok(future) => { - // A hack to prevent OutOfMemory error on Nvidia :( - // https://github.com/vulkano-rs/vulkano/issues/627 - match future.wait(None) { - Ok(x) => x, - Err(err) => println!("{:?}", err), - } - self.previous_frame_end = Some(future.boxed()); - } - Err(FlushError::OutOfDate) => { - self.recreate_swapchain = true; - self.previous_frame_end = - Some(sync::now(self.graphics_queue.device().clone()).boxed()); - } - Err(e) => { - println!("Failed to flush future: {:?}", e); - self.previous_frame_end = - Some(sync::now(self.graphics_queue.device().clone()).boxed()); - } - } - } - - /// Recreates swapchain images and image views that should follow swap chain image size - fn recreate_swapchain_and_views(&mut self) { - let (new_swapchain, new_images) = match self.swapchain.recreate(SwapchainCreateInfo { - image_extent: self.window().inner_size().into(), - ..self.swapchain.create_info() - }) { - Ok(r) => r, - Err(e @ SwapchainCreationError::ImageExtentNotSupported { .. }) => { - println!("{}", e); - return; - } - Err(e) => panic!("Failed to recreate swapchain: {:?}", e), - }; - - self.swapchain = new_swapchain; - let new_images = new_images - .into_iter() - .map(|image| ImageView::new_default(image).unwrap()) - .collect::>(); - self.final_views = new_images; - // Resize images that follow swapchain size - let resizable_views = self - .image_views - .iter() - .filter(|(_, (_img, follow_swapchain))| *follow_swapchain) - .map(|c| *c.0) - .collect::>(); - for i in resizable_views { - let format = self.get_image_target(i).format().unwrap(); - self.remove_image_target(i); - self.add_image_target(i, None, format); - } - self.recreate_swapchain = false; - } -} - -/// Creates a default storage image -pub fn create_device_image(queue: Arc, size: [u32; 2], format: Format) -> DeviceImageView { - let dims = ImageDimensions::Dim2d { - width: size[0], - height: size[1], - array_layers: 1, - }; - let flags = ImageCreateFlags::none(); - ImageView::new_default( - StorageImage::with_usage( - queue.device().clone(), - dims, - format, - ImageUsage { - sampled: true, - storage: true, - color_attachment: true, - transfer_dst: true, - ..ImageUsage::none() - }, - flags, - Some(queue.family()), - ) - .unwrap(), - ) - .unwrap() -} - -/// Creates a device image with usage flags -pub fn create_device_image_with_usage( - queue: Arc, - size: [u32; 2], - format: Format, - usage: ImageUsage, -) -> DeviceImageView { - let dims = ImageDimensions::Dim2d { - width: size[0], - height: size[1], - array_layers: 1, - }; - let flags = ImageCreateFlags::none(); - ImageView::new_default( - StorageImage::with_usage( - queue.device().clone(), - dims, - format, - usage, - flags, - Some(queue.family()), - ) - .unwrap(), - ) - .unwrap() -} diff --git a/vulkano/src/image/storage.rs b/vulkano/src/image/storage.rs index 09079ed594..8c6685564f 100644 --- a/vulkano/src/image/storage.rs +++ b/vulkano/src/image/storage.rs @@ -11,6 +11,8 @@ use super::{ sys::UnsafeImage, traits::ImageContent, ImageAccess, ImageCreateFlags, ImageCreationError, ImageDescriptorLayouts, ImageDimensions, ImageInner, ImageLayout, ImageUsage, }; +use crate::device::Queue; +use crate::image::view::ImageView; use crate::{ device::{physical::QueueFamily, Device, DeviceOwned}, format::Format, @@ -223,6 +225,55 @@ impl StorageImage { })) } + /// Allows the creation of a simple 2D general purpose image view from `StorageImage`. + /// ## Example + /// ``` + /// use vulkano::image::StorageImage; + /// use vulkano::image::ImageUsage; + /// use vulkano::format::Format; + /// + /// let image = StorageImage::general_purpose_image_view( + /// queue.clone(), + /// size, + /// Format::R8G8B8A8_UNORM, + /// ImageUsage { + /// sampled: true, + /// storage: true, + /// color_attachment: true, + /// transfer_dst: true, + /// ..ImageUsage::none() + /// }, + /// ) + /// .unwrap(); + /// ``` + pub fn general_purpose_image_view( + queue: Arc, + size: [u32; 2], + format: Format, + usage: ImageUsage, + ) -> Result>, ImageCreationError> { + let dims = ImageDimensions::Dim2d { + width: size[0], + height: size[1], + array_layers: 1, + }; + let flags = ImageCreateFlags::none(); + match StorageImage::with_usage( + queue.device().clone(), + dims, + format, + usage, + flags, + Some(queue.family()), + ) { + Ok(image) => match ImageView::new_default(image) { + Ok(view) => Ok(view), + Err(e) => Err(ImageCreationError::ImageViewCreationFailed(e)), + }, + Err(e) => Err(e), + } + } + /// Exports posix file descriptor for the allocated memory /// requires `khr_external_memory_fd` and `khr_external_memory` extensions to be loaded. pub fn export_posix_fd(&self) -> Result { diff --git a/vulkano/src/image/sys.rs b/vulkano/src/image/sys.rs index 615b0122e9..aaa3557332 100644 --- a/vulkano/src/image/sys.rs +++ b/vulkano/src/image/sys.rs @@ -18,6 +18,7 @@ use super::{ ImageSubresourceLayers, ImageSubresourceRange, ImageTiling, ImageUsage, SampleCount, SampleCounts, }; +use crate::image::view::ImageViewCreationError; use crate::{ buffer::cpu_access::{ReadLockError, WriteLockError}, check_errors, @@ -1532,7 +1533,9 @@ pub enum ImageCreationError { FormatNotSupported, /// A requested usage flag was not supported by the given format. - FormatUsageNotSupported { usage: &'static str }, + FormatUsageNotSupported { + usage: &'static str, + }, /// The image configuration as queried through the `image_format_properties` function was not /// supported by the device. @@ -1540,18 +1543,30 @@ pub enum ImageCreationError { /// The number of array layers exceeds the maximum supported by the device for this image /// configuration. - MaxArrayLayersExceeded { array_layers: u32, max: u32 }, + MaxArrayLayersExceeded { + array_layers: u32, + max: u32, + }, /// The specified dimensions exceed the maximum supported by the device for this image /// configuration. - MaxDimensionsExceeded { extent: [u32; 3], max: [u32; 3] }, + MaxDimensionsExceeded { + extent: [u32; 3], + max: [u32; 3], + }, /// The usage included one of the attachment types, and the specified width and height exceeded /// the `max_framebuffer_width` or `max_framebuffer_height` limits. - MaxFramebufferDimensionsExceeded { extent: [u32; 2], max: [u32; 2] }, + MaxFramebufferDimensionsExceeded { + extent: [u32; 2], + max: [u32; 2], + }, /// The maximum number of mip levels for the given dimensions has been exceeded. - MaxMipLevelsExceeded { mip_levels: u32, max: u32 }, + MaxMipLevelsExceeded { + mip_levels: u32, + max: u32, + }, /// Multisampling was enabled, and the `cube_compatible` flag was set. MultisampleCubeCompatible, @@ -1573,7 +1588,9 @@ pub enum ImageCreationError { /// The sharing mode was set to `Concurrent`, but one of the specified queue family ids was not /// valid. - SharingInvalidQueueFamilyId { id: u32 }, + SharingInvalidQueueFamilyId { + id: u32, + }, /// A YCbCr format was given, but the specified width and/or height was not a multiple of 2 /// as required by the format's chroma subsampling. @@ -1587,6 +1604,8 @@ pub enum ImageCreationError { /// A YCbCr format was given, but the image type was not 2D. YcbcrFormatNot2d, + + ImageViewCreationFailed(ImageViewCreationError), } impl error::Error for ImageCreationError { @@ -1720,6 +1739,9 @@ impl fmt::Display for ImageCreationError { "a YCbCr format was given, but the image type was not 2D" ) } + Self::ImageViewCreationFailed(e) => { + write!(fmt, "Image view creation failed {}", e) + } } } } diff --git a/vulkano/src/image/view.rs b/vulkano/src/image/view.rs index 4ca7388ac6..5093b870ca 100644 --- a/vulkano/src/image/view.rs +++ b/vulkano/src/image/view.rs @@ -701,7 +701,7 @@ impl ImageViewCreateInfo { } /// Error that can happen when creating an image view. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ImageViewCreationError { /// Allocating memory failed. OomError(OomError), From 4925dcc84d42b5d382213ea8574742f07a8d1c58 Mon Sep 17 00:00:00 2001 From: hakolao Date: Mon, 20 Jun 2022 22:24:09 -0400 Subject: [PATCH 05/22] Add some missing functionality --- vulkano-util/src/lib.rs | 2 -- vulkano-util/src/renderer.rs | 38 ++++++++-------------------------- vulkano-util/src/window.rs | 40 +++++++++++++++++++++++++++++++----- 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/vulkano-util/src/lib.rs b/vulkano-util/src/lib.rs index 08fd9290ff..552fee36f0 100644 --- a/vulkano-util/src/lib.rs +++ b/vulkano-util/src/lib.rs @@ -7,8 +7,6 @@ // notice may not be copied, modified, or distributed except // according to those terms. -#![allow(dead_code)] - pub mod context; pub mod renderer; pub mod window; diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs index 1aab670955..020a5124d5 100644 --- a/vulkano-util/src/renderer.rs +++ b/vulkano-util/src/renderer.rs @@ -13,7 +13,7 @@ use std::sync::Arc; use crate::context::VulkanoContext; use crate::window::WindowDescriptor; use vulkano::device::Device; -use vulkano::image::{ImageCreateFlags, ImageDimensions, ImageUsage, StorageImage, SwapchainImage}; +use vulkano::image::{ImageUsage, StorageImage, SwapchainImage}; use vulkano::{ device::Queue, format::Format, @@ -199,8 +199,13 @@ impl VulkanoWindowRenderer { /// Add interim image view that resizes with window pub fn add_additional_image_view(&mut self, key: usize, format: Format, usage: ImageUsage) { let size = self.swapchain_image_size(); - let image = - create_device_image_with_usage(self.graphics_queue.clone(), size, format, usage); + let image = StorageImage::general_purpose_image_view( + self.graphics_queue.clone(), + size, + format, + usage, + ) + .unwrap(); self.additional_image_views.insert(key, image); } @@ -312,30 +317,3 @@ impl VulkanoWindowRenderer { self.recreate_swapchain = false; } } - -/// A simple utility function to create storage image views with usage -pub fn create_device_image_with_usage( - queue: Arc, - size: [u32; 2], - format: Format, - usage: ImageUsage, -) -> DeviceImageView { - let dims = ImageDimensions::Dim2d { - width: size[0], - height: size[1], - array_layers: 1, - }; - let flags = ImageCreateFlags::none(); - ImageView::new_default( - StorageImage::with_usage( - queue.device().clone(), - dims, - format, - usage, - flags, - Some(queue.family()), - ) - .unwrap(), - ) - .unwrap() -} diff --git a/vulkano-util/src/window.rs b/vulkano-util/src/window.rs index ba6f936717..cca34f7232 100644 --- a/vulkano-util/src/window.rs +++ b/vulkano-util/src/window.rs @@ -11,9 +11,11 @@ use crate::context::VulkanoContext; use crate::renderer::VulkanoWindowRenderer; +use std::collections::hash_map::{Iter, IterMut}; use std::collections::HashMap; use vulkano::swapchain::{PresentMode, SwapchainCreateInfo}; use winit::dpi::LogicalSize; +use winit::window::WindowId; /// A struct organizing windows and their corresponding renderers. This makes it easy to handle multiple windows. /// @@ -28,8 +30,8 @@ use winit::dpi::LogicalSize; /// let context = VulkanoContext::new(VulkanoConfig::default()); /// let event_loop = EventLoop::new(); /// let mut vulkano_windows = VulkanoWindows::default(); -/// vulkano_windows.create_window(&event_loop, &context, &Default::default(), |_| {}); -/// vulkano_windows.create_window(&event_loop, &context, &Default::default(), |_| {}); +/// let _id1 = vulkano_windows.create_window(&event_loop, &context, &Default::default(), |_| {}); +/// let _id2 = vulkano_windows.create_window(&event_loop, &context, &Default::default(), |_| {}); /// // You should now have two windows /// } /// ``` @@ -46,7 +48,7 @@ impl VulkanoWindows { vulkano_context: &VulkanoContext, window_descriptor: &WindowDescriptor, swapchain_create_info_modify: fn(&mut SwapchainCreateInfo), - ) { + ) -> winit::window::WindowId { #[cfg(target_os = "windows")] let mut winit_window_builder = { use winit::platform::windows::WindowBuilderExtWindows; @@ -146,12 +148,13 @@ impl VulkanoWindows { winit_window.set_cursor_visible(window_descriptor.cursor_visible); + let id = winit_window.id(); if self.primary.is_none() { - self.primary = Some(winit_window.id()); + self.primary = Some(id); } self.windows.insert( - winit_window.id(), + id, VulkanoWindowRenderer::new( vulkano_context, winit_window, @@ -159,6 +162,8 @@ impl VulkanoWindows { swapchain_create_info_modify, ), ); + + id } /// Get a mutable reference to the primary window's renderer @@ -207,6 +212,31 @@ impl VulkanoWindows { .get(&id) .and_then(|v_window| Some(v_window.window())) } + + /// Return primary window id + pub fn primary_window_id(&self) -> Option { + self.primary + } + + /// Remove renderer by window id + pub fn remove_renderer(&mut self, id: winit::window::WindowId) { + self.windows.remove(&id); + if let Some(primary) = self.primary { + if primary == id { + self.primary = None; + } + } + } + + /// Return iterator over window renderers + pub fn iter(&self) -> Iter { + self.windows.iter() + } + + /// Return iterator over mutable window renderers + pub fn iter_mut(&mut self) -> IterMut { + self.windows.iter_mut() + } } pub fn get_fitting_videomode( From e3ca69a914034f0440651b24b62ea6a5005df0f5 Mon Sep 17 00:00:00 2001 From: hakolao Date: Mon, 20 Jun 2022 22:24:24 -0400 Subject: [PATCH 06/22] Fix game of life --- examples/src/bin/interactive_fractal/main.rs | 2 +- .../src/bin/multi_window_game_of_life/app.rs | 84 ++++++++++--------- .../multi_window_game_of_life/game_of_life.rs | 17 +++- .../src/bin/multi_window_game_of_life/main.rs | 63 ++++++-------- .../multi_window_game_of_life/render_pass.rs | 8 +- 5 files changed, 90 insertions(+), 84 deletions(-) diff --git a/examples/src/bin/interactive_fractal/main.rs b/examples/src/bin/interactive_fractal/main.rs index e3b69c480a..6a3560142e 100644 --- a/examples/src/bin/interactive_fractal/main.rs +++ b/examples/src/bin/interactive_fractal/main.rs @@ -39,7 +39,7 @@ fn main() { let mut event_loop = EventLoop::new(); let context = VulkanoContext::new(VulkanoConfig::default()); let mut windows = VulkanoWindows::default(); - windows.create_window( + let _id = windows.create_window( &event_loop, &context, &WindowDescriptor { diff --git a/examples/src/bin/multi_window_game_of_life/app.rs b/examples/src/bin/multi_window_game_of_life/app.rs index 4350285d72..dff4c3d64b 100644 --- a/examples/src/bin/multi_window_game_of_life/app.rs +++ b/examples/src/bin/multi_window_game_of_life/app.rs @@ -8,16 +8,14 @@ // according to those terms. use crate::{ - game_of_life::GameOfLifeComputePipeline, render_pass::RenderPassPlaceOverFrame, - vulkano_config::VulkanoConfig, vulkano_context::VulkanoContext, vulkano_window::VulkanoWindow, - SCALING, WINDOW2_HEIGHT, WINDOW2_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH, + game_of_life::GameOfLifeComputePipeline, render_pass::RenderPassPlaceOverFrame, SCALING, + WINDOW2_HEIGHT, WINDOW2_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH, }; use std::{collections::HashMap, sync::Arc}; use vulkano::{device::Queue, format::Format}; -use winit::{ - event_loop::EventLoop, - window::{WindowBuilder, WindowId}, -}; +use vulkano_util::context::{VulkanoConfig, VulkanoContext}; +use vulkano_util::window::{VulkanoWindows, WindowDescriptor}; +use winit::{event_loop::EventLoop, window::WindowId}; pub struct RenderPipeline { pub compute: GameOfLifeComputePipeline, @@ -40,64 +38,72 @@ impl RenderPipeline { pub struct App { pub context: VulkanoContext, - pub windows: HashMap, + pub windows: VulkanoWindows, pub pipelines: HashMap, - pub primary_window_id: WindowId, } impl App { pub fn open(&mut self, event_loop: &EventLoop<()>) { // Create windows & pipelines - let winit_window_primary_builder = WindowBuilder::new() - .with_inner_size(winit::dpi::LogicalSize::new( - WINDOW_WIDTH as f32, - WINDOW_HEIGHT as f32, - )) - .with_title("Game of Life Primary"); - let winit_window_secondary_builder = WindowBuilder::new() - .with_inner_size(winit::dpi::LogicalSize::new( - WINDOW2_WIDTH as f32, - WINDOW2_HEIGHT as f32, - )) - .with_title("Game of Life Secondary"); - let winit_window_primary = winit_window_primary_builder.build(&event_loop).unwrap(); - let winit_window_secondary = winit_window_secondary_builder.build(&event_loop).unwrap(); - let window_primary = VulkanoWindow::new(&self.context, winit_window_primary, false); - let window_secondary = VulkanoWindow::new(&self.context, winit_window_secondary, false); + let id1 = self.windows.create_window( + event_loop, + &self.context, + &WindowDescriptor { + width: WINDOW_WIDTH, + height: WINDOW_HEIGHT, + title: "Game of Life Primary".to_string(), + ..Default::default() + }, + |_| {}, + ); + let id2 = self.windows.create_window( + event_loop, + &self.context, + &WindowDescriptor { + width: WINDOW2_WIDTH, + height: WINDOW2_HEIGHT, + title: "Game of Life Secondary".to_string(), + ..Default::default() + }, + |_| {}, + ); self.pipelines.insert( - window_primary.window().id(), + id1, RenderPipeline::new( // Use same queue.. for synchronization self.context.graphics_queue(), self.context.graphics_queue(), - [WINDOW_WIDTH / SCALING, WINDOW_HEIGHT / SCALING], - window_primary.swapchain_format(), + [ + (WINDOW_WIDTH / SCALING) as u32, + (WINDOW_HEIGHT / SCALING) as u32, + ], + self.windows + .get_primary_renderer() + .unwrap() + .swapchain_format(), ), ); self.pipelines.insert( - window_secondary.window().id(), + id2, RenderPipeline::new( self.context.graphics_queue(), self.context.graphics_queue(), - [WINDOW2_WIDTH / SCALING, WINDOW2_HEIGHT / SCALING], - window_secondary.swapchain_format(), + [ + (WINDOW2_WIDTH / SCALING) as u32, + (WINDOW2_HEIGHT / SCALING) as u32, + ], + self.windows.get_renderer(id2).unwrap().swapchain_format(), ), ); - self.primary_window_id = window_primary.window().id(); - self.windows - .insert(window_primary.window().id(), window_primary); - self.windows - .insert(window_secondary.window().id(), window_secondary); } } impl Default for App { fn default() -> Self { App { - context: VulkanoContext::new(&VulkanoConfig::default()), - windows: HashMap::new(), + context: VulkanoContext::new(VulkanoConfig::default()), + windows: VulkanoWindows::default(), pipelines: HashMap::new(), - primary_window_id: unsafe { WindowId::dummy() }, } } } diff --git a/examples/src/bin/multi_window_game_of_life/game_of_life.rs b/examples/src/bin/multi_window_game_of_life/game_of_life.rs index e10e1240b3..73af000c24 100644 --- a/examples/src/bin/multi_window_game_of_life/game_of_life.rs +++ b/examples/src/bin/multi_window_game_of_life/game_of_life.rs @@ -7,10 +7,10 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use crate::{vulkano_context::DeviceImageView, vulkano_window::create_device_image}; use cgmath::Vector2; use rand::Rng; use std::sync::Arc; +use vulkano::image::{ImageUsage, StorageImage}; use vulkano::{ buffer::{BufferUsage, CpuAccessibleBuffer}, command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer}, @@ -21,6 +21,7 @@ use vulkano::{ pipeline::{ComputePipeline, Pipeline, PipelineBindPoint}, sync::GpuFuture, }; +use vulkano_util::renderer::DeviceImageView; /// Pipeline holding double buffered grid & color image. /// Grids are used to calculate the state, and color image is used to show the output. @@ -64,7 +65,19 @@ impl GameOfLifeComputePipeline { .unwrap() }; - let image = create_device_image(compute_queue.clone(), size, Format::R8G8B8A8_UNORM); + let image = StorageImage::general_purpose_image_view( + compute_queue.clone(), + size, + Format::R8G8B8A8_UNORM, + ImageUsage { + sampled: true, + storage: true, + color_attachment: true, + transfer_dst: true, + ..ImageUsage::none() + }, + ) + .unwrap(); GameOfLifeComputePipeline { compute_queue, compute_life_pipeline, diff --git a/examples/src/bin/multi_window_game_of_life/main.rs b/examples/src/bin/multi_window_game_of_life/main.rs index 29d18259f7..da665b3a88 100644 --- a/examples/src/bin/multi_window_game_of_life/main.rs +++ b/examples/src/bin/multi_window_game_of_life/main.rs @@ -11,19 +11,12 @@ mod app; mod game_of_life; mod pixels_draw; mod render_pass; -mod vulkano_config; -#[allow(unused)] -mod vulkano_context; -#[allow(unused)] -mod vulkano_window; -use crate::{ - app::{App, RenderPipeline}, - vulkano_window::VulkanoWindow, -}; +use crate::app::{App, RenderPipeline}; use cgmath::Vector2; use std::time::Instant; use vulkano::image::ImageAccess; +use vulkano_util::renderer::VulkanoWindowRenderer; use winit::{ event::{ElementState, Event, MouseButton, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -37,17 +30,11 @@ use winit::{ // - how to do a cellular automata simulation using compute shaders // The possibilities are limitless ;) -// Architecture: -// `VulkanoConfig`: Information for device & context creation -// `VulkanoContext`: A struct containing everything related to vulkano device, instance and queues. Separated to make it convenient to handle multiple windows. -// `VulkanoWindow`: A struct owning winit windows and target images. This is used to begin and end frame. And between that you should call your render pipelines. -// `App`: Holds the windows in a hash map, retains id of the primary window, render pipelines per window and context. - -pub const WINDOW_WIDTH: u32 = 1024; -pub const WINDOW_HEIGHT: u32 = 1024; -pub const WINDOW2_WIDTH: u32 = 512; -pub const WINDOW2_HEIGHT: u32 = 512; -pub const SCALING: u32 = 2; +pub const WINDOW_WIDTH: f32 = 1024.0; +pub const WINDOW_HEIGHT: f32 = 1024.0; +pub const WINDOW2_WIDTH: f32 = 512.0; +pub const WINDOW2_HEIGHT: f32 = 512.0; +pub const SCALING: f32 = 2.0; fn main() { println!("Welcome to Vulkano Game of Life\n Use Mouse to draw life on the grid(s)\n"); @@ -105,17 +92,17 @@ fn handle_events( event, window_id, .. } => match event { WindowEvent::CloseRequested => { - if *window_id == app.primary_window_id { + if *window_id == app.windows.primary_window_id().unwrap() { is_running = false; } else { - // Destroy window by removing it... - app.windows.remove(window_id); + // Destroy window by removing its renderer... + app.windows.remove_renderer(*window_id); app.pipelines.remove(window_id); } } // Resize window and its images... WindowEvent::Resized(..) | WindowEvent::ScaleFactorChanged { .. } => { - let vulkano_window = app.windows.get_mut(window_id).unwrap(); + let vulkano_window = app.windows.get_renderer_mut(*window_id).unwrap(); vulkano_window.resize(); } WindowEvent::CursorMoved { position, .. } => { @@ -130,7 +117,7 @@ fn handle_events( if button == &MouseButton::Left && state == &ElementState::Released { mouse_pressed = false; } - if window_id == &app.primary_window_id { + if window_id == &app.windows.primary_window_id().unwrap() { *mouse_pressed_w1 = mouse_pressed; } else { *mouse_pressed_w2 = mouse_pressed; @@ -151,11 +138,12 @@ fn draw_life( mouse_is_pressed_w1: bool, mouse_is_pressed_w2: bool, ) { + let primary_window_id = app.windows.primary_window_id().unwrap(); for (id, window) in app.windows.iter_mut() { - if id == &app.primary_window_id && !mouse_is_pressed_w1 { + if id == &primary_window_id && !mouse_is_pressed_w1 { continue; } - if id != &app.primary_window_id && !mouse_is_pressed_w2 { + if id != &primary_window_id && !mouse_is_pressed_w2 { continue; } let window_size = window.window_size(); @@ -180,34 +168,35 @@ fn draw_life( /// Compute and render per window fn compute_then_render_per_window(app: &mut App) { - for (window_id, vulkano_window) in app.windows.iter_mut() { + let primary_window_id = app.windows.primary_window_id().unwrap(); + for (window_id, window_renderer) in app.windows.iter_mut() { let pipeline = app.pipelines.get_mut(window_id).unwrap(); - if *window_id == app.primary_window_id { - compute_then_render(vulkano_window, pipeline, [1.0, 0.0, 0.0, 1.0], [0.0; 4]); + if *window_id == primary_window_id { + compute_then_render(window_renderer, pipeline, [1.0, 0.0, 0.0, 1.0], [0.0; 4]); } else { - compute_then_render(vulkano_window, pipeline, [0.0, 0.0, 0.0, 1.0], [1.0; 4]); + compute_then_render(window_renderer, pipeline, [0.0, 0.0, 0.0, 1.0], [1.0; 4]); } } } /// Compute game of life, then display result on target image fn compute_then_render( - vulkano_window: &mut VulkanoWindow, + window_renderer: &mut VulkanoWindowRenderer, pipeline: &mut RenderPipeline, life_color: [f32; 4], dead_color: [f32; 4], ) { // Skip this window when minimized - match vulkano_window.window_size() { + match window_renderer.window_size() { [w, h] => { - if w == 0 || h == 0 { + if w == 0.0 || h == 0.0 { return; } } } // Start frame - let before_pipeline_future = match vulkano_window.start_frame() { + let before_pipeline_future = match window_renderer.start_frame() { Err(e) => { println!("{}", e.to_string()); return; @@ -222,12 +211,12 @@ fn compute_then_render( // Render let color_image = pipeline.compute.color_image(); - let target_image = vulkano_window.final_image(); + let target_image = window_renderer.swapchain_image_view(); let after_render = pipeline .place_over_frame .render(after_compute, color_image, target_image); // Finish frame - vulkano_window.finish_frame(after_render); + window_renderer.finish_frame(after_render); } diff --git a/examples/src/bin/multi_window_game_of_life/render_pass.rs b/examples/src/bin/multi_window_game_of_life/render_pass.rs index b48103967e..b186c5b0da 100644 --- a/examples/src/bin/multi_window_game_of_life/render_pass.rs +++ b/examples/src/bin/multi_window_game_of_life/render_pass.rs @@ -7,10 +7,7 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use crate::{ - pixels_draw::PixelsDrawPipeline, - vulkano_context::{DeviceImageView, FinalImageView}, -}; +use crate::pixels_draw::PixelsDrawPipeline; use std::sync::Arc; use vulkano::{ command_buffer::{ @@ -22,6 +19,7 @@ use vulkano::{ render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, sync::GpuFuture, }; +use vulkano_util::renderer::{DeviceImageView, SwapchainImageView}; /// A render pass which places an incoming image over frame filling it pub struct RenderPassPlaceOverFrame { @@ -62,7 +60,7 @@ impl RenderPassPlaceOverFrame { &mut self, before_future: F, view: DeviceImageView, - target: FinalImageView, + target: SwapchainImageView, ) -> Box where F: GpuFuture + 'static, From cf4076c77010905cbef2c662794188bcd0bded94 Mon Sep 17 00:00:00 2001 From: hakolao Date: Mon, 20 Jun 2022 22:59:20 -0400 Subject: [PATCH 07/22] Rename error --- vulkano/src/image/sys.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vulkano/src/image/sys.rs b/vulkano/src/image/sys.rs index aaa3557332..e7b9043c85 100644 --- a/vulkano/src/image/sys.rs +++ b/vulkano/src/image/sys.rs @@ -1605,7 +1605,7 @@ pub enum ImageCreationError { /// A YCbCr format was given, but the image type was not 2D. YcbcrFormatNot2d, - ImageViewCreationFailed(ImageViewCreationError), + DirectImageViewCreationFailed(ImageViewCreationError), } impl error::Error for ImageCreationError { @@ -1739,8 +1739,8 @@ impl fmt::Display for ImageCreationError { "a YCbCr format was given, but the image type was not 2D" ) } - Self::ImageViewCreationFailed(e) => { - write!(fmt, "Image view creation failed {}", e) + Self::DirectImageViewCreationFailed(e) => { + write!(fmt, "Image view creation failed {}", e.to_string()) } } } From 30372ceb8865d1cfd771c18519cde1d1e4cf36fa Mon Sep 17 00:00:00 2001 From: hakolao Date: Mon, 20 Jun 2022 22:59:29 -0400 Subject: [PATCH 08/22] Fix tests --- vulkano/src/image/storage.rs | 80 ++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/vulkano/src/image/storage.rs b/vulkano/src/image/storage.rs index 8c6685564f..aeab443158 100644 --- a/vulkano/src/image/storage.rs +++ b/vulkano/src/image/storage.rs @@ -226,26 +226,6 @@ impl StorageImage { } /// Allows the creation of a simple 2D general purpose image view from `StorageImage`. - /// ## Example - /// ``` - /// use vulkano::image::StorageImage; - /// use vulkano::image::ImageUsage; - /// use vulkano::format::Format; - /// - /// let image = StorageImage::general_purpose_image_view( - /// queue.clone(), - /// size, - /// Format::R8G8B8A8_UNORM, - /// ImageUsage { - /// sampled: true, - /// storage: true, - /// color_attachment: true, - /// transfer_dst: true, - /// ..ImageUsage::none() - /// }, - /// ) - /// .unwrap(); - /// ``` pub fn general_purpose_image_view( queue: Arc, size: [u32; 2], @@ -258,18 +238,22 @@ impl StorageImage { array_layers: 1, }; let flags = ImageCreateFlags::none(); - match StorageImage::with_usage( + let image_result = StorageImage::with_usage( queue.device().clone(), dims, format, usage, flags, Some(queue.family()), - ) { - Ok(image) => match ImageView::new_default(image) { - Ok(view) => Ok(view), - Err(e) => Err(ImageCreationError::ImageViewCreationFailed(e)), - }, + ); + match image_result { + Ok(image) => { + let image_view = ImageView::new_default(image); + match image_view { + Ok(view) => Ok(view), + Err(e) => Err(ImageCreationError::DirectImageViewCreationFailed(e)), + } + } Err(e) => Err(e), } } @@ -369,7 +353,8 @@ where mod tests { use super::StorageImage; use crate::format::Format; - use crate::image::ImageDimensions; + use crate::image::view::ImageViewCreationError; + use crate::image::{ImageAccess, ImageCreationError, ImageDimensions, ImageUsage}; #[test] fn create() { @@ -386,4 +371,45 @@ mod tests { ) .unwrap(); } + + #[test] + fn create_general_purpose_image_view() { + let (device, queue) = gfx_dev_and_queue!(); + let usage = ImageUsage { + transfer_src: true, + transfer_dst: true, + color_attachment: true, + ..ImageUsage::none() + }; + let img_view = StorageImage::general_purpose_image_view( + queue.clone(), + [32, 32], + Format::R8G8B8A8_UNORM, + usage, + ) + .unwrap(); + assert_eq!(img_view.image().usage(), &usage); + } + + #[test] + fn create_general_purpose_image_view_failed() { + let (device, queue) = gfx_dev_and_queue!(); + // Not valid for image view... + let usage = ImageUsage { + transfer_src: true, + ..ImageUsage::none() + }; + let img_result = StorageImage::general_purpose_image_view( + queue.clone(), + [32, 32], + Format::R8G8B8A8_UNORM, + usage, + ); + assert_eq!( + img_result, + Err(ImageCreationError::DirectImageViewCreationFailed( + ImageViewCreationError::ImageMissingUsage + )) + ); + } } From 95284a99d55e52cb697f2e125eba4cfe5461a683 Mon Sep 17 00:00:00 2001 From: hakolao Date: Mon, 20 Jun 2022 23:19:00 -0400 Subject: [PATCH 09/22] Update documentation texts --- vulkano-util/src/renderer.rs | 21 +++++++++++++++------ vulkano-util/src/window.rs | 6 ++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs index 020a5124d5..d19a393059 100644 --- a/vulkano-util/src/renderer.rs +++ b/vulkano-util/src/renderer.rs @@ -34,6 +34,12 @@ pub type DeviceImageView = Arc>; /// Most common image format pub const DEFAULT_IMAGE_FORMAT: Format = Format::R8G8B8A8_UNORM; +/// A window renderer struct holding the winit window surface, functionality to organize and resize +/// swapchain images. +/// +/// Begin rendering with `start_frame` and finish with `finish_frame`. +/// +/// The intended usage of this struct is through `VulkanoWindows`. pub struct VulkanoWindowRenderer { surface: Arc>, graphics_queue: Arc, @@ -41,6 +47,7 @@ pub struct VulkanoWindowRenderer { swap_chain: Arc>, final_views: Vec, /// Additional image views that you can add which are resized with the window. + /// Use associated functions to get access to these. additional_image_views: HashMap, recreate_swapchain: bool, previous_frame_end: Option>, @@ -53,7 +60,8 @@ unsafe impl Send for VulkanoWindowRenderer {} impl VulkanoWindowRenderer { /// Creates a new `VulkanoWindowRenderer` which is used to orchestrate your rendering with Vulkano. - pub fn new( + /// Pass `WindowDescriptor` and optionally a function modifying the `SapchainCreateInfo` parameters. + pub(crate) fn new( vulkano_context: &VulkanoContext, window: winit::window::Window, descriptor: &WindowDescriptor, @@ -85,7 +93,8 @@ impl VulkanoWindowRenderer { } } - /// Creates the swapchain and its images + /// Creates the swapchain and its images based on `WindowDescriptor`. The swapchain creation + /// can be modified with the `swapchain_create_info_modify` function passed as an input. fn create_swap_chain( device: Arc, surface: Arc>, @@ -219,10 +228,10 @@ impl VulkanoWindowRenderer { self.additional_image_views.remove(&key); } - /// This is the first to call in render orchestration. - /// Returns a gpu future representing the time after which the swapchain image has been acquired + /// Begin your rendering by calling `start_frame`. + /// Returns a `GpuFuture` representing the time after which the swapchain image has been acquired /// and previous frame ended. - /// After calling this, you should execute your command buffers and pass your last future to `finish_frame`. + /// Execute your command buffers after calling this function and finish rendering by calling `finish_frame`. pub fn start_frame(&mut self) -> std::result::Result, AcquireError> { // Recreate swap chain if needed (when resizing of window occurs or swapchain is outdated) // Also resize render views if needed @@ -251,7 +260,7 @@ impl VulkanoWindowRenderer { Ok(future.boxed()) } - /// Finishes render by presenting the swapchain. Pass your last future as an input to this function. + /// Finishes rendering by presenting the swapchain. Pass your last future as an input to this function. pub fn finish_frame(&mut self, after_future: Box) { let future = after_future .then_swapchain_present( diff --git a/vulkano-util/src/window.rs b/vulkano-util/src/window.rs index cca34f7232..f621299372 100644 --- a/vulkano-util/src/window.rs +++ b/vulkano-util/src/window.rs @@ -42,6 +42,8 @@ pub struct VulkanoWindows { } impl VulkanoWindows { + /// Creates a winit window with `VulkanoWindowRenderer` based on the given `WindowDescriptor` + /// input and swapchain creation modifications pub fn create_window( &mut self, event_loop: &winit::event_loop::EventLoopWindowTarget<()>, @@ -239,7 +241,7 @@ impl VulkanoWindows { } } -pub fn get_fitting_videomode( +fn get_fitting_videomode( monitor: &winit::monitor::MonitorHandle, width: u32, height: u32, @@ -269,7 +271,7 @@ pub fn get_fitting_videomode( modes.first().unwrap().clone() } -pub fn get_best_videomode(monitor: &winit::monitor::MonitorHandle) -> winit::monitor::VideoMode { +fn get_best_videomode(monitor: &winit::monitor::MonitorHandle) -> winit::monitor::VideoMode { let mut modes = monitor.video_modes().collect::>(); modes.sort_by(|a, b| { use std::cmp::Ordering::*; From 589f17e69b3600fe2534f295eeb1b99b278dfd5d Mon Sep 17 00:00:00 2001 From: hakolao Date: Mon, 20 Jun 2022 23:27:27 -0400 Subject: [PATCH 10/22] Modify license comment --- vulkano-util/src/window.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vulkano-util/src/window.rs b/vulkano-util/src/window.rs index f621299372..c9aaa5088e 100644 --- a/vulkano-util/src/window.rs +++ b/vulkano-util/src/window.rs @@ -1,4 +1,6 @@ -// Mostly a copy from https://github.com/bevyengine/bevy/tree/main/crates/bevy_window, modified to fit Vulkano. +// Modified from https://github.com/bevyengine/bevy/tree/main/crates/bevy_window, to fit Vulkano. +// Their licences: https://github.com/bevyengine/bevy/blob/main/LICENSE-MIT +// https://github.com/bevyengine/bevy/blob/main/LICENSE-APACHE // Copyright (c) 2022 The vulkano developers // Licensed under the Apache License, Version 2.0 From 4013798249d72a216ba573158a5b32f0d3fdae4f Mon Sep 17 00:00:00 2001 From: hakolao Date: Mon, 20 Jun 2022 23:27:38 -0400 Subject: [PATCH 11/22] Add to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 202262625c..21668d1103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ - Fixed bug that triggered an assert if a render pass had an attachment with `Undefined` initial layout. - Added an `is_signaled` method to `FenceSignalFuture`. - Add a simple `general_purpose_image_view` method to `StorageImage` for less verbose image view creation for e.g. intermediary render targets. +- Add a `vulkano_util` crate to help simple use cases. `VulkanoContext` to hold access to device & instances, `VulkanoWindows` to organize windows and their renderers. `VulkanoRenderer` to hold the window and methods to `start_frame` and `end_frame` between which you are intended to execute your pipelines. # Version 0.29.0 (2022-03-11) From fda29af4823b13d8b728f92546dfdd23178c7111 Mon Sep 17 00:00:00 2001 From: hakolao Date: Mon, 20 Jun 2022 23:47:19 -0400 Subject: [PATCH 12/22] Improve docs --- vulkano-util/src/context.rs | 14 ++++++++++++-- vulkano-util/src/renderer.rs | 23 ++++++++++++----------- vulkano-util/src/window.rs | 2 +- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/vulkano-util/src/context.rs b/vulkano-util/src/context.rs index f1954e0de1..199f87f2af 100644 --- a/vulkano-util/src/context.rs +++ b/vulkano-util/src/context.rs @@ -16,12 +16,18 @@ use vulkano::instance::debug::{DebugUtilsMessenger, DebugUtilsMessengerCreateInf use vulkano::instance::{Instance, InstanceCreateInfo}; use vulkano::Version; +/// A config struct to pass various creation options to create [`VulkanoContext`]. +#[derive(Debug)] pub struct VulkanoConfig { instance_create_info: InstanceCreateInfo, + /// Optionally you can pass the `DebugUtilsMessengerCreateInfo` to create the debug callback + /// for printing debug information at runtime. debug_create_info: Option, + /// Pass priority order function for your physical device selection. See default for example. device_priority_fn: fn(device_type: PhysicalDeviceType) -> u32, device_extensions: DeviceExtensions, device_features: Features, + /// Print your selected device name at start. print_device_name: bool, } @@ -51,7 +57,11 @@ impl Default for VulkanoConfig { } } -/// VulkanoContext is a utility struct to create and access Vulkano device(s), queues and so on. +/// A utility struct to create, access and hold alive Vulkano device, instance and queues. +/// +/// Vulkano context is used in the creation of your graphics or compute pipelines, images and +/// in the creation of [`VulkanoWindowRenderer`] through [`VulkanoWindows`]. +/// /// ## Example /// /// ``` @@ -81,7 +91,7 @@ impl Default for VulkanoContext { } impl VulkanoContext { - /// Creates a new `VulkanoContext`. + /// Creates a new [`VulkanoContext`]. /// /// # Panics /// diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs index d19a393059..5a5c6fa205 100644 --- a/vulkano-util/src/renderer.rs +++ b/vulkano-util/src/renderer.rs @@ -34,12 +34,13 @@ pub type DeviceImageView = Arc>; /// Most common image format pub const DEFAULT_IMAGE_FORMAT: Format = Format::R8G8B8A8_UNORM; -/// A window renderer struct holding the winit window surface, functionality to organize and resize -/// swapchain images. +/// A window renderer struct holding the winit window surface and functionality for organizing your render +/// between frames. /// -/// Begin rendering with `start_frame` and finish with `finish_frame`. +/// Begin rendering with [`VulkanoWindowRenderer::start_frame`] and finish with [`VulkanoWindowRenderer::finish_frame`]. +/// Between those, you should execute your command buffers. /// -/// The intended usage of this struct is through `VulkanoWindows`. +/// The intended usage of this struct is through [`crate::window::VulkanoWindows`]. pub struct VulkanoWindowRenderer { surface: Arc>, graphics_queue: Arc, @@ -59,8 +60,8 @@ unsafe impl Sync for VulkanoWindowRenderer {} unsafe impl Send for VulkanoWindowRenderer {} impl VulkanoWindowRenderer { - /// Creates a new `VulkanoWindowRenderer` which is used to orchestrate your rendering with Vulkano. - /// Pass `WindowDescriptor` and optionally a function modifying the `SapchainCreateInfo` parameters. + /// Creates a new [`VulkanoWindowRenderer`] which is used to orchestrate your rendering with Vulkano. + /// Pass [`WindowDescriptor`] and optionally a function modifying the [`SwapchainCreateInfo`](vulkano::swapchain::SwapchainCreateInfo) parameters. pub(crate) fn new( vulkano_context: &VulkanoContext, window: winit::window::Window, @@ -93,7 +94,7 @@ impl VulkanoWindowRenderer { } } - /// Creates the swapchain and its images based on `WindowDescriptor`. The swapchain creation + /// Creates the swapchain and its images based on [`WindowDescriptor`]. The swapchain creation /// can be modified with the `swapchain_create_info_modify` function passed as an input. fn create_swap_chain( device: Arc, @@ -149,12 +150,12 @@ impl VulkanoWindowRenderer { self.image_index } - /// Graphics queue of this window. You also can access this through VulkanoContext + /// Graphics queue of this window. You also can access this through [`VulkanoContext`] pub fn graphics_queue(&self) -> Arc { self.graphics_queue.clone() } - /// Compute queue of this window. You can also access this through VulkanoContext + /// Compute queue of this window. You can also access this through [`VulkanoContext`] pub fn compute_queue(&self) -> Arc { self.compute_queue.clone() } @@ -229,9 +230,9 @@ impl VulkanoWindowRenderer { } /// Begin your rendering by calling `start_frame`. - /// Returns a `GpuFuture` representing the time after which the swapchain image has been acquired + /// Returns a [`GpuFuture`](vulkano::sync::future::GpuFuture) representing the time after which the swapchain image has been acquired /// and previous frame ended. - /// Execute your command buffers after calling this function and finish rendering by calling `finish_frame`. + /// Execute your command buffers after calling this function and finish rendering by calling [`VulkanoWindowRenderer::finish_frame`]. pub fn start_frame(&mut self) -> std::result::Result, AcquireError> { // Recreate swap chain if needed (when resizing of window occurs or swapchain is outdated) // Also resize render views if needed diff --git a/vulkano-util/src/window.rs b/vulkano-util/src/window.rs index c9aaa5088e..1ddc421e34 100644 --- a/vulkano-util/src/window.rs +++ b/vulkano-util/src/window.rs @@ -44,7 +44,7 @@ pub struct VulkanoWindows { } impl VulkanoWindows { - /// Creates a winit window with `VulkanoWindowRenderer` based on the given `WindowDescriptor` + /// Creates a winit window with [`VulkanoWindowRenderer`] based on the given [`WindowDescriptor`] /// input and swapchain creation modifications pub fn create_window( &mut self, From c744c80b7f18d9c3bef9783fdac26129c4055246 Mon Sep 17 00:00:00 2001 From: hakolao Date: Tue, 21 Jun 2022 00:08:24 -0400 Subject: [PATCH 13/22] Allow option to wait on the future --- examples/src/bin/interactive_fractal/main.rs | 5 +++-- .../src/bin/multi_window_game_of_life/main.rs | 4 ++-- vulkano-util/src/renderer.rs | 19 +++++++++++-------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/src/bin/interactive_fractal/main.rs b/examples/src/bin/interactive_fractal/main.rs index 6a3560142e..401ac7beab 100644 --- a/examples/src/bin/interactive_fractal/main.rs +++ b/examples/src/bin/interactive_fractal/main.rs @@ -154,6 +154,7 @@ fn compute_then_render( let after_renderpass_future = app.place_over_frame .render(after_compute, image, renderer.swapchain_image_view()); - // Finish frame (which presents the view). Input last future - renderer.finish_frame(after_renderpass_future); + // Finish frame (which presents the view). Input last future. Wait for the future so resources are not in use + // when we render + renderer.finish_frame(after_renderpass_future, true); } diff --git a/examples/src/bin/multi_window_game_of_life/main.rs b/examples/src/bin/multi_window_game_of_life/main.rs index da665b3a88..5cbf4d38b3 100644 --- a/examples/src/bin/multi_window_game_of_life/main.rs +++ b/examples/src/bin/multi_window_game_of_life/main.rs @@ -217,6 +217,6 @@ fn compute_then_render( .place_over_frame .render(after_compute, color_image, target_image); - // Finish frame - window_renderer.finish_frame(after_render); + // Finish frame. Wait for the future so resources are not in use when we render + window_renderer.finish_frame(after_render, true); } diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs index 5a5c6fa205..aaed7cc9de 100644 --- a/vulkano-util/src/renderer.rs +++ b/vulkano-util/src/renderer.rs @@ -262,7 +262,7 @@ impl VulkanoWindowRenderer { } /// Finishes rendering by presenting the swapchain. Pass your last future as an input to this function. - pub fn finish_frame(&mut self, after_future: Box) { + pub fn finish_frame(&mut self, after_future: Box, wait_future: bool) { let future = after_future .then_swapchain_present( self.graphics_queue.clone(), @@ -271,14 +271,17 @@ impl VulkanoWindowRenderer { ) .then_signal_fence_and_flush(); match future { - Ok(future) => { - // Prevent OutOfMemory error on Nvidia :( - // https://github.com/vulkano-rs/vulkano/issues/627. - // Maybe there's some way to prevent this with synchronization... - match future.wait(None) { - Ok(x) => x, - Err(err) => println!("{:?}", err), + Ok(mut future) => { + if wait_future { + match future.wait(None) { + Ok(x) => x, + Err(err) => println!("{:?}", err), + } + // wait allows you to organize resource waiting yourself. + } else { + future.cleanup_finished(); } + self.previous_frame_end = Some(future.boxed()); } Err(FlushError::OutOfDate) => { From e8a9d69b7b22dcd65b3b7e97a47552d08335fef2 Mon Sep 17 00:00:00 2001 From: hakolao Date: Tue, 21 Jun 2022 00:12:57 -0400 Subject: [PATCH 14/22] Update doc text --- vulkano-util/src/renderer.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs index aaed7cc9de..5e4ee37468 100644 --- a/vulkano-util/src/renderer.rs +++ b/vulkano-util/src/renderer.rs @@ -262,6 +262,9 @@ impl VulkanoWindowRenderer { } /// Finishes rendering by presenting the swapchain. Pass your last future as an input to this function. + /// + /// Depending on your implementation, you may want to wait on your future. For example, a compute shader + /// dispatch using an image that's being later drawn should probably be waited on. pub fn finish_frame(&mut self, after_future: Box, wait_future: bool) { let future = after_future .then_swapchain_present( From db2d3be97697b941d7484389158d6f93ee2e5ab9 Mon Sep 17 00:00:00 2001 From: hakolao Date: Tue, 21 Jun 2022 11:42:47 -0400 Subject: [PATCH 15/22] Add filter fn --- vulkano-util/src/context.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/vulkano-util/src/context.rs b/vulkano-util/src/context.rs index 199f87f2af..2e561e5c5f 100644 --- a/vulkano-util/src/context.rs +++ b/vulkano-util/src/context.rs @@ -17,14 +17,15 @@ use vulkano::instance::{Instance, InstanceCreateInfo}; use vulkano::Version; /// A config struct to pass various creation options to create [`VulkanoContext`]. -#[derive(Debug)] pub struct VulkanoConfig { instance_create_info: InstanceCreateInfo, - /// Optionally you can pass the `DebugUtilsMessengerCreateInfo` to create the debug callback + /// Pass the `DebugUtilsMessengerCreateInfo` to create the debug callback /// for printing debug information at runtime. debug_create_info: Option, + /// Pass filter function for your physical device selection. See default for example. + device_filter_fn: Arc bool>, /// Pass priority order function for your physical device selection. See default for example. - device_priority_fn: fn(device_type: PhysicalDeviceType) -> u32, + device_priority_fn: Arc u32>, device_extensions: DeviceExtensions, device_features: Features, /// Print your selected device name at start. @@ -33,6 +34,10 @@ pub struct VulkanoConfig { impl Default for VulkanoConfig { fn default() -> Self { + let device_extensions = DeviceExtensions { + khr_swapchain: true, + ..DeviceExtensions::none() + }; VulkanoConfig { instance_create_info: InstanceCreateInfo { application_version: Version::V1_2, @@ -40,18 +45,18 @@ impl Default for VulkanoConfig { ..Default::default() }, debug_create_info: None, - device_priority_fn: |p| match p { + device_filter_fn: Arc::new(move |p| { + p.supported_extensions().is_superset_of(&device_extensions) + }), + device_priority_fn: Arc::new(|p| match p.properties().device_type { PhysicalDeviceType::DiscreteGpu => 1, PhysicalDeviceType::IntegratedGpu => 2, PhysicalDeviceType::VirtualGpu => 3, PhysicalDeviceType::Cpu => 4, PhysicalDeviceType::Other => 5, - }, + }), print_device_name: true, - device_extensions: DeviceExtensions { - khr_swapchain: true, - ..DeviceExtensions::none() - }, + device_extensions, device_features: Features::none(), } } @@ -111,7 +116,8 @@ impl VulkanoContext { }; // Get prioritized device let physical_device = PhysicalDevice::enumerate(&instance) - .min_by_key(|p| (config.device_priority_fn)(p.properties().device_type)) + .filter(|p| (config.device_filter_fn)(p)) + .min_by_key(|p| (config.device_priority_fn)(p)) .expect("Failed to create physical device"); // Print used device if config.print_device_name { From 5b493e6289c6f55b2918868b796f35172379b77c Mon Sep 17 00:00:00 2001 From: hakolao Date: Tue, 21 Jun 2022 12:05:30 -0400 Subject: [PATCH 16/22] Modify queue selection --- vulkano-util/src/context.rs | 82 +++++++++++++++--------------------- vulkano-util/src/renderer.rs | 2 +- 2 files changed, 36 insertions(+), 48 deletions(-) diff --git a/vulkano-util/src/context.rs b/vulkano-util/src/context.rs index 2e561e5c5f..6275a4945d 100644 --- a/vulkano-util/src/context.rs +++ b/vulkano-util/src/context.rs @@ -16,7 +16,7 @@ use vulkano::instance::debug::{DebugUtilsMessenger, DebugUtilsMessengerCreateInf use vulkano::instance::{Instance, InstanceCreateInfo}; use vulkano::Version; -/// A config struct to pass various creation options to create [`VulkanoContext`]. +/// A configuration struct to pass various creation options to create [`VulkanoContext`]. pub struct VulkanoConfig { instance_create_info: InstanceCreateInfo, /// Pass the `DebugUtilsMessengerCreateInfo` to create the debug callback @@ -75,6 +75,7 @@ impl Default for VulkanoConfig { /// #[test] /// fn test() { /// let context = VulkanoContext::new(VulkanoConfig::default()); +/// // Then create event loop, windows, pipelines, etc. /// } /// ``` pub struct VulkanoContext { @@ -144,8 +145,8 @@ impl VulkanoContext { } } - /// Creates vulkan device with required queue families and required extensions. Returns an optional secondary queue for compute. - /// However, typically you can just use the gfx queue for that. + /// Creates vulkano device with required queue families and required extensions. Creates a separate queue for compute + /// if possible. If not, same queue as graphics is used. fn create_device( physical: PhysicalDevice, device_extensions: DeviceExtensions, @@ -156,54 +157,41 @@ impl VulkanoContext { .enumerate() .find(|&(_i, q)| q.supports_graphics()) .expect("Could not find a queue that supports graphics"); + // Try finding a separate queue for compute let compute_family_data = physical .queue_families() .enumerate() - .find(|&(i, q)| i != gfx_index && q.supports_compute()); + .find(|&(i, q)| q.supports_compute() && i != gfx_index); - // If we have an extra compute queue: - if let Some((_compute_index, queue_family_compute)) = compute_family_data { - let (device, mut queues) = { - Device::new( - physical, - DeviceCreateInfo { - enabled_extensions: physical - .required_extensions() - .union(&device_extensions), - enabled_features: features, - queue_create_infos: vec![ - QueueCreateInfo::family(queue_family_graphics), - QueueCreateInfo::family(queue_family_compute), - ], - ..Default::default() - }, - ) - .expect("Failed to create device") - }; - let gfx_queue = queues.next().unwrap(); - let compute_queue = queues.next().unwrap(); - (device, gfx_queue, compute_queue) - } - // And if we do not have an extra compute queue, just use the same queue for gfx and compute - else { - let (device, mut queues) = { - Device::new( - physical, - DeviceCreateInfo { - enabled_extensions: physical - .required_extensions() - .union(&device_extensions), - enabled_features: features, - queue_create_infos: vec![QueueCreateInfo::family(queue_family_graphics)], - ..Default::default() - }, - ) - .expect("Failed to create device") - }; - let gfx_queue = queues.next().unwrap(); - let compute_queue = gfx_queue.clone(); - (device, gfx_queue, compute_queue) - } + let is_separate_compute_queue = compute_family_data.is_some(); + let queue_create_infos = if is_separate_compute_queue { + let (_i, queue_family_compute) = compute_family_data.unwrap(); + vec![ + QueueCreateInfo::family(queue_family_graphics), + QueueCreateInfo::family(queue_family_compute), + ] + } else { + vec![QueueCreateInfo::family(queue_family_graphics)] + }; + let (device, mut queues) = { + Device::new( + physical, + DeviceCreateInfo { + enabled_extensions: physical.required_extensions().union(&device_extensions), + enabled_features: features, + queue_create_infos, + ..Default::default() + }, + ) + .expect("Failed to create device") + }; + let gfx_queue = queues.next().unwrap(); + let compute_queue = if is_separate_compute_queue { + queues.next().unwrap() + } else { + gfx_queue.clone() + }; + (device, gfx_queue, compute_queue) } /// Check device name diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs index 5e4ee37468..bf14f76d90 100644 --- a/vulkano-util/src/renderer.rs +++ b/vulkano-util/src/renderer.rs @@ -26,7 +26,7 @@ use vulkano::{ use vulkano_win::create_surface_from_winit; use winit::window::Window; -/// Final render target to which you draw and then display on the window +/// Swapchain Image View. Your final render target typically. pub type SwapchainImageView = Arc>>; /// Multipurpose image view pub type DeviceImageView = Arc>; From c594b6e048efc4da8ee7d3ddfb4bceec50c84381 Mon Sep 17 00:00:00 2001 From: hakolao Date: Tue, 21 Jun 2022 14:15:35 -0400 Subject: [PATCH 17/22] Fix import error --- vulkano-util/src/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vulkano-util/src/context.rs b/vulkano-util/src/context.rs index 6275a4945d..6a16606ca0 100644 --- a/vulkano-util/src/context.rs +++ b/vulkano-util/src/context.rs @@ -239,7 +239,7 @@ fn create_instance(instance_create_info: InstanceCreateInfo) -> Arc { { match Instance::new(instance_create_info) { Err(e) => match e { - InstanceCreationError::LoadingError(le) => { + vulkano::instance::InstanceCreationError::LoadingError(le) => { Err(le).expect("Failed to create instance. Did you install vulkanSDK from https://vulkan.lunarg.com/sdk/home ?") } _ => Err(e).expect("Failed to create instance"), From 41a506d7a7babdbfab047cd0c220aaba6cf9ffeb Mon Sep 17 00:00:00 2001 From: hakolao Date: Tue, 21 Jun 2022 14:27:33 -0400 Subject: [PATCH 18/22] Remove non working tests --- vulkano-util/src/context.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/vulkano-util/src/context.rs b/vulkano-util/src/context.rs index 6a16606ca0..fa75b03433 100644 --- a/vulkano-util/src/context.rs +++ b/vulkano-util/src/context.rs @@ -69,10 +69,9 @@ impl Default for VulkanoConfig { /// /// ## Example /// -/// ``` +/// ```no_run /// use vulkano_util::context::{VulkanoConfig, VulkanoContext}; /// -/// #[test] /// fn test() { /// let context = VulkanoContext::new(VulkanoConfig::default()); /// // Then create event loop, windows, pipelines, etc. @@ -252,15 +251,3 @@ fn create_instance(instance_create_info: InstanceCreateInfo) -> Arc { Instance::new(instance_create_info).expect("Failed to create instance") } } - -#[cfg(test)] -mod tests { - use crate::context::{VulkanoConfig, VulkanoContext}; - - // Simply test creation of the context... - #[test] - fn test_creation() { - let context = VulkanoContext::new(VulkanoConfig::default()); - assert_ne!(context.max_memory(), 0); - } -} From c3659548c27ccac4ea5c47f1305b94770c767a51 Mon Sep 17 00:00:00 2001 From: hakolao Date: Tue, 21 Jun 2022 17:58:55 -0400 Subject: [PATCH 19/22] Rename start and finish frame to acquire and present --- CHANGELOG.md | 2 +- examples/src/bin/interactive_fractal/main.rs | 4 ++-- examples/src/bin/multi_window_game_of_life/main.rs | 4 ++-- vulkano-util/src/renderer.rs | 10 +++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21668d1103..9cc246f6eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,7 @@ - Fixed bug that triggered an assert if a render pass had an attachment with `Undefined` initial layout. - Added an `is_signaled` method to `FenceSignalFuture`. - Add a simple `general_purpose_image_view` method to `StorageImage` for less verbose image view creation for e.g. intermediary render targets. -- Add a `vulkano_util` crate to help simple use cases. `VulkanoContext` to hold access to device & instances, `VulkanoWindows` to organize windows and their renderers. `VulkanoRenderer` to hold the window and methods to `start_frame` and `end_frame` between which you are intended to execute your pipelines. +- Add a `vulkano_util` crate to help reduce boilerplate in many use cases. `VulkanoContext` to hold access to device & instances, `VulkanoWindows` to organize windows and their renderers. `VulkanoRenderer` to hold the window and methods to `acquire` (swapchain) and `present` between which you are intended to execute your pipelines. # Version 0.29.0 (2022-03-11) diff --git a/examples/src/bin/interactive_fractal/main.rs b/examples/src/bin/interactive_fractal/main.rs index 401ac7beab..b4a074d418 100644 --- a/examples/src/bin/interactive_fractal/main.rs +++ b/examples/src/bin/interactive_fractal/main.rs @@ -139,7 +139,7 @@ fn compute_then_render( target_image_id: usize, ) { // Start frame - let before_pipeline_future = match renderer.start_frame() { + let before_pipeline_future = match renderer.acquire() { Err(e) => { println!("{}", e.to_string()); return; @@ -156,5 +156,5 @@ fn compute_then_render( .render(after_compute, image, renderer.swapchain_image_view()); // Finish frame (which presents the view). Input last future. Wait for the future so resources are not in use // when we render - renderer.finish_frame(after_renderpass_future, true); + renderer.present(after_renderpass_future, true); } diff --git a/examples/src/bin/multi_window_game_of_life/main.rs b/examples/src/bin/multi_window_game_of_life/main.rs index 5cbf4d38b3..93c691db3b 100644 --- a/examples/src/bin/multi_window_game_of_life/main.rs +++ b/examples/src/bin/multi_window_game_of_life/main.rs @@ -196,7 +196,7 @@ fn compute_then_render( } // Start frame - let before_pipeline_future = match window_renderer.start_frame() { + let before_pipeline_future = match window_renderer.acquire() { Err(e) => { println!("{}", e.to_string()); return; @@ -218,5 +218,5 @@ fn compute_then_render( .render(after_compute, color_image, target_image); // Finish frame. Wait for the future so resources are not in use when we render - window_renderer.finish_frame(after_render, true); + window_renderer.present(after_render, true); } diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs index bf14f76d90..d5d2a88555 100644 --- a/vulkano-util/src/renderer.rs +++ b/vulkano-util/src/renderer.rs @@ -37,7 +37,7 @@ pub const DEFAULT_IMAGE_FORMAT: Format = Format::R8G8B8A8_UNORM; /// A window renderer struct holding the winit window surface and functionality for organizing your render /// between frames. /// -/// Begin rendering with [`VulkanoWindowRenderer::start_frame`] and finish with [`VulkanoWindowRenderer::finish_frame`]. +/// Begin rendering with [`VulkanoWindowRenderer::acquire`] and finish with [`VulkanoWindowRenderer::present`]. /// Between those, you should execute your command buffers. /// /// The intended usage of this struct is through [`crate::window::VulkanoWindows`]. @@ -229,11 +229,11 @@ impl VulkanoWindowRenderer { self.additional_image_views.remove(&key); } - /// Begin your rendering by calling `start_frame`. + /// Begin your rendering by calling `acquire`. /// Returns a [`GpuFuture`](vulkano::sync::future::GpuFuture) representing the time after which the swapchain image has been acquired /// and previous frame ended. - /// Execute your command buffers after calling this function and finish rendering by calling [`VulkanoWindowRenderer::finish_frame`]. - pub fn start_frame(&mut self) -> std::result::Result, AcquireError> { + /// Execute your command buffers after calling this function and finish rendering by calling [`VulkanoWindowRenderer::present`]. + pub fn acquire(&mut self) -> std::result::Result, AcquireError> { // Recreate swap chain if needed (when resizing of window occurs or swapchain is outdated) // Also resize render views if needed if self.recreate_swapchain { @@ -265,7 +265,7 @@ impl VulkanoWindowRenderer { /// /// Depending on your implementation, you may want to wait on your future. For example, a compute shader /// dispatch using an image that's being later drawn should probably be waited on. - pub fn finish_frame(&mut self, after_future: Box, wait_future: bool) { + pub fn present(&mut self, after_future: Box, wait_future: bool) { let future = after_future .then_swapchain_present( self.graphics_queue.clone(), From 16dab45127a242aa5399505e5e7758ff2497bffd Mon Sep 17 00:00:00 2001 From: hakolao Date: Wed, 22 Jun 2022 21:27:31 -0400 Subject: [PATCH 20/22] Allow pub creation of window renderer --- vulkano-util/src/renderer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs index d5d2a88555..83b2715f6f 100644 --- a/vulkano-util/src/renderer.rs +++ b/vulkano-util/src/renderer.rs @@ -62,7 +62,7 @@ unsafe impl Send for VulkanoWindowRenderer {} impl VulkanoWindowRenderer { /// Creates a new [`VulkanoWindowRenderer`] which is used to orchestrate your rendering with Vulkano. /// Pass [`WindowDescriptor`] and optionally a function modifying the [`SwapchainCreateInfo`](vulkano::swapchain::SwapchainCreateInfo) parameters. - pub(crate) fn new( + pub fn new( vulkano_context: &VulkanoContext, window: winit::window::Window, descriptor: &WindowDescriptor, From 21ffc121ad6140e774c18af786a9f43acca9d5c0 Mon Sep 17 00:00:00 2001 From: hakolao Date: Fri, 24 Jun 2022 10:37:53 -0400 Subject: [PATCH 21/22] Ensure config members are pub and it has send + sync --- vulkano-util/src/context.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/vulkano-util/src/context.rs b/vulkano-util/src/context.rs index fa75b03433..7001536a08 100644 --- a/vulkano-util/src/context.rs +++ b/vulkano-util/src/context.rs @@ -18,20 +18,24 @@ use vulkano::Version; /// A configuration struct to pass various creation options to create [`VulkanoContext`]. pub struct VulkanoConfig { - instance_create_info: InstanceCreateInfo, + pub instance_create_info: InstanceCreateInfo, /// Pass the `DebugUtilsMessengerCreateInfo` to create the debug callback /// for printing debug information at runtime. - debug_create_info: Option, + pub debug_create_info: Option, /// Pass filter function for your physical device selection. See default for example. - device_filter_fn: Arc bool>, + pub device_filter_fn: Arc bool>, /// Pass priority order function for your physical device selection. See default for example. - device_priority_fn: Arc u32>, - device_extensions: DeviceExtensions, - device_features: Features, + pub device_priority_fn: Arc u32>, + pub device_extensions: DeviceExtensions, + pub device_features: Features, /// Print your selected device name at start. - print_device_name: bool, + pub print_device_name: bool, } +unsafe impl Send for VulkanoConfig {} + +unsafe impl Sync for VulkanoConfig {} + impl Default for VulkanoConfig { fn default() -> Self { let device_extensions = DeviceExtensions { @@ -55,7 +59,7 @@ impl Default for VulkanoConfig { PhysicalDeviceType::Cpu => 4, PhysicalDeviceType::Other => 5, }), - print_device_name: true, + print_device_name: false, device_extensions, device_features: Features::none(), } From fd05a6da061c219efcb44bb4cb1632176394ff66 Mon Sep 17 00:00:00 2001 From: hakolao Date: Fri, 24 Jun 2022 11:01:39 -0400 Subject: [PATCH 22/22] Remove send syncs --- vulkano-util/src/context.rs | 8 -------- vulkano-util/src/renderer.rs | 4 ---- 2 files changed, 12 deletions(-) diff --git a/vulkano-util/src/context.rs b/vulkano-util/src/context.rs index 7001536a08..1e0db3f2f6 100644 --- a/vulkano-util/src/context.rs +++ b/vulkano-util/src/context.rs @@ -32,10 +32,6 @@ pub struct VulkanoConfig { pub print_device_name: bool, } -unsafe impl Send for VulkanoConfig {} - -unsafe impl Sync for VulkanoConfig {} - impl Default for VulkanoConfig { fn default() -> Self { let device_extensions = DeviceExtensions { @@ -89,10 +85,6 @@ pub struct VulkanoContext { compute_queue: Arc, } -unsafe impl Sync for VulkanoContext {} - -unsafe impl Send for VulkanoContext {} - impl Default for VulkanoContext { fn default() -> Self { VulkanoContext::new(VulkanoConfig::default()) diff --git a/vulkano-util/src/renderer.rs b/vulkano-util/src/renderer.rs index 83b2715f6f..343b806b65 100644 --- a/vulkano-util/src/renderer.rs +++ b/vulkano-util/src/renderer.rs @@ -55,10 +55,6 @@ pub struct VulkanoWindowRenderer { image_index: usize, } -unsafe impl Sync for VulkanoWindowRenderer {} - -unsafe impl Send for VulkanoWindowRenderer {} - impl VulkanoWindowRenderer { /// Creates a new [`VulkanoWindowRenderer`] which is used to orchestrate your rendering with Vulkano. /// Pass [`WindowDescriptor`] and optionally a function modifying the [`SwapchainCreateInfo`](vulkano::swapchain::SwapchainCreateInfo) parameters.