mirror of
https://github.com/vulkano-rs/vulkano.git
synced 2024-11-21 22:34:43 +00:00
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:
parent
3ae9ec0f70
commit
18f68337a5
@ -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)
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
[workspace]
|
||||
members = ["examples", "vulkano", "vulkano-shaders", "vulkano-win"]
|
||||
members = ["examples", "vulkano", "vulkano-shaders", "vulkano-win", "vulkano-util"]
|
||||
exclude = ["www"]
|
||||
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
}
|
@ -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() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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![],
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
17
vulkano-util/Cargo.toml
Normal 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
201
vulkano-util/LICENSE-APACHE
Normal 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
25
vulkano-util/LICENSE-MIT
Normal 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
249
vulkano-util/src/context.rs
Normal 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
12
vulkano-util/src/lib.rs
Normal 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;
|
331
vulkano-util/src/renderer.rs
Normal file
331
vulkano-util/src/renderer.rs
Normal 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
426
vulkano-util/src/window.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
Loading…
Reference in New Issue
Block a user