Vulkano util proposal (#1918)

* Init vulkano util library

* Add window & renderer structs

* Update fractal to use vulkano utils

* Add general purpose image view generation to storage image

* Add some missing functionality

* Fix game of life

* Rename error

* Fix tests

* Update documentation texts

* Modify license comment

* Add to changelog

* Improve docs

* Allow option to wait on the future

* Update doc text

* Add filter fn

* Modify queue selection

* Fix import error

* Remove non working tests

* Rename start and finish frame to acquire and present

* Allow pub creation of window renderer

* Ensure config members are pub and it has send + sync

* Remove send syncs
This commit is contained in:
Okko Hakola 2022-06-24 11:27:33 -04:00 committed by GitHub
parent 3ae9ec0f70
commit 18f68337a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1560 additions and 1337 deletions

View File

@ -47,6 +47,8 @@
- 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.
- 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)

View File

@ -1,3 +1,3 @@
[workspace]
members = ["examples", "vulkano", "vulkano-shaders", "vulkano-win"]
members = ["examples", "vulkano", "vulkano-shaders", "vulkano-win", "vulkano-util"]
exclude = ["www"]

View File

@ -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"

View File

@ -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<Queue>, 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<dyn GpuFuture> {
pub fn compute(&mut self, image_target: DeviceImageView) -> Box<dyn GpuFuture> {
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 {

View File

@ -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>,
queue: Arc<Queue>,
pipeline: Arc<ComputePipeline>,
palette: Arc<CpuAccessibleBuffer<[[f32; 4]]>>,
palette_size: i32,
@ -30,7 +30,7 @@ pub struct FractalComputePipeline {
}
impl FractalComputePipeline {
pub fn new(gfx_queue: Arc<Queue>) -> FractalComputePipeline {
pub fn new(queue: Arc<Queue>) -> 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<f32>,
scale: Vector2<f32>,
translation: Vector2<f32>,
@ -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()
}
}

View File

@ -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();
let _id = 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.
let render_target_id = 0;
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()
},
);
// 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());
// 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,9 +133,13 @@ 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() {
let before_pipeline_future = match renderer.acquire() {
Err(e) => {
println!("{}", e.to_string());
return;
@ -124,14 +147,14 @@ 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);
// Finish frame (which presents the view). Input last future
renderer.finish_frame(after_renderpass_future);
app.place_over_frame
.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.present(after_renderpass_future, true);
}

View File

@ -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<F>(
&mut self,
before_future: F,
view: InterimImageView,
target: FinalImageView,
view: DeviceImageView,
target: SwapchainImageView,
) -> Box<dyn GpuFuture>
where
F: GpuFuture + 'static,

View File

@ -1,472 +0,0 @@
// Copyright (c) 2021 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// 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<ImageView<SwapchainImage<Window>>>;
/// Other intermediate render targets
pub type InterimImageView = Arc<ImageView<AttachmentImage>>;
/// 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<Instance>,
device: Arc<Device>,
surface: Arc<Surface<Window>>,
queue: Arc<Queue>,
swapchain: Arc<Swapchain<Window>>,
image_index: usize,
final_views: Vec<FinalImageView>,
/// 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<usize, (InterimImageView, bool)>,
recreate_swapchain: bool,
previous_frame_end: Option<Box<dyn GpuFuture>>,
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<Surface<Window>>,
) -> (Arc<Device>, Arc<Queue>) {
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<Surface<Window>>,
physical: PhysicalDevice,
device: Arc<Device>,
present_mode: PresentMode,
) -> (Arc<Swapchain<Window>>, Vec<FinalImageView>) {
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::<Vec<_>>();
(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<Device> {
self.device.clone()
}
/// Access rendering queue
pub fn queue(&self) -> Arc<Queue> {
self.queue.clone()
}
/// Render target surface
#[allow(unused)]
pub fn surface(&self) -> Arc<Surface<Window>> {
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<Box<dyn GpuFuture>, 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<dyn GpuFuture>) {
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::<Vec<_>>();
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::<Vec<usize>>();
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<F>(
renderer: &mut Renderer,
before_pipeline_future: F,
image: InterimImageView,
) -> Box<dyn GpuFuture>
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()
}

View File

@ -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<WindowId, VulkanoWindow>,
pub windows: VulkanoWindows,
pub pipelines: HashMap<WindowId, RenderPipeline>,
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() },
}
}
}

View File

@ -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,

View File

@ -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.acquire() {
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);
// Finish frame. Wait for the future so resources are not in use when we render
window_renderer.present(after_render, true);
}

View File

@ -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<dyn GpuFuture>
where
F: GpuFuture + 'static,

View File

@ -1,42 +0,0 @@
// Copyright (c) 2022 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// 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<String>,
}
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![],
}
}
}

View File

@ -1,338 +0,0 @@
// Copyright (c) 2022 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// 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<ImageView<SwapchainImage<Window>>>;
/// Multipurpose image view
pub type DeviceImageView = Arc<ImageView<StorageImage>>;
/// 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<Instance>,
device: Arc<Device>,
graphics_queue: Arc<Queue>,
compute_queue: Arc<Queue>,
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<Device>, Arc<Queue>, Arc<Queue>) {
// 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<Surface<Window>>,
present_mode: PresentMode,
) -> (Arc<Swapchain<Window>>, Vec<FinalImageView>) {
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::<Vec<_>>();
(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<Instance> {
self.instance.clone()
}
/// Access device
pub fn device(&self) -> Arc<Device> {
self.device.clone()
}
/// Access rendering queue
pub fn graphics_queue(&self) -> Arc<Queue> {
self.graphics_queue.clone()
}
/// Access compute queue
pub fn compute_queue(&self) -> Arc<Queue> {
self.compute_queue.clone()
}
}
// Create vk instance with given layers
pub fn create_vk_instance(
instance_extensions: InstanceExtensions,
layers: Vec<String>,
) -> Arc<Instance> {
// 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<Instance>, 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()
}
}

View File

@ -1,329 +0,0 @@
// Copyright (c) 2022 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// 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<Surface<Window>>,
graphics_queue: Arc<Queue>,
swapchain: Arc<Swapchain<Window>>,
final_views: Vec<FinalImageView>,
/// Image view that is to be rendered with our pipeline.
/// (bool refers to whether it should get resized with swapchain resize)
image_views: HashMap<usize, (DeviceImageView, bool)>,
recreate_swapchain: bool,
previous_frame_end: Option<Box<dyn GpuFuture>>,
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<Queue> {
self.graphics_queue.clone()
}
/// Render target surface
pub fn surface(&self) -> Arc<Surface<Window>> {
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<Box<dyn GpuFuture>, 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<dyn GpuFuture>) {
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::<Vec<_>>();
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::<Vec<usize>>();
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<Queue>, 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<Queue>,
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()
}

17
vulkano-util/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[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" }
vulkano-win = { version = "0.29.0", path = "../vulkano-win" }
winit = { version = "0.26" }

201
vulkano-util/LICENSE-APACHE Normal file
View File

@ -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.

25
vulkano-util/LICENSE-MIT Normal file
View File

@ -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.

249
vulkano-util/src/context.rs Normal file
View File

@ -0,0 +1,249 @@
// Copyright (c) 2022 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// 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::{
Device, DeviceCreateInfo, DeviceExtensions, Features, Queue, QueueCreateInfo,
};
use vulkano::instance::debug::{DebugUtilsMessenger, DebugUtilsMessengerCreateInfo};
use vulkano::instance::{Instance, InstanceCreateInfo};
use vulkano::Version;
/// A configuration struct to pass various creation options to create [`VulkanoContext`].
pub struct VulkanoConfig {
pub instance_create_info: InstanceCreateInfo,
/// Pass the `DebugUtilsMessengerCreateInfo` to create the debug callback
/// for printing debug information at runtime.
pub debug_create_info: Option<DebugUtilsMessengerCreateInfo>,
/// Pass filter function for your physical device selection. See default for example.
pub device_filter_fn: Arc<dyn Fn(&PhysicalDevice) -> bool>,
/// Pass priority order function for your physical device selection. See default for example.
pub device_priority_fn: Arc<dyn Fn(&PhysicalDevice) -> u32>,
pub device_extensions: DeviceExtensions,
pub device_features: Features,
/// Print your selected device name at start.
pub print_device_name: bool,
}
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,
enabled_extensions: vulkano_win::required_extensions(),
..Default::default()
},
debug_create_info: None,
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: false,
device_extensions,
device_features: Features::none(),
}
}
}
/// 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
///
/// ```no_run
/// use vulkano_util::context::{VulkanoConfig, VulkanoContext};
///
/// fn test() {
/// let context = VulkanoContext::new(VulkanoConfig::default());
/// // Then create event loop, windows, pipelines, etc.
/// }
/// ```
pub struct VulkanoContext {
instance: Arc<Instance>,
_debug_utils_messenger: Option<DebugUtilsMessenger>,
device: Arc<Device>,
graphics_queue: Arc<Queue>,
compute_queue: Arc<Queue>,
}
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)
.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 {
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 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,
features: Features,
) -> (Arc<Device>, Arc<Queue>, Arc<Queue>) {
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");
// Try finding a separate queue for compute
let compute_family_data = physical
.queue_families()
.enumerate()
.find(|&(i, q)| q.supports_compute() && i != gfx_index);
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
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<Instance> {
self.instance.clone()
}
/// Access device
pub fn device(&self) -> Arc<Device> {
self.device.clone()
}
/// Access rendering queue
pub fn graphics_queue(&self) -> Arc<Queue> {
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<Queue> {
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<Instance> {
#[cfg(target_os = "macos")]
{
match Instance::new(instance_create_info) {
Err(e) => match e {
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"),
},
Ok(i) => i,
}
}
#[cfg(not(target_os = "macos"))]
{
Instance::new(instance_create_info).expect("Failed to create instance")
}
}

12
vulkano-util/src/lib.rs Normal file
View File

@ -0,0 +1,12 @@
// Copyright (c) 2022 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
pub mod context;
pub mod renderer;
pub mod window;

View File

@ -0,0 +1,331 @@
// Copyright (c) 2022 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// 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::{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;
/// Swapchain Image View. Your final render target typically.
pub type SwapchainImageView = Arc<ImageView<SwapchainImage<Window>>>;
/// Multipurpose image view
pub type DeviceImageView = Arc<ImageView<StorageImage>>;
/// Most common image format
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::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`].
pub struct VulkanoWindowRenderer {
surface: Arc<Surface<Window>>,
graphics_queue: Arc<Queue>,
compute_queue: Arc<Queue>,
swap_chain: Arc<Swapchain<Window>>,
final_views: Vec<SwapchainImageView>,
/// 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<usize, DeviceImageView>,
recreate_swapchain: bool,
previous_frame_end: Option<Box<dyn GpuFuture>>,
image_index: usize,
}
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 fn new(
vulkano_context: &VulkanoContext,
window: winit::window::Window,
descriptor: &WindowDescriptor,
swapchain_create_info_modify: fn(&mut 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(),
descriptor,
swapchain_create_info_modify,
);
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 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<Device>,
surface: Arc<Surface<Window>>,
window_descriptor: &WindowDescriptor,
swapchain_create_info_modify: fn(&mut SwapchainCreateInfo),
) -> (Arc<Swapchain<Window>>, Vec<SwapchainImageView>) {
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, {
let mut create_info = 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(),
..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()
.map(|image| ImageView::new_default(image).unwrap())
.collect::<Vec<_>>();
(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<Queue> {
self.graphics_queue.clone()
}
/// Compute queue of this window. You can also access this through [`VulkanoContext`]
pub fn compute_queue(&self) -> Arc<Queue> {
self.compute_queue.clone()
}
/// Render target surface
pub fn surface(&self) -> Arc<Surface<Window>> {
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 = StorageImage::general_purpose_image_view(
self.graphics_queue.clone(),
size,
format,
usage,
)
.unwrap();
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);
}
/// 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::present`].
pub fn acquire(&mut self) -> std::result::Result<Box<dyn GpuFuture>, 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 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 present(&mut self, after_future: Box<dyn GpuFuture>, wait_future: bool) {
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(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) => {
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::<Vec<_>>();
self.final_views = new_images;
// Resize images that follow swapchain size
let resizable_views = self
.additional_image_views
.iter()
.map(|c| *c.0)
.collect::<Vec<usize>>();
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;
}
}

426
vulkano-util/src/window.rs Normal file
View File

@ -0,0 +1,426 @@
// 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
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// 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::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.
///
/// ## 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();
/// 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
/// }
/// ```
#[derive(Default)]
pub struct VulkanoWindows {
windows: HashMap<winit::window::WindowId, VulkanoWindowRenderer>,
primary: Option<winit::window::WindowId>,
}
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<()>,
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;
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::<f64>(*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::<f64>(*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);
let id = winit_window.id();
if self.primary.is_none() {
self.primary = Some(id);
}
self.windows.insert(
id,
VulkanoWindowRenderer::new(
vulkano_context,
winit_window,
window_descriptor,
swapchain_create_info_modify,
),
);
id
}
/// 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()))
}
/// Return primary window id
pub fn primary_window_id(&self) -> Option<winit::window::WindowId> {
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<WindowId, VulkanoWindowRenderer> {
self.windows.iter()
}
/// Return iterator over mutable window renderers
pub fn iter_mut(&mut self) -> IterMut<WindowId, VulkanoWindowRenderer> {
self.windows.iter_mut()
}
}
fn get_fitting_videomode(
monitor: &winit::monitor::MonitorHandle,
width: u32,
height: u32,
) -> winit::monitor::VideoMode {
let mut modes = monitor.video_modes().collect::<Vec<_>>();
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()
}
fn get_best_videomode(monitor: &winit::monitor::MonitorHandle) -> winit::monitor::VideoMode {
let mut modes = monitor.video_modes().collect::<Vec<_>>();
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<f64>,
/// 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,
}
}
}

View File

@ -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,39 @@ impl StorageImage {
}))
}
/// Allows the creation of a simple 2D general purpose image view from `StorageImage`.
pub fn general_purpose_image_view(
queue: Arc<Queue>,
size: [u32; 2],
format: Format,
usage: ImageUsage,
) -> Result<Arc<ImageView<StorageImage>>, ImageCreationError> {
let dims = ImageDimensions::Dim2d {
width: size[0],
height: size[1],
array_layers: 1,
};
let flags = ImageCreateFlags::none();
let image_result = StorageImage::with_usage(
queue.device().clone(),
dims,
format,
usage,
flags,
Some(queue.family()),
);
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),
}
}
/// 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<File, DeviceMemoryExportError> {
@ -318,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() {
@ -335,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
))
);
}
}

View File

@ -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,
DirectImageViewCreationFailed(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::DirectImageViewCreationFailed(e) => {
write!(fmt, "Image view creation failed {}", e.to_string())
}
}
}
}

View File

@ -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),