summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs31
-rw-r--r--src/renderer/backend.rs13
-rw-r--r--src/renderer/dummy.rs55
-rw-r--r--src/renderer/error.rs132
-rw-r--r--src/renderer/mod.rs46
-rw-r--r--src/renderer/text.rs72
-rw-r--r--src/renderer/vulkan.rs951
7 files changed, 1300 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..0fbf198
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,31 @@
+#![allow(unused)]
+
+mod renderer;
+
+use renderer::backend::RendererBackend;
+use renderer::dummy::DummyRenderer;
+use renderer::text::{TextSpan, TextStyle};
+use renderer::vulkan::VulkanRenderer;
+
+use winit::{application::ApplicationHandler, event_loop::EventLoop};
+
+fn main() {
+ // Dummy content
+ let span = TextSpan::new("Hello, Emacs!\nThis is a test", TextStyle::default_bold());
+
+ // Initialize backend
+ let mut backend: Box<dyn RendererBackend> = Box::new(VulkanRenderer::new());
+ backend.init(128, 128);
+ println!("The backend is {}", backend.name());
+
+ // Create loop
+
+ let mut event_loop = EventLoop::new().unwrap();
+
+ event_loop.run_app(&mut backend).unwrap();
+
+ backend.draw_text(&span);
+ backend.present();
+ backend.clear();
+ backend.shutdown();
+}
diff --git a/src/renderer/backend.rs b/src/renderer/backend.rs
new file mode 100644
index 0000000..0aa994a
--- /dev/null
+++ b/src/renderer/backend.rs
@@ -0,0 +1,13 @@
+use winit::application::ApplicationHandler;
+
+use super::text::TextSpan;
+
+/// Trait that all renderer backends must implement.
+pub trait RendererBackend: ApplicationHandler {
+ fn name(&self) -> &str;
+ fn init(&mut self, width: i32, height: i32) -> bool;
+ fn clear(&mut self);
+ fn draw_text(&mut self, span: &TextSpan);
+ fn present(&mut self);
+ fn shutdown(&mut self);
+}
diff --git a/src/renderer/dummy.rs b/src/renderer/dummy.rs
new file mode 100644
index 0000000..6a5b378
--- /dev/null
+++ b/src/renderer/dummy.rs
@@ -0,0 +1,55 @@
+use winit::application::ApplicationHandler;
+
+use super::backend::RendererBackend;
+use super::text::TextSpan;
+
+#[derive(Debug, Clone)]
+pub struct DummyRenderer;
+
+impl DummyRenderer {
+ pub fn new() -> Self {
+ Self
+ }
+}
+
+impl RendererBackend for DummyRenderer {
+ fn init(&mut self, _width: i32, _height: i32) -> bool {
+ println!("DummyRenderer initialized.");
+ true
+ }
+
+ fn draw_text(&mut self, span: &TextSpan) {
+ println!("[style: {:?}] {}", span.style, span.text);
+ }
+
+ fn shutdown(&mut self) {
+ println!("DummyRenderer shutting down.");
+ }
+
+ fn name(&self) -> &str {
+ "Dummy"
+ }
+
+ fn clear(&mut self) {
+ println!("DummyRenderer clearing.");
+ }
+
+ fn present(&mut self) {
+ println!("DummyRenderer is presenting.");
+ }
+}
+
+impl ApplicationHandler for DummyRenderer {
+ fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
+ println!("resumed");
+ }
+
+ fn window_event(
+ &mut self,
+ event_loop: &winit::event_loop::ActiveEventLoop,
+ window_id: winit::window::WindowId,
+ event: winit::event::WindowEvent,
+ ) {
+ println!("received an event: {event:?}");
+ }
+}
diff --git a/src/renderer/error.rs b/src/renderer/error.rs
new file mode 100644
index 0000000..f1418e8
--- /dev/null
+++ b/src/renderer/error.rs
@@ -0,0 +1,132 @@
+#![allow(unused)]
+
+use winit::error as werror;
+
+use vulkanalia::{bytecode::BytecodeError, loader::LoaderError, prelude::v1_0::*};
+
+pub(super) enum MainError {
+ NotSupported(werror::NotSupportedError),
+ Os(werror::OsError),
+ Event(werror::EventLoopError),
+ Code(i32),
+ LibLoadError(String),
+ BoxLoadError(Box<dyn LoaderError>),
+ ValidationNotSupported,
+ Suitability(SuitabilityError),
+ NoDevice,
+ NoExtension,
+ InsufficientSwapchainSupport,
+ InvalidBytecode(usize),
+ AllocBytecode,
+}
+
+impl std::fmt::Debug for MainError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Code(code) => write!(f, "{:?}", vk::ErrorCode::from_raw(*code)),
+ Self::NotSupported(e) => write!(f, "{e:?}"),
+ Self::Os(e) => write!(f, "{e:?}"),
+ Self::Event(e) => write!(f, "{e:?}"),
+ Self::BoxLoadError(e) => write!(f, "{e:?}"),
+ Self::LibLoadError(e) => write!(f, "{e:?}"),
+ Self::ValidationNotSupported => write!(f, "ValidationNotSupported"),
+ Self::Suitability(e) => write!(f, "suitability: {e:?}"),
+ Self::NoDevice => write!(f, "No suitable device is found"),
+ Self::NoExtension => write!(f, "Some required device extension is not supported"),
+ Self::InsufficientSwapchainSupport => {
+ write!(f, "The swapchain support is insufficient.")
+ }
+ Self::InvalidBytecode(len) => write!(
+ f,
+ "The length of the SPIRV byte code should be a multiple of four, but got {len}"
+ ),
+ Self::AllocBytecode => write!(f, "Failed to allocate space to store SPIRV byte codes"),
+ }
+ }
+}
+
+impl std::fmt::Display for MainError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ MainError::Event(e) => write!(f, "Event loop error: {e}"),
+ MainError::NotSupported(not) => write!(f, "Not supported: {not}"),
+ MainError::Os(e) => write!(f, "Oeration system: {e}"),
+ MainError::Code(code) => write!(f, "{}", vk::ErrorCode::from_raw(*code)),
+ MainError::BoxLoadError(e) => write!(f, "Boxed loading error: {e}"),
+ MainError::LibLoadError(e) => write!(f, "Library loading error: {e}"),
+ MainError::ValidationNotSupported => write!(
+ f,
+ "The validation layers are requested but are not available."
+ ),
+ MainError::Suitability(e) => write!(f, "Suitability error: {e}"),
+ MainError::NoDevice => write!(f, "No suitable device is found"),
+ MainError::NoExtension => write!(f, "Some required device extension is not supported"),
+ MainError::InsufficientSwapchainSupport => {
+ write!(f, "The swapchain support is insufficient.")
+ }
+ MainError::InvalidBytecode(len) => write!(
+ f,
+ "The length of the SPIRV byte code should be a multiple of four, but got {len}"
+ ),
+ MainError::AllocBytecode => {
+ write!(f, "Failed to allocate space to store SPIRV byte codes")
+ }
+ }
+ }
+}
+
+impl std::error::Error for MainError {}
+
+impl From<werror::OsError> for MainError {
+ fn from(value: werror::OsError) -> Self {
+ MainError::Os(value)
+ }
+}
+
+impl From<werror::NotSupportedError> for MainError {
+ fn from(value: werror::NotSupportedError) -> Self {
+ MainError::NotSupported(value)
+ }
+}
+
+impl From<werror::EventLoopError> for MainError {
+ fn from(value: werror::EventLoopError) -> Self {
+ MainError::Event(value)
+ }
+}
+
+impl From<vk::ErrorCode> for MainError {
+ fn from(value: vk::ErrorCode) -> Self {
+ MainError::Code(value.as_raw())
+ }
+}
+
+impl From<Box<dyn LoaderError>> for MainError {
+ fn from(value: Box<dyn LoaderError>) -> Self {
+ MainError::BoxLoadError(value)
+ }
+}
+
+impl From<BytecodeError> for MainError {
+ fn from(value: BytecodeError) -> Self {
+ match value {
+ BytecodeError::Alloc => Self::AllocBytecode,
+ BytecodeError::Length(len) => Self::InvalidBytecode(len),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub(super) enum SuitabilityError {
+ NoQueuefamily,
+}
+
+impl std::fmt::Display for SuitabilityError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ SuitabilityError::NoQueuefamily => write!(f, "missing required queue families"),
+ }
+ }
+}
+
+impl std::error::Error for SuitabilityError {}
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
new file mode 100644
index 0000000..05f7e25
--- /dev/null
+++ b/src/renderer/mod.rs
@@ -0,0 +1,46 @@
+#![allow(unused)]
+
+pub mod backend;
+pub mod dummy;
+pub mod error;
+pub mod text;
+pub mod vulkan;
+
+use backend::RendererBackend;
+use text::TextSpan;
+
+pub struct Renderer<'a> {
+ pub backend: Box<dyn RendererBackend + 'a>,
+ pub width: i32,
+ pub height: i32,
+}
+
+impl<'a> Renderer<'a> {
+ pub fn new<B>(mut backend: B, width: i32, height: i32) -> Self
+ where
+ B: RendererBackend + 'a,
+ {
+ backend.init(width, height);
+ Self {
+ backend: Box::new(backend),
+ width,
+ height,
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.backend.clear();
+ }
+
+ pub fn draw_text(&mut self, span: &TextSpan) {
+ self.backend.draw_text(span);
+ }
+
+ pub fn present(&mut self) {
+ self.backend.present();
+ }
+
+ pub fn shutdown(&mut self) {
+ self.backend.shutdown();
+ }
+}
diff --git a/src/renderer/text.rs b/src/renderer/text.rs
new file mode 100644
index 0000000..ec913b8
--- /dev/null
+++ b/src/renderer/text.rs
@@ -0,0 +1,72 @@
+#![allow(unused)]
+/// This file handles text-related settings.
+
+#[derive(Debug, Clone, Copy)]
+pub struct Color {
+ r: u8,
+ g: u8,
+ b: u8,
+ a: u8,
+}
+
+impl Color {
+ pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
+ Self { r, g, b, a }
+ }
+ pub fn new_noa(r: u8, g: u8, b: u8) -> Self {
+ Self { r, g, b, a: 1u8 }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct TextStyle {
+ bold: bool,
+ italic: bool,
+ font_size: u16,
+}
+
+impl TextStyle {
+ pub fn default() -> Self {
+ Self {
+ bold: false,
+ italic: false,
+ font_size: 20,
+ }
+ }
+
+ pub fn default_bold() -> Self {
+ Self {
+ bold: true,
+ ..Self::default()
+ }
+ }
+ pub fn default_italic() -> Self {
+ Self {
+ italic: true,
+ ..Self::default()
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct TextSpan<'a> {
+ pub text: &'a str,
+ x: i32,
+ y: i32,
+ fg: Color,
+ bg: Color,
+ pub style: TextStyle,
+}
+
+impl<'a> TextSpan<'a> {
+ pub fn new(text: &'a str, style: TextStyle) -> Self {
+ Self {
+ text,
+ style,
+ x: 0,
+ y: 0,
+ fg: Color::new_noa(255, 0, 0),
+ bg: Color::new_noa(0, 0, 0),
+ }
+ }
+}
diff --git a/src/renderer/vulkan.rs b/src/renderer/vulkan.rs
new file mode 100644
index 0000000..a1451a3
--- /dev/null
+++ b/src/renderer/vulkan.rs
@@ -0,0 +1,951 @@
+#![allow(unused)]
+
+use winit::{
+ application::ApplicationHandler,
+ dpi::LogicalSize,
+ error as werror,
+ event::{DeviceEvent, DeviceId, Event, WindowEvent},
+ event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
+ window::{Window, WindowId},
+};
+
+use vulkanalia::{
+ bytecode::{Bytecode, BytecodeError},
+ loader::{LibloadingLoader, LoaderError, LIBRARY},
+ prelude::v1_0::*,
+ vk::{ExtDebugUtilsExtension, KhrSurfaceExtension, KhrSwapchainExtension},
+ window as vw, Version,
+};
+
+use std::{collections::HashSet, ffi::CStr, os::raw::c_void};
+
+use super::{RendererBackend, TextSpan};
+
+use super::error::{MainError, SuitabilityError};
+
+const PORTABILITY_MACOS_VERSION: Version = Version::new(1, 3, 216);
+
+const VALIDATION_ENABLED: bool = cfg!(debug_assertions);
+
+const VALIDATION_LAYER: vk::ExtensionName =
+ vk::ExtensionName::from_bytes("VK_LAYER_KHRONOS_validation".as_bytes());
+
+const DEVICE_EXTENSIONS: &[vk::ExtensionName] = &[vk::KHR_SWAPCHAIN_EXTENSION.name];
+
+const MAX_FRAMES_IN_FLIGHT: usize = 2;
+
+#[derive(Debug)]
+pub struct VulkanRenderer {
+ window: Option<Window>,
+ entry: Option<Entry>,
+ instance: Option<Instance>,
+ data: AppData,
+ device: Option<Device>,
+ frame: usize,
+ resized: bool,
+ resumed: bool,
+}
+
+impl VulkanRenderer {
+ pub fn new() -> Self {
+ let window = None;
+ let entry = None;
+
+ let data = Default::default();
+ let device = None;
+ let instance = None;
+ let frame = 0usize;
+ let resized = false;
+ let resumed = false;
+
+ Self {
+ window,
+ entry,
+ instance,
+ data,
+ device,
+ frame,
+ resized,
+ resumed,
+ }
+ }
+}
+
+impl ApplicationHandler for VulkanRenderer {
+ fn resumed(&mut self, event_loop: &ActiveEventLoop) {
+ if self.resumed {
+ // redundant signal, just ignore this
+ return;
+ }
+
+ self.resumed = true;
+
+ // create the window here
+
+ let attrs = Window::default_attributes()
+ .with_title("vulkan")
+ .with_decorations(true)
+ .with_resizable(true)
+ .with_inner_size(LogicalSize::new(1024, 720));
+
+ let window = event_loop.create_window(attrs).unwrap();
+
+ let mut data = AppData::default();
+
+ unsafe {
+ let loader = LibloadingLoader::new(LIBRARY)
+ .map_err(|e| MainError::LibLoadError(e.to_string()))
+ .unwrap();
+ let entry = Entry::new(loader).unwrap();
+
+ let instance = create_instance(&window, &entry, &mut data).unwrap();
+
+ data.surface = vw::create_surface(&instance, &window, &window).unwrap();
+
+ pick_physical_device(&instance, &mut data).unwrap();
+
+ let device = create_logical_device(&entry, &instance, &mut data).unwrap();
+
+ create_swapchain(&window, &instance, &device, &mut data).unwrap();
+
+ create_swapchain_image_views(&device, &mut data).unwrap();
+
+ create_render_pass(&instance, &device, &mut data).unwrap();
+
+ create_pipeline(&device, &mut data).unwrap();
+
+ create_framebuffers(&device, &mut data).unwrap();
+
+ create_command_pool(&instance, &device, &mut data).unwrap();
+
+ create_command_buffers(&device, &mut data).unwrap();
+
+ create_synchronized_objects(&device, &mut data).unwrap();
+
+ let frame = 0usize;
+
+ let resized = false;
+
+ self.window = Some(window);
+ }
+ }
+
+ fn window_event(
+ &mut self,
+ event_loop: &ActiveEventLoop,
+ _window_id: WindowId,
+ event: WindowEvent,
+ ) {
+ dbg!(format!("{event:?}"));
+ let window = match self.window.as_ref() {
+ Some(window) => window,
+ _ => return,
+ };
+
+ match event {
+ WindowEvent::CloseRequested => event_loop.exit(),
+ WindowEvent::RedrawRequested => {
+ // to do
+ window.request_redraw();
+ }
+ _ => (),
+ }
+ }
+}
+
+impl RendererBackend for VulkanRenderer {
+ fn name(&self) -> &str {
+ "vulkan"
+ }
+
+ fn init(&mut self, width: i32, height: i32) -> bool {
+ // Initialize Vulkan instance, device, swapchain, etc.
+ println!("Initializing Vulkan backend...");
+
+ true
+ }
+
+ fn clear(&mut self) {
+ // Clear frame
+ }
+
+ fn draw_text(&mut self, _span: &TextSpan) {
+ // Placeholder: draw text with Vulkan
+ }
+
+ fn present(&mut self) {
+ // Present frame
+ }
+
+ fn shutdown(&mut self) {
+ println!("Shutting down Vulkan backend...");
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+struct AppData {
+ surface: vk::SurfaceKHR,
+ messenger: vk::DebugUtilsMessengerEXT,
+ physical_device: vk::PhysicalDevice,
+ gqueue: vk::Queue,
+ pqueue: vk::Queue,
+ swapchain: vk::SwapchainKHR,
+ swapchain_images: Vec<vk::Image>,
+ swapchain_format: vk::Format,
+ swapchain_extent: vk::Extent2D,
+ 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_sp: Vec<vk::Semaphore>,
+ render_finished_sp: 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, MainError> {
+ unsafe {
+ let application_info = vk::ApplicationInfo::builder()
+ .application_name("vulkan-tuto\0".as_bytes())
+ .application_version(vk::make_version(1, 0, 0))
+ .engine_name("No engine\0".as_bytes())
+ .engine_version(vk::make_version(1, 0, 0))
+ .api_version(vk::make_version(1, 0, 0));
+
+ // for s in vw::get_required_instance_extensions(window).iter() {
+ // println!("required instance extension: {s:?}");
+ // }
+
+ let available_layers = entry
+ .enumerate_instance_layer_properties()?
+ .iter()
+ .map(|l| l.layer_name)
+ .collect::<HashSet<_>>();
+
+ // println!("layers count: {}", available_layers.len());
+
+ // for layer in available_layers.iter() {
+ // println!("layer: {layer:?}");
+ // }
+
+ if VALIDATION_ENABLED && !available_layers.contains(&VALIDATION_LAYER) {
+ dbg!();
+ return Err(MainError::ValidationNotSupported);
+ }
+
+ let mut required_extensions = vw::get_required_instance_extensions(window)
+ .iter()
+ .map(|s| s.as_ptr())
+ .collect::<Vec<_>>();
+
+ if VALIDATION_ENABLED {
+ required_extensions.push(vk::EXT_DEBUG_UTILS_EXTENSION.name.as_ptr());
+ }
+
+ if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION {
+ println!("Enabling extensions for Mac OS portability...");
+
+ required_extensions.extend([
+ vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION
+ .name
+ .as_ptr(),
+ vk::KHR_PORTABILITY_ENUMERATION_EXTENSION.name.as_ptr(),
+ ]);
+ }
+
+ let mut info = vk::InstanceCreateInfo::builder()
+ .application_info(&application_info)
+ .enabled_extension_names(&required_extensions);
+
+ let mut debug_info = vk::DebugUtilsMessengerCreateInfoEXT::builder()
+ .message_severity(vk::DebugUtilsMessageSeverityFlagsEXT::all())
+ .message_type(vk::DebugUtilsMessageTypeFlagsEXT::all())
+ .user_callback(Some(debug_callback));
+
+ if cfg!(target_os = "macos") && entry.version()? >= PORTABILITY_MACOS_VERSION {
+ info = info.flags(vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR);
+ }
+
+ let layers = vec![VALIDATION_LAYER.as_ptr()];
+
+ if VALIDATION_ENABLED {
+ info = info.enabled_layer_names(&layers);
+
+ 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)
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+struct QueueFamilyIndices {
+ graphics: u32,
+ presentation: u32,
+}
+
+impl QueueFamilyIndices {
+ unsafe fn get(
+ instance: &Instance,
+ data: &AppData,
+ physical_device: vk::PhysicalDevice,
+ ) -> Result<Self, MainError> {
+ unsafe {
+ let properties = instance.get_physical_device_queue_family_properties(physical_device);
+
+ // for property in properties.iter() {
+ // println!("property: {property:?}");
+ // }
+
+ let graphics = properties
+ .iter()
+ .position(|p| p.queue_flags.contains(vk::QueueFlags::GRAPHICS))
+ .map(|i| i as u32);
+
+ let mut presentation = None;
+
+ for (index, _property) in properties.iter().enumerate() {
+ let index = index as u32;
+
+ if instance.get_physical_device_surface_support_khr(
+ physical_device,
+ index,
+ data.surface,
+ )? {
+ presentation = Some(index);
+ break;
+ }
+ }
+
+ if let (Some(graphics), Some(presentation)) = (graphics, presentation) {
+ Ok(Self {
+ graphics,
+ presentation,
+ })
+ } else {
+ Err(MainError::Suitability(SuitabilityError::NoQueuefamily))
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+struct SwapchainSupport {
+ capabilities: vk::SurfaceCapabilitiesKHR,
+ formats: Vec<vk::SurfaceFormatKHR>,
+ pmodes: Vec<vk::PresentModeKHR>,
+}
+
+impl SwapchainSupport {
+ unsafe fn get(
+ instance: &Instance,
+ data: &AppData,
+ physical_device: vk::PhysicalDevice,
+ ) -> Result<Self, MainError> {
+ unsafe {
+ let capabilities = instance
+ .get_physical_device_surface_capabilities_khr(physical_device, data.surface)?;
+
+ let formats =
+ instance.get_physical_device_surface_formats_khr(physical_device, data.surface)?;
+
+ let pmodes = instance
+ .get_physical_device_surface_present_modes_khr(physical_device, data.surface)?;
+
+ Ok(Self {
+ capabilities,
+ formats,
+ pmodes,
+ })
+ }
+ }
+}
+
+unsafe fn check_physical_device_extensions(
+ instance: &Instance,
+ physical_device: vk::PhysicalDevice,
+) -> Result<(), MainError> {
+ unsafe {
+ let extensions: HashSet<_> = instance
+ .enumerate_device_extension_properties(physical_device, None)?
+ .iter()
+ .map(|e| e.extension_name)
+ .collect();
+
+ // for ext in extensions.iter() {
+ // println!("support extension {ext}");
+ // }
+
+ if !DEVICE_EXTENSIONS.iter().all(|e| extensions.contains(e)) {
+ Err(MainError::NoExtension)
+ } else {
+ Ok(())
+ }
+ }
+}
+
+fn get_swapchain_surace_format(formats: &[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR {
+ if let Some(format) = formats.iter().find(|format| {
+ format.format == vk::Format::B8G8R8A8_SRGB
+ && format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR
+ }) {
+ *format
+ } else {
+ // panic is desired here
+ *formats.first().unwrap()
+ }
+}
+
+fn get_swapchain_pmode(pmodes: &[vk::PresentModeKHR]) -> vk::PresentModeKHR {
+ pmodes
+ .iter()
+ .find(|pmode| **pmode == vk::PresentModeKHR::MAILBOX)
+ .copied()
+ .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 {
+ let min = capabilities.min_image_extent;
+ let max = capabilities.max_image_extent;
+
+ vk::Extent2D::builder()
+ .width(window.inner_size().width.clamp(min.width, max.width))
+ .height(window.inner_size().height.clamp(min.height, max.height))
+ .build()
+ }
+}
+
+unsafe fn create_swapchain(
+ window: &Window,
+ instance: &Instance,
+ device: &Device,
+ data: &mut AppData,
+) -> Result<(), MainError> {
+ unsafe {
+ let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?;
+ let support = SwapchainSupport::get(instance, data, data.physical_device)?;
+
+ let format = get_swapchain_surace_format(&support.formats);
+ let pmode = get_swapchain_pmode(&support.pmodes);
+ let extent = get_swapchain_extent(window, support.capabilities);
+
+ let min_image_count = support.capabilities.min_image_count;
+ let mut max_image_count = support.capabilities.max_image_count;
+
+ if max_image_count == 0 {
+ max_image_count = u32::MAX;
+ }
+
+ let image_count = (min_image_count + 1).clamp(min_image_count, max_image_count);
+
+ let mut queue_family_indices = Vec::new();
+
+ let image_sharing_mode = if indices.graphics != indices.presentation {
+ queue_family_indices.extend([indices.graphics, indices.presentation]);
+
+ vk::SharingMode::CONCURRENT
+ } else {
+ vk::SharingMode::EXCLUSIVE
+ };
+
+ let info = vk::SwapchainCreateInfoKHR::builder()
+ .surface(data.surface)
+ .min_image_count(image_count)
+ .image_format(format.format)
+ .image_color_space(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(pmode)
+ .clipped(true)
+ .old_swapchain(vk::SwapchainKHR::null());
+
+ data.swapchain = device.create_swapchain_khr(&info, None)?;
+
+ data.swapchain_images = device.get_swapchain_images_khr(data.swapchain)?;
+
+ data.swapchain_format = format.format;
+ data.swapchain_extent = extent;
+
+ Ok(())
+ }
+}
+
+unsafe fn create_swapchain_image_views(
+ device: &Device,
+ data: &mut AppData,
+) -> Result<(), MainError> {
+ unsafe {
+ data.swapchain_image_views = data
+ .swapchain_images
+ .iter()
+ .map(|image| unsafe {
+ 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(*image)
+ .view_type(vk::ImageViewType::_2D)
+ .format(data.swapchain_format)
+ .components(components)
+ .subresource_range(subresource_range);
+
+ device.create_image_view(&info, None)
+ })
+ .collect::<Result<Vec<_>, vk::ErrorCode>>()?;
+
+ Ok(())
+ }
+}
+
+unsafe fn create_shader_module(
+ device: &Device,
+ bytecode: &[u8],
+) -> Result<vk::ShaderModule, MainError> {
+ unsafe {
+ let bytecode = Bytecode::new(bytecode)?;
+
+ let info = vk::ShaderModuleCreateInfo::builder()
+ .code_size(bytecode.code_size())
+ .code(bytecode.code());
+
+ device.create_shader_module(&info, None).map_err(Into::into)
+ }
+}
+
+unsafe fn create_render_pass(
+ _instance: &Instance,
+ device: &Device,
+ data: &mut AppData,
+) -> Result<(), MainError> {
+ unsafe {
+ 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_reference = vk::AttachmentReference::builder()
+ .attachment(0)
+ .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
+
+ let color_attachment_references = [color_attachment_reference];
+
+ let sub_dependency = vk::SubpassDependency::builder()
+ .src_subpass(vk::SUBPASS_EXTERNAL)
+ .dst_subpass(0u32)
+ .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 subpass = vk::SubpassDescription::builder()
+ .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
+ .color_attachments(&color_attachment_references);
+
+ let attachments = [color_attachment];
+
+ let subpasses = [subpass];
+
+ let dependencies = [sub_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<(), MainError> {
+ unsafe {
+ let vertex_shader = include_bytes!("../../shaders/shader.vert.spv");
+ let fragment_shader = include_bytes!("../../shaders/shader.frag.spv");
+
+ let vertex_shader_module = create_shader_module(device, vertex_shader.as_slice())?;
+ let fragment_shader_module = create_shader_module(device, fragment_shader.as_slice())?;
+
+ let vertex_stage = vk::PipelineShaderStageCreateInfo::builder()
+ .stage(vk::ShaderStageFlags::VERTEX)
+ .module(vertex_shader_module)
+ .name("main\0".as_bytes());
+
+ let fragment_stage = vk::PipelineShaderStageCreateInfo::builder()
+ .stage(vk::ShaderStageFlags::FRAGMENT)
+ .module(fragment_shader_module)
+ .name("main\0".as_bytes());
+
+ 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(0f32)
+ .y(0f32)
+ .width(data.swapchain_extent.width as f32)
+ .height(data.swapchain_extent.height as f32)
+ .min_depth(0f32)
+ .max_depth(1f32);
+
+ let scissor = vk::Rect2D::builder()
+ .offset(vk::Offset2D::builder().x(0).y(0).build())
+ .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(1f32)
+ .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_state = vk::PipelineColorBlendAttachmentState::builder()
+ .color_write_mask(vk::ColorComponentFlags::all())
+ .blend_enable(false)
+ .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 attachment_states = [attachment_state];
+
+ let color_blend_state = vk::PipelineColorBlendStateCreateInfo::builder()
+ .logic_op_enable(false)
+ .logic_op(vk::LogicOp::COPY)
+ .attachments(&attachment_states)
+ .blend_constants([0f32, 0f32, 0f32, 0f32]);
+
+ let layout_info = vk::PipelineLayoutCreateInfo::builder();
+
+ data.pipeline_layout = device.create_pipeline_layout(&layout_info, None)?;
+
+ let stages = [vertex_stage, fragment_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);
+
+ data.pipeline = device
+ .create_graphics_pipelines(vk::PipelineCache::null(), &[info], None)?
+ .0[0];
+
+ device.destroy_shader_module(vertex_shader_module, None);
+ device.destroy_shader_module(fragment_shader_module, None);
+
+ Ok(())
+ }
+}
+
+unsafe fn create_framebuffers(device: &Device, data: &mut AppData) -> Result<(), MainError> {
+ unsafe {
+ data.framebuffers = data
+ .swapchain_image_views
+ .iter()
+ .map(|view| {
+ let attachments = [*view];
+
+ let 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(&info, None)
+ })
+ .collect::<Result<Vec<_>, _>>()?;
+
+ Ok(())
+ }
+}
+
+unsafe fn create_command_pool(
+ instance: &Instance,
+ device: &Device,
+ data: &mut AppData,
+) -> Result<(), MainError> {
+ unsafe {
+ let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?;
+
+ let info = vk::CommandPoolCreateInfo::builder()
+ .flags(vk::CommandPoolCreateFlags::empty())
+ .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<(), MainError> {
+ unsafe {
+ 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, buffer) in data.command_buffers.iter().enumerate() {
+ let inheritance = vk::CommandBufferInheritanceInfo::builder();
+
+ let info = vk::CommandBufferBeginInfo::builder()
+ .flags(vk::CommandBufferUsageFlags::empty())
+ .inheritance_info(&inheritance);
+
+ device.begin_command_buffer(*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.3f32, 1f32, 1f32, 0.25f32],
+ },
+ };
+
+ 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(*buffer, &info, vk::SubpassContents::INLINE);
+
+ device.cmd_bind_pipeline(*buffer, vk::PipelineBindPoint::GRAPHICS, data.pipeline);
+
+ device.cmd_draw(*buffer, 3, 1, 0, 0);
+
+ device.cmd_end_render_pass(*buffer);
+
+ device.end_command_buffer(*buffer)?;
+ }
+
+ Ok(())
+ }
+}
+
+unsafe fn create_synchronized_objects(
+ device: &Device,
+ data: &mut AppData,
+) -> Result<(), MainError> {
+ unsafe {
+ let info = vk::SemaphoreCreateInfo::builder();
+
+ let fence_info = vk::FenceCreateInfo::builder().flags(vk::FenceCreateFlags::SIGNALED);
+
+ data.image_available_sp = std::iter::repeat_with(|| device.create_semaphore(&info, None))
+ .take(MAX_FRAMES_IN_FLIGHT)
+ .collect::<Result<Vec<_>, _>>()?;
+
+ data.render_finished_sp = std::iter::repeat_with(|| device.create_semaphore(&info, None))
+ .take(MAX_FRAMES_IN_FLIGHT)
+ .collect::<Result<Vec<_>, _>>()?;
+
+ data.in_flight_fences = std::iter::repeat_with(|| device.create_fence(&fence_info, None))
+ .take(MAX_FRAMES_IN_FLIGHT)
+ .collect::<Result<Vec<_>, _>>()?;
+
+ data.images_in_flight = data
+ .swapchain_images
+ .iter()
+ .map(|_| vk::Fence::null())
+ .collect();
+
+ Ok(())
+ }
+}
+
+unsafe fn check_physical_device(
+ instance: &Instance,
+ data: &mut AppData,
+ physical_device: vk::PhysicalDevice,
+) -> Result<(), MainError> {
+ unsafe {
+ let properties = instance.get_physical_device_properties(physical_device);
+
+ let _features = instance.get_physical_device_features(physical_device);
+
+ println!("device type = {:?}", properties.device_type);
+
+ 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.pmodes.is_empty() {
+ return Err(MainError::InsufficientSwapchainSupport);
+ }
+
+ // println!("Swapchain support: {support:?}");
+
+ Ok(())
+ }
+}
+
+unsafe fn pick_physical_device(instance: &Instance, data: &mut AppData) -> Result<(), MainError> {
+ unsafe {
+ for device in instance.enumerate_physical_devices()? {
+ #[cfg(debug_assertions)]
+ let properties = instance.get_physical_device_properties(device);
+
+ if let Err(e) = check_physical_device(instance, data, device) {
+ #[cfg(debug_assertions)]
+ println!(
+ "Skipping device {} due to error {e}",
+ properties.device_name
+ );
+ } else {
+ #[cfg(debug_assertions)]
+ println!("Selecting device {}", properties.device_name);
+
+ data.physical_device = device;
+
+ return Ok(());
+ }
+ }
+
+ Err(MainError::NoDevice)
+ }
+}
+
+unsafe fn create_logical_device(
+ entry: &Entry,
+ instance: &Instance,
+ data: &mut AppData,
+) -> Result<Device, MainError> {
+ unsafe {
+ let indices = QueueFamilyIndices::get(instance, data, data.physical_device)?;
+
+ let mut unique_indices = HashSet::new();
+ unique_indices.insert(indices.graphics);
+ unique_indices.insert(indices.presentation);
+
+ let queue_priorities = [1.0f32];
+
+ let layers = if VALIDATION_ENABLED {
+ vec![VALIDATION_LAYER.as_ptr()]
+ } else {
+ Vec::new()
+ };
+
+ let mut extensions: Vec<_> = DEVICE_EXTENSIONS.iter().map(|ext| ext.as_ptr()).collect();
+
+ 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 queue_infos: Vec<_> = unique_indices
+ .iter()
+ .map(|index| {
+ vk::DeviceQueueCreateInfo::builder()
+ .queue_family_index(*index)
+ .queue_priorities(&queue_priorities)
+ })
+ .collect();
+
+ 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.gqueue = device.get_device_queue(indices.graphics, 0);
+ data.pqueue = device.get_device_queue(indices.presentation, 0);
+
+ Ok(device)
+ }
+}
+
+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 {
+ println!("error: ({type_:?}), {message}");
+ } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING {
+ println!("warning: ({type_:?}), {message}");
+ } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::INFO {
+ // #[cfg(debug_assertions)]
+ // println!("info: ({type_:?}), {message}");
+ } else {
+ // #[cfg(debug_assertions)]
+ // println!("note: ({type_:?}), {message}");
+ }
+
+ vk::FALSE
+}