From 9b36d712e25fb1d209df848281b9913b61a6ec45 Mon Sep 17 00:00:00 2001 From: JSDurand Date: Sat, 21 Jun 2025 13:32:55 +0800 Subject: init commit A basic window is available. Now we shall try to render texts and some auxiliary functionalities. --- src/main.rs | 31 ++ src/renderer/backend.rs | 13 + src/renderer/dummy.rs | 55 +++ src/renderer/error.rs | 132 +++++++ src/renderer/mod.rs | 46 +++ src/renderer/text.rs | 72 ++++ src/renderer/vulkan.rs | 951 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1300 insertions(+) create mode 100644 src/main.rs create mode 100644 src/renderer/backend.rs create mode 100644 src/renderer/dummy.rs create mode 100644 src/renderer/error.rs create mode 100644 src/renderer/mod.rs create mode 100644 src/renderer/text.rs create mode 100644 src/renderer/vulkan.rs (limited to 'src') 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 = 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), + 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 for MainError { + fn from(value: werror::OsError) -> Self { + MainError::Os(value) + } +} + +impl From for MainError { + fn from(value: werror::NotSupportedError) -> Self { + MainError::NotSupported(value) + } +} + +impl From for MainError { + fn from(value: werror::EventLoopError) -> Self { + MainError::Event(value) + } +} + +impl From for MainError { + fn from(value: vk::ErrorCode) -> Self { + MainError::Code(value.as_raw()) + } +} + +impl From> for MainError { + fn from(value: Box) -> Self { + MainError::BoxLoadError(value) + } +} + +impl From 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, + pub width: i32, + pub height: i32, +} + +impl<'a> Renderer<'a> { + pub fn new(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, + entry: Option, + instance: Option, + data: AppData, + device: Option, + 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, + swapchain_format: vk::Format, + swapchain_extent: vk::Extent2D, + swapchain_image_views: Vec, + render_pass: vk::RenderPass, + pipeline_layout: vk::PipelineLayout, + pipeline: vk::Pipeline, + framebuffers: Vec, + command_pool: vk::CommandPool, + command_buffers: Vec, + image_available_sp: Vec, + render_finished_sp: Vec, + in_flight_fences: Vec, + images_in_flight: Vec, +} + +unsafe fn create_instance( + window: &Window, + entry: &Entry, + data: &mut AppData, +) -> Result { + 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::>(); + + // 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::>(); + + 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 { + 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, + pmodes: Vec, +} + +impl SwapchainSupport { + unsafe fn get( + instance: &Instance, + data: &AppData, + physical_device: vk::PhysicalDevice, + ) -> Result { + 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::, vk::ErrorCode>>()?; + + Ok(()) + } +} + +unsafe fn create_shader_module( + device: &Device, + bytecode: &[u8], +) -> Result { + 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::, _>>()?; + + 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::, _>>()?; + + data.render_finished_sp = std::iter::repeat_with(|| device.create_semaphore(&info, None)) + .take(MAX_FRAMES_IN_FLIGHT) + .collect::, _>>()?; + + data.in_flight_fences = std::iter::repeat_with(|| device.create_fence(&fence_info, None)) + .take(MAX_FRAMES_IN_FLIGHT) + .collect::, _>>()?; + + 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 { + 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 +} -- cgit v1.2.3-18-g5258