summaryrefslogtreecommitdiff
path: root/src/renderer/vulkan.rs
diff options
context:
space:
mode:
authorJSDurand <mmemmew@gmail.com>2025-06-21 13:32:55 +0800
committerJSDurand <mmemmew@gmail.com>2025-06-21 13:32:55 +0800
commit9b36d712e25fb1d209df848281b9913b61a6ec45 (patch)
treee7a126af70f71a02b2e63292b07b8458effb7da5 /src/renderer/vulkan.rs
init commit
A basic window is available. Now we shall try to render texts and some auxiliary functionalities.
Diffstat (limited to 'src/renderer/vulkan.rs')
-rw-r--r--src/renderer/vulkan.rs951
1 files changed, 951 insertions, 0 deletions
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
+}