diff options
author | JSDurand <mmemmew@gmail.com> | 2025-06-21 13:32:55 +0800 |
---|---|---|
committer | JSDurand <mmemmew@gmail.com> | 2025-06-21 13:32:55 +0800 |
commit | 9b36d712e25fb1d209df848281b9913b61a6ec45 (patch) | |
tree | e7a126af70f71a02b2e63292b07b8458effb7da5 /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.rs | 951 |
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 +} |