940 lines
31 KiB
Rust
940 lines
31 KiB
Rust
use anyhow::Result;
|
|
use anyhow::anyhow;
|
|
|
|
use cgmath::vec2;
|
|
use cgmath::vec3;
|
|
|
|
use log::*;
|
|
|
|
use std::collections::HashSet;
|
|
use std::ffi::CStr;
|
|
use std::mem::size_of;
|
|
use std::os::raw::c_void;
|
|
|
|
use thiserror::Error;
|
|
|
|
use vulkanalia::Version;
|
|
use vulkanalia::bytecode::Bytecode;
|
|
use vulkanalia::loader::LIBRARY;
|
|
use vulkanalia::loader::LibloadingLoader;
|
|
use vulkanalia::prelude::v1_0::*;
|
|
use vulkanalia::window as vk_window;
|
|
|
|
use vulkanalia::vk::ExtDebugUtilsExtension;
|
|
use vulkanalia::vk::KhrSurfaceExtension;
|
|
use vulkanalia::vk::KhrSwapchainExtension;
|
|
|
|
use winit::dpi::LogicalSize;
|
|
use winit::event::Event;
|
|
use winit::event::WindowEvent;
|
|
use winit::event_loop::EventLoop;
|
|
use winit::window::Window;
|
|
use winit::window::WindowBuilder;
|
|
|
|
type Vec2f = cgmath::Vector2<f32>;
|
|
type Vec3f = cgmath::Vector3<f32>;
|
|
type Vec4f = cgmath::Vector4<f32>;
|
|
|
|
type Vec2d = cgmath::Vector2<f64>;
|
|
type Vec3d = cgmath::Vector3<f64>;
|
|
type Vec4d = cgmath::Vector4<f64>;
|
|
|
|
type Mat2f = cgmath::Matrix2<f32>;
|
|
type Mat3f = cgmath::Matrix3<f32>;
|
|
type Mat4f = cgmath::Matrix4<f32>;
|
|
|
|
type Mat2d = cgmath::Matrix2<f64>;
|
|
type Mat3d = cgmath::Matrix3<f64>;
|
|
type Mat4d = cgmath::Matrix4<f64>;
|
|
|
|
const VALIDATION_ENABLED:bool = cfg!(debug_assertions); // add env support?
|
|
const VALIDATION_LAYER:vk::ExtensionName = vk::ExtensionName::from_bytes(b"VK_LAYER_KHRONOS_validation");
|
|
|
|
const DEVICE_EXTENSIONS:&[vk::ExtensionName] = &[vk::KHR_SWAPCHAIN_EXTENSION.name];
|
|
const PORTABILITY_MACOS_VERSION:Version = Version::new(1, 3, 216);
|
|
|
|
const WINDOW_TITLE:&'static str = "MineMod";
|
|
const VK_APPLICATION_NAME:&'static [u8; 8] = b"minemod\0";
|
|
const VK_ENGINE_NAME:&'static [u8; 10] = b"cataclysm\0";
|
|
const MAX_FRAMES_IN_FLIGHT:usize = 2;
|
|
|
|
macro_rules! const_shaders {
|
|
{ $($vis:vis $identifier:ident = $string:literal; )* } => {
|
|
$(
|
|
#[allow(non_upper_case_globals)]
|
|
$vis static $identifier: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders/", $string));
|
|
)*
|
|
};
|
|
}
|
|
|
|
pub fn main() -> Result<()> {
|
|
tracing_subscriber::fmt()
|
|
.compact()
|
|
.with_timer(tracing_subscriber::fmt::time::uptime())
|
|
.with_ansi(true)
|
|
.with_level(true)
|
|
// .with_thread_ids(true)
|
|
.with_thread_names(true)
|
|
.with_max_level(if cfg!(debug_assertions) {
|
|
tracing::level_filters::LevelFilter::DEBUG
|
|
} else {
|
|
tracing::level_filters::LevelFilter::INFO
|
|
})
|
|
.init();
|
|
// pretty_env_logger::init_timed();
|
|
|
|
info!("Registering CTRLC hook.");
|
|
|
|
let (shutdown_tx, shutdown_rx) = std::sync::mpsc::channel();
|
|
|
|
ctrlc::set_handler(move || {
|
|
shutdown_tx.send(()).expect("Failed to send shutdown signal");
|
|
})
|
|
.expect("Error setting Ctrl-C handler");
|
|
|
|
info!("Initializing event loop and winit window instance.");
|
|
|
|
// Window
|
|
|
|
let event_loop = EventLoop::new()?;
|
|
let window = WindowBuilder::new()
|
|
.with_title(WINDOW_TITLE)
|
|
.with_inner_size(LogicalSize::new(1024, 768))
|
|
.build(&event_loop)?;
|
|
|
|
info!("Creating app and starting event loop.");
|
|
|
|
// App
|
|
|
|
let mut app = unsafe { App::create(&window)? };
|
|
let mut minimized = false;
|
|
|
|
let shutdown_rx = std::sync::Arc::new(std::sync::Mutex::new(Some(shutdown_rx)));
|
|
|
|
event_loop.run(move |event, elwt| {
|
|
let mut shutdown_rx_guard = shutdown_rx.lock().unwrap();
|
|
|
|
if let Some(receiver) = shutdown_rx_guard.as_mut() {
|
|
if receiver.try_recv().is_ok() {
|
|
info!("Closing event loop and destroying Vulkan instance.");
|
|
elwt.exit();
|
|
unsafe {
|
|
app.device.device_wait_idle().unwrap();
|
|
app.destroy();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
match event {
|
|
// Request a redraw when all events were processed.
|
|
Event::AboutToWait => window.request_redraw(),
|
|
Event::WindowEvent { event, .. } => match event {
|
|
// Render a frame if our Vulkan app is not being destroyed.
|
|
WindowEvent::RedrawRequested if !elwt.exiting() && !minimized => {
|
|
unsafe { app.render(&window) }.unwrap();
|
|
},
|
|
WindowEvent::Resized(size) =>
|
|
if size.width == 0 || size.height == 0 {
|
|
minimized = true;
|
|
} else {
|
|
minimized = false;
|
|
app.resized = true;
|
|
},
|
|
// Destroy our Vulkan app.
|
|
WindowEvent::CloseRequested => {
|
|
info!("Closing event loop and destroying Vulkan instance.");
|
|
elwt.exit();
|
|
unsafe {
|
|
app.device.device_wait_idle().unwrap();
|
|
app.destroy();
|
|
}
|
|
},
|
|
_ => {},
|
|
},
|
|
_ => {},
|
|
}
|
|
})?;
|
|
|
|
info!("Exiting program.");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Our Vulkan app.
|
|
#[derive(Clone, Debug)]
|
|
struct App {
|
|
entry: Entry,
|
|
instance:Instance,
|
|
data: AppData,
|
|
device: Device,
|
|
frame: usize, // current frame
|
|
resized: bool,
|
|
}
|
|
|
|
impl App {
|
|
/// Creates our Vulkan app.
|
|
unsafe fn create(window:&Window) -> Result<Self> {
|
|
let loader = LibloadingLoader::new(LIBRARY)?;
|
|
let entry = Entry::new(loader).map_err(|b| anyhow!("{}", b))?;
|
|
let mut data = AppData::default();
|
|
let instance = create_instance(window, &entry, &mut data)?;
|
|
data.surface = vk_window::create_surface(&instance, &window, &window)?;
|
|
pick_physical_device(&instance, &mut data)?;
|
|
let device = create_logical_device(&entry, &instance, &mut data)?;
|
|
create_swapchain(window, &instance, &device, &mut data)?;
|
|
create_swapchain_image_views(&device, &mut data)?;
|
|
create_render_pass(&instance, &device, &mut data)?;
|
|
create_pipeline(&device, &mut data)?;
|
|
create_framebuffers(&device, &mut data)?;
|
|
create_command_pool(&instance, &device, &mut data)?;
|
|
create_command_buffers(&device, &mut data)?;
|
|
create_sync_objects(&device, &mut data)?;
|
|
Ok(Self {
|
|
entry,
|
|
instance,
|
|
data,
|
|
device,
|
|
frame:0,
|
|
resized:false,
|
|
})
|
|
}
|
|
|
|
/// Renders a frame for our Vulkan app.
|
|
unsafe fn render(&mut self, window:&Window) -> Result<()> {
|
|
self.device.wait_for_fences(&[self.data.in_flight_fences[self.frame]], true, u64::MAX)?;
|
|
|
|
let result = self.device.acquire_next_image_khr(
|
|
self.data.swapchain,
|
|
u64::MAX,
|
|
self.data.image_available_semaphores[self.frame],
|
|
vk::Fence::null(),
|
|
);
|
|
|
|
let image_index = match result {
|
|
Ok((image_index, _)) => image_index as usize,
|
|
Err(vk::ErrorCode::OUT_OF_DATE_KHR) => return self.recreate_swapchain(window),
|
|
Err(e) => return Err(anyhow!(e)),
|
|
};
|
|
|
|
if !self.data.images_in_flight[image_index as usize].is_null() {
|
|
self.device
|
|
.wait_for_fences(&[self.data.images_in_flight[image_index as usize]], true, u64::MAX)?;
|
|
}
|
|
|
|
self.data.images_in_flight[image_index as usize] = self.data.in_flight_fences[self.frame];
|
|
|
|
let wait_semaphores = &[self.data.image_available_semaphores[self.frame]];
|
|
let wait_stages = &[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
|
|
let command_buffers = &[self.data.command_buffers[image_index as usize]];
|
|
let signal_semaphores = &[self.data.render_finished_semaphores[self.frame]];
|
|
let submit_info = vk::SubmitInfo::builder()
|
|
.wait_semaphores(wait_semaphores)
|
|
.wait_dst_stage_mask(wait_stages)
|
|
.command_buffers(command_buffers)
|
|
.signal_semaphores(signal_semaphores);
|
|
|
|
self.device.reset_fences(&[self.data.in_flight_fences[self.frame]])?;
|
|
|
|
self.device
|
|
.queue_submit(self.data.graphics_queue, &[submit_info], self.data.in_flight_fences[self.frame])?;
|
|
|
|
let swapchains = &[self.data.swapchain];
|
|
let image_indices = &[image_index as u32];
|
|
let present_info = vk::PresentInfoKHR::builder()
|
|
.wait_semaphores(signal_semaphores)
|
|
.swapchains(swapchains)
|
|
.image_indices(image_indices);
|
|
|
|
let result = self.device.queue_present_khr(self.data.present_queue, &present_info);
|
|
|
|
let changed = result == Ok(vk::SuccessCode::SUBOPTIMAL_KHR) || result == Err(vk::ErrorCode::OUT_OF_DATE_KHR);
|
|
|
|
if self.resized || changed {
|
|
self.resized = false;
|
|
self.recreate_swapchain(window)?;
|
|
} else if let Err(e) = result {
|
|
return Err(anyhow!(e));
|
|
}
|
|
|
|
// self.device.queue_wait_idle(self.data.present_queue)?;
|
|
|
|
self.frame = (self.frame + 1) % MAX_FRAMES_IN_FLIGHT;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn recreate_swapchain(&mut self, window:&Window) -> Result<()> {
|
|
self.device.device_wait_idle()?;
|
|
self.destroy_swapchain();
|
|
create_swapchain(window, &self.instance, &self.device, &mut self.data)?;
|
|
create_swapchain_image_views(&self.device, &mut self.data)?;
|
|
create_render_pass(&self.instance, &self.device, &mut self.data)?;
|
|
create_pipeline(&self.device, &mut self.data)?;
|
|
create_framebuffers(&self.device, &mut self.data)?;
|
|
create_command_buffers(&self.device, &mut self.data)?;
|
|
self.data.images_in_flight.resize(self.data.swapchain_images.len(), vk::Fence::null());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn destroy_swapchain(&mut self) {
|
|
self.data.framebuffers.iter().for_each(|f| self.device.destroy_framebuffer(*f, None));
|
|
self.device.free_command_buffers(self.data.command_pool, &self.data.command_buffers);
|
|
self.device.destroy_pipeline(self.data.pipeline, None);
|
|
self.device.destroy_pipeline_layout(self.data.pipeline_layout, None);
|
|
self.device.destroy_render_pass(self.data.render_pass, None);
|
|
self.data.swapchain_image_views.iter().for_each(|v| self.device.destroy_image_view(*v, None));
|
|
self.device.destroy_swapchain_khr(self.data.swapchain, None);
|
|
}
|
|
|
|
/// Destroys our Vulkan app.
|
|
unsafe fn destroy(&mut self) {
|
|
self.destroy_swapchain();
|
|
|
|
self.data.in_flight_fences.iter().for_each(|f| self.device.destroy_fence(*f, None));
|
|
self.data
|
|
.render_finished_semaphores
|
|
.iter()
|
|
.for_each(|s| self.device.destroy_semaphore(*s, None));
|
|
self.data
|
|
.image_available_semaphores
|
|
.iter()
|
|
.for_each(|s| self.device.destroy_semaphore(*s, None));
|
|
|
|
self.device.destroy_command_pool(self.data.command_pool, None);
|
|
self.device.destroy_device(None);
|
|
self.instance.destroy_surface_khr(self.data.surface, None);
|
|
|
|
if VALIDATION_ENABLED {
|
|
self.instance.destroy_debug_utils_messenger_ext(self.data.messenger, None);
|
|
}
|
|
|
|
self.instance.destroy_instance(None);
|
|
}
|
|
}
|
|
|
|
/// The Vulkan handles and associated properties used by our Vulkan app.
|
|
#[derive(Clone, Debug, Default)]
|
|
struct AppData {
|
|
messenger: vk::DebugUtilsMessengerEXT,
|
|
surface: vk::SurfaceKHR,
|
|
physical_device: vk::PhysicalDevice,
|
|
graphics_queue: vk::Queue,
|
|
present_queue: vk::Queue,
|
|
swapchain_format: vk::Format,
|
|
swapchain_extent: vk::Extent2D,
|
|
swapchain: vk::SwapchainKHR,
|
|
swapchain_images: Vec<vk::Image>,
|
|
swapchain_image_views: Vec<vk::ImageView>,
|
|
render_pass: vk::RenderPass,
|
|
pipeline_layout: vk::PipelineLayout,
|
|
pipeline: vk::Pipeline,
|
|
framebuffers: Vec<vk::Framebuffer>,
|
|
command_pool: vk::CommandPool,
|
|
command_buffers: Vec<vk::CommandBuffer>,
|
|
image_available_semaphores:Vec<vk::Semaphore>,
|
|
render_finished_semaphores:Vec<vk::Semaphore>,
|
|
in_flight_fences: Vec<vk::Fence>,
|
|
images_in_flight: Vec<vk::Fence>,
|
|
}
|
|
|
|
unsafe fn create_instance(window:&Window, entry:&Entry, data:&mut AppData) -> Result<Instance> {
|
|
let application_info = vk::ApplicationInfo::builder()
|
|
.application_name(VK_APPLICATION_NAME)
|
|
.application_version(vk::make_version(0, 1, 0))
|
|
.engine_name(VK_ENGINE_NAME)
|
|
.engine_version(vk::make_version(0, 1, 0))
|
|
.api_version(vk::make_version(1, 0, 0));
|
|
|
|
let available_layers = entry
|
|
.enumerate_instance_layer_properties()?
|
|
.iter()
|
|
.map(|l| l.layer_name)
|
|
.collect::<HashSet<_>>();
|
|
|
|
if VALIDATION_ENABLED && !available_layers.contains(&VALIDATION_LAYER) {
|
|
return Err(anyhow!("Validation layer requested but not supported."));
|
|
}
|
|
|
|
let layers = if VALIDATION_ENABLED { vec![VALIDATION_LAYER.as_ptr()] } else { Vec::new() };
|
|
|
|
let mut extensions = vk_window::get_required_instance_extensions(window)
|
|
.iter()
|
|
.map(|e| e.as_ptr())
|
|
.collect::<Vec<_>>();
|
|
|
|
// needed for semaphores, unix only?
|
|
extensions.push(vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION.name.as_ptr());
|
|
|
|
// Required by Vulkan SDK on macOS since 1.3.216.
|
|
let flags = if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION {
|
|
info!("Enabling extensions for macOS portability.");
|
|
// extensions.push( // already present above
|
|
// vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION
|
|
// .name
|
|
// .as_ptr(),
|
|
// );
|
|
extensions.push(vk::KHR_PORTABILITY_ENUMERATION_EXTENSION.name.as_ptr());
|
|
vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR
|
|
} else {
|
|
vk::InstanceCreateFlags::empty()
|
|
};
|
|
if VALIDATION_ENABLED {
|
|
extensions.push(vk::EXT_DEBUG_UTILS_EXTENSION.name.as_ptr());
|
|
}
|
|
|
|
let mut info = vk::InstanceCreateInfo::builder()
|
|
.application_info(&application_info)
|
|
.enabled_layer_names(&layers)
|
|
.enabled_extension_names(&extensions)
|
|
.flags(flags);
|
|
|
|
let mut debug_info = vk::DebugUtilsMessengerCreateInfoEXT::builder()
|
|
.message_severity(vk::DebugUtilsMessageSeverityFlagsEXT::all())
|
|
.message_type(
|
|
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
|
|
)
|
|
.user_callback(Some(debug_callback));
|
|
|
|
if VALIDATION_ENABLED {
|
|
info = info.push_next(&mut debug_info);
|
|
}
|
|
|
|
let instance = entry.create_instance(&info, None)?;
|
|
|
|
if VALIDATION_ENABLED {
|
|
data.messenger = instance.create_debug_utils_messenger_ext(&debug_info, None)?;
|
|
}
|
|
|
|
Ok(instance)
|
|
}
|
|
|
|
extern "system" fn debug_callback(
|
|
severity:vk::DebugUtilsMessageSeverityFlagsEXT, type_:vk::DebugUtilsMessageTypeFlagsEXT, data:*const vk::DebugUtilsMessengerCallbackDataEXT, _:*mut c_void,
|
|
) -> vk::Bool32 {
|
|
let data = unsafe { *data };
|
|
let message = unsafe { CStr::from_ptr(data.message) }.to_string_lossy();
|
|
|
|
if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::ERROR {
|
|
error!("({:?}) {}", type_, message);
|
|
} else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING {
|
|
warn!("({:?}) {}", type_, message);
|
|
} else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::INFO {
|
|
debug!("({:?}) {}", type_, message);
|
|
} else {
|
|
trace!("({:?}) {}", type_, message);
|
|
}
|
|
|
|
vk::FALSE
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
#[error("{0}")]
|
|
pub struct SuitabilityError(pub &'static str);
|
|
|
|
unsafe fn pick_physical_device(instance:&Instance, data:&mut AppData) -> Result<()> {
|
|
for physical_device in instance.enumerate_physical_devices()? {
|
|
let properties = instance.get_physical_device_properties(physical_device);
|
|
|
|
if let Err(error) = check_physical_device(instance, data, physical_device) {
|
|
warn!("Skipping physical device (`{}`): {}", properties.device_name, error);
|
|
} else {
|
|
info!("Selected physical device (`{}`).", properties.device_name);
|
|
data.physical_device = physical_device;
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
Err(anyhow!("Failed to find suitable physical device."))
|
|
}
|
|
unsafe fn check_physical_device(instance:&Instance, data:&AppData, physical_device:vk::PhysicalDevice) -> Result<()> {
|
|
QueueFamilyIndices::get(instance, data, physical_device)?;
|
|
check_physical_device_extensions(instance, physical_device)?;
|
|
|
|
let support = SwapchainSupport::get(instance, data, physical_device)?;
|
|
if support.formats.is_empty() || support.present_modes.is_empty() {
|
|
return Err(anyhow!(SuitabilityError("Insufficient swapchain support.")));
|
|
}
|
|
|
|
// // TODO handle this like the other one?
|
|
// let properties = instance.get_physical_device_properties(physical_device);
|
|
// if properties.device_type != vk::PhysicalDeviceType::DISCRETE_GPU {
|
|
// return Err(anyhow!(SuitabilityError(
|
|
// "Only discrete GPUs are supported."
|
|
// )));
|
|
// }
|
|
|
|
// let features = instance.get_physical_device_features(physical_device);
|
|
// let required_features = [
|
|
// (features.geometry_shader, "Missing geometry shader support."),
|
|
// // ( // needed for vr
|
|
// // features.multi_viewport,
|
|
// // "Missing support for multiple viewports.",
|
|
// // ),
|
|
// ];
|
|
|
|
// for (feature, string) in required_features {
|
|
// if feature != vk::TRUE {
|
|
// return Err(anyhow!(SuitabilityError(string)));
|
|
// }
|
|
// }
|
|
|
|
Ok(())
|
|
}
|
|
unsafe fn check_physical_device_extensions(instance:&Instance, physical_device:vk::PhysicalDevice) -> Result<()> {
|
|
let extensions = instance
|
|
.enumerate_device_extension_properties(physical_device, None)?
|
|
.iter()
|
|
.map(|e| e.extension_name)
|
|
.collect::<HashSet<_>>();
|
|
if DEVICE_EXTENSIONS.iter().all(|e| extensions.contains(e)) {
|
|
Ok(())
|
|
} else {
|
|
Err(anyhow!(SuitabilityError("Missing required device extensions.")))
|
|
}
|
|
}
|
|
|
|
unsafe fn create_logical_device(entry:&Entry, instance:&Instance, data:&mut AppData) -> Result<Device> {
|
|
let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?;
|
|
|
|
let mut unique_indices = HashSet::new();
|
|
unique_indices.insert(indices.graphics);
|
|
unique_indices.insert(indices.present);
|
|
|
|
let queue_priorities = &[1.0];
|
|
let queue_infos = unique_indices
|
|
.iter()
|
|
.map(|i| vk::DeviceQueueCreateInfo::builder().queue_family_index(*i).queue_priorities(queue_priorities))
|
|
.collect::<Vec<_>>();
|
|
|
|
let layers = if VALIDATION_ENABLED { vec![VALIDATION_LAYER.as_ptr()] } else { vec![] };
|
|
|
|
let mut extensions = DEVICE_EXTENSIONS.iter().map(|n| n.as_ptr()).collect::<Vec<_>>();
|
|
|
|
// Required by Vulkan SDK on macOS since 1.3.216.
|
|
if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION {
|
|
extensions.push(vk::KHR_PORTABILITY_SUBSET_EXTENSION.name.as_ptr());
|
|
}
|
|
|
|
let features = vk::PhysicalDeviceFeatures::builder();
|
|
|
|
let info = vk::DeviceCreateInfo::builder()
|
|
.queue_create_infos(&queue_infos)
|
|
.enabled_layer_names(&layers)
|
|
.enabled_extension_names(&extensions)
|
|
.enabled_features(&features);
|
|
|
|
let device = instance.create_device(data.physical_device, &info, None)?;
|
|
|
|
data.graphics_queue = device.get_device_queue(indices.graphics, 0);
|
|
data.present_queue = device.get_device_queue(indices.present, 0);
|
|
|
|
Ok(device)
|
|
}
|
|
|
|
unsafe fn create_swapchain(
|
|
window:&Window,
|
|
instance:&Instance,
|
|
device:&Device,
|
|
data:&mut AppData,
|
|
// old_swapchain: Option<vk::SwapchainKHR>
|
|
) -> Result<()> {
|
|
let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?;
|
|
let support = SwapchainSupport::get(instance, data, data.physical_device)?;
|
|
|
|
let surface_format = get_swapchain_surface_format(&support.formats);
|
|
let present_mode = get_swapchain_present_mode(&support.present_modes);
|
|
let extent = get_swapchain_extent(window, support.capabilities);
|
|
|
|
data.swapchain_format = surface_format.format;
|
|
data.swapchain_extent = extent;
|
|
|
|
let mut image_count = support.capabilities.min_image_count + 1;
|
|
if support.capabilities.max_image_count != 0 && image_count > support.capabilities.max_image_count {
|
|
image_count = support.capabilities.max_image_count;
|
|
}
|
|
|
|
let mut queue_family_indices = vec![];
|
|
let image_sharing_mode = if indices.graphics != indices.present {
|
|
queue_family_indices.push(indices.graphics);
|
|
queue_family_indices.push(indices.present);
|
|
vk::SharingMode::CONCURRENT
|
|
} else {
|
|
vk::SharingMode::EXCLUSIVE
|
|
};
|
|
|
|
let info = vk::SwapchainCreateInfoKHR::builder()
|
|
.surface(data.surface)
|
|
.min_image_count(image_count)
|
|
.image_format(surface_format.format)
|
|
.image_color_space(surface_format.color_space)
|
|
.image_extent(extent)
|
|
.image_array_layers(1)
|
|
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
|
.image_sharing_mode(image_sharing_mode)
|
|
.queue_family_indices(&queue_family_indices)
|
|
.pre_transform(support.capabilities.current_transform)
|
|
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
|
|
.present_mode(present_mode)
|
|
.clipped(true)
|
|
.old_swapchain(vk::SwapchainKHR::null());
|
|
// .old_swapchain(data.swapchain); // TODO if experiencing issues replace with vk::SwapchainKHR::null()
|
|
|
|
data.swapchain = device.create_swapchain_khr(&info, None)?;
|
|
|
|
data.swapchain_images = device.get_swapchain_images_khr(data.swapchain)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn get_swapchain_surface_format(formats:&[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR {
|
|
formats
|
|
.iter()
|
|
.cloned()
|
|
.find(|f| f.format == vk::Format::B8G8R8A8_SRGB && f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR)
|
|
.unwrap_or_else(|| formats[0])
|
|
}
|
|
|
|
fn get_swapchain_present_mode(present_modes:&[vk::PresentModeKHR]) -> vk::PresentModeKHR {
|
|
present_modes
|
|
.iter()
|
|
.cloned()
|
|
.find(|m| *m == vk::PresentModeKHR::MAILBOX)
|
|
.unwrap_or(vk::PresentModeKHR::FIFO)
|
|
}
|
|
|
|
fn get_swapchain_extent(window:&Window, capabilities:vk::SurfaceCapabilitiesKHR) -> vk::Extent2D {
|
|
if capabilities.current_extent.width != u32::MAX {
|
|
capabilities.current_extent
|
|
} else {
|
|
vk::Extent2D::builder()
|
|
.width(
|
|
window
|
|
.inner_size()
|
|
.width
|
|
.clamp(capabilities.min_image_extent.width, capabilities.max_image_extent.width),
|
|
)
|
|
.height(
|
|
window
|
|
.inner_size()
|
|
.height
|
|
.clamp(capabilities.min_image_extent.height, capabilities.max_image_extent.height),
|
|
)
|
|
.build()
|
|
}
|
|
}
|
|
|
|
unsafe fn create_swapchain_image_views(device:&Device, data:&mut AppData) -> Result<()> {
|
|
data.swapchain_image_views = data
|
|
.swapchain_images
|
|
.iter()
|
|
.map(|i| {
|
|
let components = vk::ComponentMapping::builder()
|
|
.r(vk::ComponentSwizzle::IDENTITY)
|
|
.g(vk::ComponentSwizzle::IDENTITY)
|
|
.b(vk::ComponentSwizzle::IDENTITY)
|
|
.a(vk::ComponentSwizzle::IDENTITY);
|
|
|
|
let subresource_range = vk::ImageSubresourceRange::builder()
|
|
.aspect_mask(vk::ImageAspectFlags::COLOR)
|
|
.base_mip_level(0)
|
|
.level_count(1)
|
|
.base_array_layer(0)
|
|
.layer_count(1);
|
|
|
|
let info = vk::ImageViewCreateInfo::builder()
|
|
.image(*i)
|
|
.view_type(vk::ImageViewType::_2D)
|
|
.format(data.swapchain_format)
|
|
.components(components)
|
|
.subresource_range(subresource_range);
|
|
|
|
device.create_image_view(&info, None)
|
|
})
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn create_render_pass(instance:&Instance, device:&Device, data:&mut AppData) -> Result<()> {
|
|
let color_attachment = vk::AttachmentDescription::builder()
|
|
.format(data.swapchain_format)
|
|
.samples(vk::SampleCountFlags::_1)
|
|
.load_op(vk::AttachmentLoadOp::CLEAR)
|
|
.store_op(vk::AttachmentStoreOp::STORE)
|
|
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
|
|
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
|
|
.initial_layout(vk::ImageLayout::UNDEFINED)
|
|
.final_layout(vk::ImageLayout::PRESENT_SRC_KHR);
|
|
|
|
let color_attachment_ref = vk::AttachmentReference::builder()
|
|
.attachment(0)
|
|
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
|
|
|
|
let color_attachments = &[color_attachment_ref];
|
|
let subpass = vk::SubpassDescription::builder()
|
|
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
|
|
.color_attachments(color_attachments);
|
|
|
|
let dependency = vk::SubpassDependency::builder()
|
|
.src_subpass(vk::SUBPASS_EXTERNAL)
|
|
.dst_subpass(0)
|
|
.src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
|
|
.src_access_mask(vk::AccessFlags::empty())
|
|
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
|
|
.dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE);
|
|
|
|
let attachments = &[color_attachment];
|
|
let subpasses = &[subpass];
|
|
let dependencies = &[dependency];
|
|
let info = vk::RenderPassCreateInfo::builder()
|
|
.attachments(attachments)
|
|
.subpasses(subpasses)
|
|
.dependencies(dependencies);
|
|
|
|
data.render_pass = device.create_render_pass(&info, None)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn create_pipeline(device:&Device, data:&mut AppData) -> Result<()> {
|
|
const_shaders! {
|
|
frag = "f_default.spv";
|
|
vert = "v_default.spv";
|
|
}
|
|
|
|
let vert_shader_module = create_shader_module(device, &vert[..])?;
|
|
let frag_shader_module = create_shader_module(device, &frag[..])?;
|
|
|
|
let vert_stage = vk::PipelineShaderStageCreateInfo::builder()
|
|
.stage(vk::ShaderStageFlags::VERTEX)
|
|
.module(vert_shader_module)
|
|
.name(b"main\0"); // keep specialization_info in mind for pipeline creation optimizations that dont happen at render time
|
|
|
|
let frag_stage = vk::PipelineShaderStageCreateInfo::builder()
|
|
.stage(vk::ShaderStageFlags::FRAGMENT)
|
|
.module(frag_shader_module)
|
|
.name(b"main\0");
|
|
|
|
let vertex_input_state = vk::PipelineVertexInputStateCreateInfo::builder();
|
|
|
|
let input_assembly_state = vk::PipelineInputAssemblyStateCreateInfo::builder()
|
|
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
|
|
.primitive_restart_enable(false);
|
|
|
|
let viewport = vk::Viewport::builder()
|
|
.x(0.0)
|
|
.y(0.0)
|
|
.width(data.swapchain_extent.width as f32)
|
|
.height(data.swapchain_extent.height as f32)
|
|
.min_depth(0.0)
|
|
.max_depth(1.0);
|
|
|
|
let scissor = vk::Rect2D::builder().offset(vk::Offset2D { x:0, y:0 }).extent(data.swapchain_extent);
|
|
|
|
let viewports = &[viewport];
|
|
let scissors = &[scissor];
|
|
let viewport_state = vk::PipelineViewportStateCreateInfo::builder().viewports(viewports).scissors(scissors);
|
|
|
|
let rasterization_state = vk::PipelineRasterizationStateCreateInfo::builder()
|
|
.depth_clamp_enable(false)
|
|
.rasterizer_discard_enable(false)
|
|
.polygon_mode(vk::PolygonMode::FILL)
|
|
.line_width(1.0)
|
|
.cull_mode(vk::CullModeFlags::BACK)
|
|
.front_face(vk::FrontFace::CLOCKWISE)
|
|
.depth_bias_enable(false);
|
|
|
|
let multisample_state = vk::PipelineMultisampleStateCreateInfo::builder()
|
|
.sample_shading_enable(false)
|
|
.rasterization_samples(vk::SampleCountFlags::_1);
|
|
|
|
let attachment = vk::PipelineColorBlendAttachmentState::builder()
|
|
.color_write_mask(vk::ColorComponentFlags::all())
|
|
// .blend_enable(false);
|
|
.blend_enable(true)
|
|
.src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
|
|
.dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
|
|
.color_blend_op(vk::BlendOp::ADD)
|
|
.src_alpha_blend_factor(vk::BlendFactor::ONE)
|
|
.dst_alpha_blend_factor(vk::BlendFactor::ZERO)
|
|
.alpha_blend_op(vk::BlendOp::ADD);
|
|
|
|
let attachments = &[attachment];
|
|
let color_blend_state = vk::PipelineColorBlendStateCreateInfo::builder()
|
|
.logic_op_enable(false)
|
|
.logic_op(vk::LogicOp::COPY)
|
|
.attachments(attachments)
|
|
.blend_constants([0.0, 0.0, 0.0, 0.0]);
|
|
|
|
let layout_info = vk::PipelineLayoutCreateInfo::builder();
|
|
|
|
data.pipeline_layout = device.create_pipeline_layout(&layout_info, None)?;
|
|
|
|
let stages = &[vert_stage, frag_stage];
|
|
let info = vk::GraphicsPipelineCreateInfo::builder()
|
|
.stages(stages)
|
|
.vertex_input_state(&vertex_input_state)
|
|
.input_assembly_state(&input_assembly_state)
|
|
.viewport_state(&viewport_state)
|
|
.rasterization_state(&rasterization_state)
|
|
.multisample_state(&multisample_state)
|
|
.color_blend_state(&color_blend_state)
|
|
.layout(data.pipeline_layout)
|
|
.render_pass(data.render_pass)
|
|
.subpass(0)
|
|
.base_pipeline_handle(vk::Pipeline::null()) // Optional.
|
|
.base_pipeline_index(-1); // Optional.
|
|
|
|
data.pipeline = device.create_graphics_pipelines(vk::PipelineCache::null(), &[info], None)?.0[0];
|
|
|
|
device.destroy_shader_module(vert_shader_module, None);
|
|
device.destroy_shader_module(frag_shader_module, None);
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn create_shader_module(device:&Device, bytecode:&[u8]) -> Result<vk::ShaderModule> {
|
|
let bytecode = Bytecode::new(bytecode).unwrap();
|
|
|
|
let info = vk::ShaderModuleCreateInfo::builder().code_size(bytecode.code_size()).code(bytecode.code());
|
|
|
|
Ok(device.create_shader_module(&info, None)?)
|
|
}
|
|
|
|
unsafe fn create_framebuffers(device:&Device, data:&mut AppData) -> Result<()> {
|
|
data.framebuffers = data
|
|
.swapchain_image_views
|
|
.iter()
|
|
.map(|i| {
|
|
let attachments = &[*i];
|
|
let create_info = vk::FramebufferCreateInfo::builder()
|
|
.render_pass(data.render_pass)
|
|
.attachments(attachments)
|
|
.width(data.swapchain_extent.width)
|
|
.height(data.swapchain_extent.height)
|
|
.layers(1);
|
|
|
|
device.create_framebuffer(&create_info, None)
|
|
})
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
Ok(())
|
|
}
|
|
unsafe fn create_command_pool(instance:&Instance, device:&Device, data:&mut AppData) -> Result<()> {
|
|
let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?;
|
|
|
|
let info = vk::CommandPoolCreateInfo::builder()
|
|
.flags(vk::CommandPoolCreateFlags::empty()) // Optional.
|
|
.queue_family_index(indices.graphics);
|
|
|
|
data.command_pool = device.create_command_pool(&info, None)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn create_command_buffers(device:&Device, data:&mut AppData) -> Result<()> {
|
|
let allocate_info = vk::CommandBufferAllocateInfo::builder()
|
|
.command_pool(data.command_pool)
|
|
.level(vk::CommandBufferLevel::PRIMARY)
|
|
.command_buffer_count(data.framebuffers.len() as u32);
|
|
|
|
data.command_buffers = device.allocate_command_buffers(&allocate_info)?;
|
|
|
|
for (i, command_buffer) in data.command_buffers.iter().enumerate() {
|
|
let inheritance = vk::CommandBufferInheritanceInfo::builder();
|
|
|
|
let info = vk::CommandBufferBeginInfo::builder()
|
|
.flags(vk::CommandBufferUsageFlags::empty()) // Optional.
|
|
.inheritance_info(&inheritance); // Optional.
|
|
|
|
device.begin_command_buffer(*command_buffer, &info)?;
|
|
|
|
let render_area = vk::Rect2D::builder().offset(vk::Offset2D::default()).extent(data.swapchain_extent);
|
|
|
|
let color_clear_value = vk::ClearValue {
|
|
color:vk::ClearColorValue { float32:[0.0, 0.0, 0.0, 1.0] },
|
|
};
|
|
|
|
let clear_values = &[color_clear_value];
|
|
let info = vk::RenderPassBeginInfo::builder()
|
|
.render_pass(data.render_pass)
|
|
.framebuffer(data.framebuffers[i])
|
|
.render_area(render_area)
|
|
.clear_values(clear_values);
|
|
|
|
device.cmd_begin_render_pass(*command_buffer, &info, vk::SubpassContents::INLINE);
|
|
device.cmd_bind_pipeline(*command_buffer, vk::PipelineBindPoint::GRAPHICS, data.pipeline);
|
|
device.cmd_draw(*command_buffer, 3, 1, 0, 0);
|
|
|
|
device.cmd_end_render_pass(*command_buffer);
|
|
device.end_command_buffer(*command_buffer)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
unsafe fn create_sync_objects(device:&Device, data:&mut AppData) -> Result<()> {
|
|
let semaphore_info = vk::SemaphoreCreateInfo::builder();
|
|
let fence_info = vk::FenceCreateInfo::builder().flags(vk::FenceCreateFlags::SIGNALED);
|
|
|
|
for _ in 0..MAX_FRAMES_IN_FLIGHT {
|
|
data.image_available_semaphores.push(device.create_semaphore(&semaphore_info, None)?);
|
|
data.render_finished_semaphores.push(device.create_semaphore(&semaphore_info, None)?);
|
|
|
|
data.in_flight_fences.push(device.create_fence(&fence_info, None)?);
|
|
}
|
|
|
|
data.images_in_flight = data.swapchain_images.iter().map(|_| vk::Fence::null()).collect();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
struct QueueFamilyIndices {
|
|
graphics:u32,
|
|
present: u32,
|
|
}
|
|
|
|
impl QueueFamilyIndices {
|
|
unsafe fn get(instance:&Instance, data:&AppData, physical_device:vk::PhysicalDevice) -> Result<Self> {
|
|
let properties = instance.get_physical_device_queue_family_properties(physical_device);
|
|
|
|
let graphics = properties
|
|
.iter()
|
|
.position(|p| p.queue_flags.contains(vk::QueueFlags::GRAPHICS))
|
|
.map(|i| i as u32);
|
|
|
|
let mut present = None;
|
|
for (index, properties) in properties.iter().enumerate() {
|
|
if instance.get_physical_device_surface_support_khr(physical_device, index as u32, data.surface)? {
|
|
present = Some(index as u32);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Note that it's very likely that these end up being the same queue family after all, but throughout the program we will treat them as if they were separate queues for a uniform approach. Nevertheless, you could add logic to explicitly prefer a physical device that supports drawing and presentation in the same queue for improved performance.
|
|
if let (Some(graphics), Some(present)) = (graphics, present) {
|
|
Ok(Self { graphics, present })
|
|
} else {
|
|
Err(anyhow!(SuitabilityError("Missing required queue families.")))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct SwapchainSupport {
|
|
capabilities: vk::SurfaceCapabilitiesKHR,
|
|
formats: Vec<vk::SurfaceFormatKHR>,
|
|
present_modes:Vec<vk::PresentModeKHR>,
|
|
}
|
|
|
|
impl SwapchainSupport {
|
|
unsafe fn get(instance:&Instance, data:&AppData, physical_device:vk::PhysicalDevice) -> Result<Self> {
|
|
Ok(Self {
|
|
capabilities: instance.get_physical_device_surface_capabilities_khr(physical_device, data.surface)?,
|
|
formats: instance.get_physical_device_surface_formats_khr(physical_device, data.surface)?,
|
|
present_modes:instance.get_physical_device_surface_present_modes_khr(physical_device, data.surface)?,
|
|
})
|
|
}
|
|
}
|