Initial Commit

This commit is contained in:
zawz 2024-07-24 12:41:00 +02:00
commit 84019305a9
16 changed files with 3010 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
.env

1763
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

17
Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "vulkan-intro"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ash = "0.38.0"
ash-window = "0.13.0"
dotenvy = "0.15.7"
env_logger = "0.11.3"
log = "0.4.22"
thiserror = "1.0.62"
vk-mem = "0.4.0"
vk-shader-macros = "0.2.10"
winit = { version = "0.29", features = ["rwh_06"] }

9
shaders/shader.frag Normal file
View file

@ -0,0 +1,9 @@
#version 450
layout(location = 0) out vec4 theColour;
layout(location = 1) in vec4 colourdata_from_the_vertexshader;
void main() {
theColour = colourdata_from_the_vertexshader;
}

9
shaders/shader.vert Normal file
View file

@ -0,0 +1,9 @@
#version 450
layout(location = 1) out vec4 colourdata_from_the_vertexshader;
void main() {
gl_PointSize = 10.0;
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
colourdata_from_the_vertexshader = vec4(0.0, 0.6, 1.0, 1.0);
}

39
src/frame_counter.rs Normal file
View file

@ -0,0 +1,39 @@
use std::time::{Duration, Instant};
pub struct FrameCounter {
counter: usize,
frame_start: Instant,
}
impl FrameCounter {
pub fn new() -> Self {
Self {
counter: 0,
frame_start: Instant::now(),
}
}
pub fn reset(&mut self) {
self.counter = 0;
self.frame_start = Instant::now();
}
pub fn frame_count(&self) -> usize {
self.counter
}
pub fn get_frame_start(&self) -> Instant {
self.frame_start
}
pub fn frame_time(&self) -> Duration {
self.frame_start.elapsed()
}
pub fn new_frame(&mut self) -> Duration {
self.counter += 1;
let r = self.frame_start.elapsed();
self.frame_start = Instant::now();
r
}
}

70
src/instance/debug.rs Normal file
View file

@ -0,0 +1,70 @@
use std::ffi::CStr;
use ash::{ext, vk};
use super::EngineError;
pub struct DebugInstance {
pub loader: ext::debug_utils::Instance,
pub debug_messenger: vk::DebugUtilsMessengerEXT,
}
impl DebugInstance {
pub fn debug_utils_messenger_create_info() -> vk::DebugUtilsMessengerCreateInfoEXT<'static> {
// Setup debug utils
vk::DebugUtilsMessengerCreateInfoEXT::default()
.message_severity(
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
| vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE
| vk::DebugUtilsMessageSeverityFlagsEXT::INFO
| vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
)
.message_type(
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
| vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE
| vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION,
)
.pfn_user_callback(Some(vulkan_debug_utils_callback))
}
pub fn init<'a>(
entry: &'a ash::Entry,
instance: &'a ash::Instance,
) -> Result<Self, EngineError> {
// start debug messenger
let loader = ext::debug_utils::Instance::new(entry, instance);
let debug_messenger = unsafe {
loader.create_debug_utils_messenger(&Self::debug_utils_messenger_create_info(), None)?
};
Ok(Self {
loader,
debug_messenger,
})
}
pub fn destroy(&self) {
unsafe {
self.loader
.destroy_debug_utils_messenger(self.debug_messenger, None);
}
}
}
impl Drop for DebugInstance {
fn drop(&mut self) {
self.destroy()
}
}
unsafe extern "system" fn vulkan_debug_utils_callback(
message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
message_type: vk::DebugUtilsMessageTypeFlagsEXT,
p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT,
_p_user_data: *mut std::ffi::c_void,
) -> vk::Bool32 {
let message = CStr::from_ptr((*p_callback_data).p_message);
let severity = format!("{:?}", message_severity).to_lowercase();
let ty = format!("{:?}", message_type).to_lowercase();
println!("[Debug][{}][{}] {:?}", severity, ty, message);
vk::FALSE
}

375
src/instance/mod.rs Normal file
View file

@ -0,0 +1,375 @@
use std::ffi::{c_char, CString};
use ash::vk;
pub mod debug;
pub mod pipeline;
pub mod pools;
pub mod queue;
pub mod surface;
pub mod swapchain;
pub mod window;
pub use debug::DebugInstance;
pub use pipeline::Pipeline;
use pools::Pools;
pub use queue::{Queue, QueueFamilies, Queues};
pub use surface::Surface;
pub use swapchain::Swapchain;
pub use window::Window;
use crate::frame_counter::FrameCounter;
const ENGINE_NAME: &core::ffi::CStr = c"UnknownGameEngine";
const APP_NAME: &core::ffi::CStr = c"Vulkan Tutorial";
#[derive(Debug, thiserror::Error)]
pub enum EngineError {
#[error(transparent)]
VkError(#[from] vk::Result),
#[error(transparent)]
AshLoadingError(#[from] ash::LoadingError),
#[error(transparent)]
WinitOsError(#[from] winit::error::OsError),
#[error(transparent)]
WinitEventLoopError(#[from] winit::error::EventLoopError),
#[error(transparent)]
RWHError(#[from] winit::raw_window_handle::HandleError),
}
pub struct EngineInstance {
pub entry: ash::Entry,
pub ash_instance: ash::Instance,
pub debug: std::mem::ManuallyDrop<DebugInstance>,
pub surface: std::mem::ManuallyDrop<Surface>,
pub surface_format: vk::SurfaceFormatKHR,
pub physical_device: vk::PhysicalDevice,
pub physical_device_properties: vk::PhysicalDeviceProperties,
pub physical_device_memory_properties: vk::PhysicalDeviceMemoryProperties,
pub device: ash::Device,
pub queues: Queues,
pub swapchain: Swapchain,
pub renderpass: vk::RenderPass,
pub pipeline: Pipeline,
pub pools: Pools,
pub commandbuffers: Vec<vk::CommandBuffer>,
pub framecount: FrameCounter,
}
impl EngineInstance {
pub fn init(window: &Window) -> Result<Self, EngineError> {
log::debug!("Init window");
let entry = unsafe { ash::Entry::load()? };
let layers = Self::layer_names();
let layers_pointers = as_pointers(&layers);
// create ash instance
log::debug!("Init instance");
let extensions = window.extension_names()?;
let ash_instance = Self::init_instance(&entry, &layers_pointers, &extensions)?;
log::debug!("Init debugger");
let debug = DebugInstance::init(&entry, &ash_instance)?;
log::debug!("Init surface");
let surface = Surface::init(&entry, &ash_instance, &window)?;
// let (surface, surface_loader) = Self::init_surface(&entry, &ash_instance, &window)?;
log::debug!("Init device");
let (physical_device, physical_device_properties) =
Self::select_physical_device(&ash_instance)?;
let physical_device_memory_properties =
unsafe { ash_instance.get_physical_device_memory_properties(physical_device) };
dbg!(&physical_device_properties);
dbg!(&physical_device_memory_properties);
log::debug!("Select surface properties");
let surface_capabilities = surface.get_capabilities(physical_device)?;
let surface_formats = surface.get_formats(physical_device)?;
let surface_resolution = Surface::select_resolution(&surface_capabilities, &window.res);
let surface_format = Surface::select_format(&surface_formats);
log::debug!("Select queues");
let queue_families = QueueFamilies::init(&ash_instance, physical_device, &surface)?;
let queueinfo = queue_families.make_creation_info()?;
log::debug!("Create device");
let device =
Self::create_device(&ash_instance, physical_device, &layers_pointers, &queueinfo)?;
log::debug!("Create queues");
let queues = queue_families.create_queues(&device)?;
// create swapchain
log::debug!("Create swapchain");
let mut swapchain = Swapchain::init(
&ash_instance,
&device,
&surface,
&surface_capabilities,
surface_format,
surface_resolution,
&queues.graphics_queue.as_ref().unwrap(),
)?;
log::debug!("Create framebuffers");
let renderpass = Self::init_renderpass(&device, surface_format.format)?;
swapchain.create_framebuffers(&device, renderpass)?;
log::debug!("Init pipeline");
let pipeline = Pipeline::init(&device, &swapchain, &renderpass)?;
log::debug!("Init pools");
let pools = Pools::init(&device, &queue_families)?;
log::debug!("Create command buffers");
let commandbuffers = pools.create_commandbuffers(&device, swapchain.framebuffers.len())?;
for (i, &commandbuffer) in commandbuffers.iter().enumerate() {
Self::fill_commandbuffer(&device, commandbuffer, renderpass, &swapchain, i, &pipeline)?;
}
Ok(Self {
entry,
ash_instance,
surface: std::mem::ManuallyDrop::new(surface),
surface_format: *surface_format,
debug: std::mem::ManuallyDrop::new(debug),
physical_device,
physical_device_properties,
physical_device_memory_properties,
device,
queues,
swapchain,
renderpass,
pipeline,
pools,
commandbuffers,
framecount: FrameCounter::new(),
})
}
fn destroy(&mut self) {
unsafe {
self.device
.device_wait_idle()
.expect("something wrong while waiting");
self.pools.cleanup(&self.device);
// self.queues.cleanup();
self.pipeline.cleanup(&self.device);
self.device.destroy_render_pass(self.renderpass, None);
self.swapchain.cleanup(&self.device);
std::mem::ManuallyDrop::drop(&mut self.surface);
self.device.destroy_device(None);
std::mem::ManuallyDrop::drop(&mut self.debug);
self.ash_instance.destroy_instance(None);
};
}
fn application_info() -> vk::ApplicationInfo<'static> {
vk::ApplicationInfo::default()
.application_name(APP_NAME)
.application_version(0)
.engine_name(ENGINE_NAME)
.engine_version(0)
.api_version(vk::make_api_version(0, 1, 3, 281))
}
fn instance_create_info<'a>(
debugcreateinfo: &'a mut vk::DebugUtilsMessengerCreateInfoEXT,
app_info: &'a vk::ApplicationInfo,
layers: &'a [*const c_char],
extensions: &'a [*const c_char],
) -> vk::InstanceCreateInfo<'a> {
// Creation info
vk::InstanceCreateInfo::default()
.push_next(debugcreateinfo)
.application_info(app_info)
.enabled_layer_names(layers)
.enabled_extension_names(extensions)
}
fn init_instance(
entry: &ash::Entry,
layers: &[*const c_char],
extensions: &[*const c_char],
) -> Result<ash::Instance, EngineError> {
let app_info = Self::application_info();
// enable debug for instance init
let mut debugcreateinfo = DebugInstance::debug_utils_messenger_create_info();
let instance_create_info =
Self::instance_create_info(&mut debugcreateinfo, &app_info, &layers, &extensions);
let instance = unsafe { entry.create_instance(&instance_create_info, None)? };
Ok(instance)
}
pub fn layer_names() -> Vec<CString> {
vec![CString::new("VK_LAYER_KHRONOS_validation").unwrap()]
}
fn select_physical_device(
instance: &ash::Instance,
) -> Result<(vk::PhysicalDevice, vk::PhysicalDeviceProperties), EngineError> {
let phys_devs: Vec<vk::PhysicalDevice> = unsafe { instance.enumerate_physical_devices()? };
let mut primary = None;
let mut secondary = None;
let mut tertiary = None;
let mut fallback = None;
for p in phys_devs {
let properties = unsafe { instance.get_physical_device_properties(p) };
match properties.device_type {
vk::PhysicalDeviceType::DISCRETE_GPU => {
// if a dedicated is present, prioritize it
primary = Some((p, properties));
break;
}
vk::PhysicalDeviceType::INTEGRATED_GPU => {
// integrated as secondary
secondary = Some((p, properties));
}
vk::PhysicalDeviceType::VIRTUAL_GPU => {
// virtual as tertiary
tertiary = Some((p, properties));
}
_ => {
fallback = Some((p, properties));
}
}
}
let r = if let Some(v) = primary {
v
} else if let Some(v) = secondary {
v
} else if let Some(v) = tertiary {
v
} else {
fallback.unwrap()
};
Ok(r)
}
fn device_extensions() -> Vec<CString> {
vec![ash::khr::swapchain::NAME.into()]
}
fn create_device(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
enabled_layer_names: &[*const i8],
queue_infos: &[vk::DeviceQueueCreateInfo],
) -> Result<ash::Device, EngineError> {
//TODO: dedicated function
let device_extension_names = Self::device_extensions();
let device_extension_name_pointers = as_pointers(&device_extension_names);
let device_create_info = vk::DeviceCreateInfo::default()
.queue_create_infos(&queue_infos)
.enabled_extension_names(&device_extension_name_pointers)
.enabled_layer_names(enabled_layer_names); //deprecated, only for compatibility reason
let logical_device =
unsafe { instance.create_device(physical_device, &device_create_info, None)? };
Ok(logical_device)
}
fn init_renderpass(
device: &ash::Device,
surface_format: vk::Format,
) -> Result<vk::RenderPass, vk::Result> {
let attachments = [vk::AttachmentDescription::default()
.format(surface_format)
.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)
.samples(vk::SampleCountFlags::TYPE_1)];
let color_attachment_references = [vk::AttachmentReference {
attachment: 0,
layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
}];
let subpasses = [vk::SubpassDescription::default()
.color_attachments(&color_attachment_references)
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)];
let subpass_dependencies = [vk::SubpassDependency::default()
.src_subpass(vk::SUBPASS_EXTERNAL)
.src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_subpass(0)
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_access_mask(
vk::AccessFlags::COLOR_ATTACHMENT_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE,
)];
let renderpass_info = vk::RenderPassCreateInfo::default()
.attachments(&attachments)
.subpasses(&subpasses)
.dependencies(&subpass_dependencies);
let renderpass = unsafe { device.create_render_pass(&renderpass_info, None)? };
Ok(renderpass)
}
fn fill_commandbuffer(
device: &ash::Device,
commandbuffer: vk::CommandBuffer,
renderpass: vk::RenderPass,
swapchain: &Swapchain,
framebuffer_index: usize,
pipeline: &Pipeline,
) -> Result<(), vk::Result> {
let commandbuffer_begininfo = vk::CommandBufferBeginInfo::default();
unsafe {
device.begin_command_buffer(commandbuffer, &commandbuffer_begininfo)?;
}
let clearvalues = [vk::ClearValue {
color: vk::ClearColorValue {
float32: [0.0, 0.0, 0.08, 1.0],
},
}];
let renderpass_begininfo = vk::RenderPassBeginInfo::default()
.render_pass(renderpass)
.framebuffer(swapchain.framebuffers[framebuffer_index])
.render_area(vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: swapchain.extent,
})
.clear_values(&clearvalues);
unsafe {
device.cmd_begin_render_pass(
commandbuffer,
&renderpass_begininfo,
vk::SubpassContents::INLINE,
);
device.cmd_bind_pipeline(
commandbuffer,
vk::PipelineBindPoint::GRAPHICS,
pipeline.pipeline,
);
device.cmd_draw(commandbuffer, 1, 1, 0, 0);
device.cmd_end_render_pass(commandbuffer);
device.end_command_buffer(commandbuffer)?;
}
Ok(())
}
}
impl Drop for EngineInstance {
fn drop(&mut self) {
self.destroy()
}
}
fn as_pointers(value: &[CString]) -> Vec<*const i8> {
value.iter().map(|layer_name| layer_name.as_ptr()).collect()
}

130
src/instance/pipeline.rs Normal file
View file

@ -0,0 +1,130 @@
use std::ffi::CString;
use ash::vk;
use super::Swapchain;
pub struct Pipeline {
pub pipeline: vk::Pipeline,
pub layout: vk::PipelineLayout,
}
impl Pipeline {
pub fn cleanup(&self, logical_device: &ash::Device) {
unsafe {
logical_device.destroy_pipeline(self.pipeline, None);
logical_device.destroy_pipeline_layout(self.layout, None);
}
}
pub fn init(
logical_device: &ash::Device,
swapchain: &Swapchain,
renderpass: &vk::RenderPass,
) -> Result<Pipeline, vk::Result> {
let vertexshader_createinfo = vk::ShaderModuleCreateInfo::default()
.code(vk_shader_macros::include_glsl!("./shaders/shader.vert", kind: vert));
let vertexshader_module =
unsafe { logical_device.create_shader_module(&vertexshader_createinfo, None)? };
let fragmentshader_createinfo = vk::ShaderModuleCreateInfo::default()
.code(vk_shader_macros::include_glsl!("./shaders/shader.frag", kind: frag));
let fragmentshader_module =
unsafe { logical_device.create_shader_module(&fragmentshader_createinfo, None)? };
let mainfunctionname = CString::new("main").unwrap();
let vertexshader_stage = vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vertexshader_module)
.name(&mainfunctionname);
let fragmentshader_stage = vk::PipelineShaderStageCreateInfo::default()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(fragmentshader_module)
.name(&mainfunctionname);
let shader_stages = vec![vertexshader_stage, fragmentshader_stage];
let vertex_attrib_descs = [vk::VertexInputAttributeDescription {
binding: 0,
location: 0,
offset: 0,
format: vk::Format::R32G32B32A32_SFLOAT,
}];
let vertex_binding_descs = [vk::VertexInputBindingDescription {
binding: 0,
stride: 16,
input_rate: vk::VertexInputRate::VERTEX,
}];
let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_attribute_descriptions(&vertex_attrib_descs)
.vertex_binding_descriptions(&vertex_binding_descs);
let input_assembly_info = vk::PipelineInputAssemblyStateCreateInfo::default()
.topology(vk::PrimitiveTopology::POINT_LIST);
let viewports = [vk::Viewport {
x: 0.,
y: 0.,
width: swapchain.extent.width as f32,
height: swapchain.extent.height as f32,
min_depth: 0.,
max_depth: 1.,
}];
let scissors = [vk::Rect2D {
offset: vk::Offset2D { x: 0, y: 0 },
extent: swapchain.extent,
}];
let viewport_info = vk::PipelineViewportStateCreateInfo::default()
.viewports(&viewports)
.scissors(&scissors);
let rasterizer_info = vk::PipelineRasterizationStateCreateInfo::default()
.line_width(1.0)
.front_face(vk::FrontFace::COUNTER_CLOCKWISE)
.cull_mode(vk::CullModeFlags::NONE)
.polygon_mode(vk::PolygonMode::FILL);
let multisampler_info = vk::PipelineMultisampleStateCreateInfo::default()
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
let colourblend_attachments = [vk::PipelineColorBlendAttachmentState::default()
.blend_enable(true)
.src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
.dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.color_blend_op(vk::BlendOp::ADD)
.src_alpha_blend_factor(vk::BlendFactor::SRC_ALPHA)
.dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
.alpha_blend_op(vk::BlendOp::ADD)
.color_write_mask(
vk::ColorComponentFlags::R
| vk::ColorComponentFlags::G
| vk::ColorComponentFlags::B
| vk::ColorComponentFlags::A,
)];
let colourblend_info =
vk::PipelineColorBlendStateCreateInfo::default().attachments(&colourblend_attachments);
let pipelinelayout_info = vk::PipelineLayoutCreateInfo::default();
let pipelinelayout =
unsafe { logical_device.create_pipeline_layout(&pipelinelayout_info, None) }?;
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
.stages(&shader_stages)
.vertex_input_state(&vertex_input_info)
.input_assembly_state(&input_assembly_info)
.viewport_state(&viewport_info)
.rasterization_state(&rasterizer_info)
.multisample_state(&multisampler_info)
.color_blend_state(&colourblend_info)
.layout(pipelinelayout)
.render_pass(*renderpass)
.subpass(0);
let graphicspipeline = unsafe {
logical_device
.create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
.expect("A problem with the pipeline creation")
}[0];
unsafe {
logical_device.destroy_shader_module(fragmentshader_module, None);
logical_device.destroy_shader_module(vertexshader_module, None);
}
Ok(Pipeline {
pipeline: graphicspipeline,
layout: pipelinelayout,
})
}
}

58
src/instance/pools.rs Normal file
View file

@ -0,0 +1,58 @@
use ash::vk;
use super::QueueFamilies;
pub struct Pools {
commandpool_graphics: Option<vk::CommandPool>,
commandpool_transfer: Option<vk::CommandPool>,
}
impl Pools {
pub fn init(
logical_device: &ash::Device,
queue_families: &QueueFamilies,
) -> Result<Pools, vk::Result> {
let commandpool_graphics = if let Some(graphics_index) = queue_families.graphics_index {
let graphics_commandpool_info = vk::CommandPoolCreateInfo::default()
.queue_family_index(graphics_index)
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER);
Some(unsafe { logical_device.create_command_pool(&graphics_commandpool_info, None) }?)
} else {
None
};
let commandpool_transfer = if let Some(transfer_index) = queue_families.transfer_index {
let transfer_commandpool_info = vk::CommandPoolCreateInfo::default()
.queue_family_index(transfer_index)
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER);
Some(unsafe { logical_device.create_command_pool(&transfer_commandpool_info, None) }?)
} else {
None
};
Ok(Pools {
commandpool_graphics,
commandpool_transfer,
})
}
pub fn cleanup(&self, logical_device: &ash::Device) {
unsafe {
if let Some(v) = self.commandpool_graphics {
logical_device.destroy_command_pool(v, None);
}
if let Some(v) = self.commandpool_transfer {
logical_device.destroy_command_pool(v, None);
}
}
}
pub fn create_commandbuffers(
&self,
logical_device: &ash::Device,
amount: usize,
) -> Result<Vec<vk::CommandBuffer>, vk::Result> {
let commandbuf_allocate_info = vk::CommandBufferAllocateInfo::default()
.command_pool(self.commandpool_graphics.unwrap())
.command_buffer_count(amount as u32);
unsafe { logical_device.allocate_command_buffers(&commandbuf_allocate_info) }
}
}

107
src/instance/queue.rs Normal file
View file

@ -0,0 +1,107 @@
use super::{EngineError, Surface};
use ash::vk;
const PRIORITIES: [f32; 1] = [1.0f32];
pub struct Queue {
pub queue: vk::Queue,
pub index: u32,
}
impl Queue {
pub fn new(queue: vk::Queue, index: u32) -> Self {
Self { queue, index }
}
}
pub struct Queues {
pub graphics_queue: Option<Queue>,
pub transfer_queue: Option<Queue>,
}
pub struct QueueFamilies {
pub graphics_index: Option<u32>,
pub transfer_index: Option<u32>,
}
impl QueueFamilies {
pub fn init(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
surface: &Surface,
) -> Result<Self, EngineError> {
let mut graphics_index = None;
let mut transfer_index = None;
let queue_families =
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
dbg!(&queue_families);
for (index, qfam) in queue_families.iter().enumerate() {
if qfam.queue_count > 0
&& qfam.queue_flags.contains(vk::QueueFlags::GRAPHICS)
// WARN: surface drawing capable queue could be different, but in our case it's not
&& unsafe {
surface.surface_loader.get_physical_device_surface_support(
physical_device,
index as u32,
surface.surface,
)?
}
{
graphics_index = Some(index as u32);
if qfam.queue_flags.contains(vk::QueueFlags::TRANSFER) && qfam.queue_count > 1 {
transfer_index = Some(index as u32);
}
} else if qfam.queue_count > 0 && qfam.queue_flags.contains(vk::QueueFlags::TRANSFER) {
if transfer_index.is_none() || !qfam.queue_flags.contains(vk::QueueFlags::GRAPHICS)
{
transfer_index = Some(index as u32);
}
}
}
Ok(Self {
graphics_index,
transfer_index,
})
}
pub fn make_creation_info<'a>(
&self,
) -> Result<Vec<vk::DeviceQueueCreateInfo<'a>>, EngineError> {
let mut queue_infos = vec![vk::DeviceQueueCreateInfo::default()
.queue_family_index(self.graphics_index.unwrap())
.queue_priorities(&PRIORITIES)];
if let Some(v) = self.transfer_index {
queue_infos.push(
vk::DeviceQueueCreateInfo::default()
.queue_family_index(v)
.queue_priorities(&PRIORITIES),
)
}
Ok(queue_infos)
}
pub fn create_queues(&self, logical_device: &ash::Device) -> Result<Queues, EngineError> {
let graphics_queue = self
.graphics_index
.map(|x| unsafe { Queue::new(logical_device.get_device_queue(x, 0), x) });
let transfer_queue = if let Some(v) = self.transfer_index {
Some(Queue::new(
if self.graphics_index.is_some() && self.graphics_index.unwrap() == v {
// Graphics and transfer queue family indexes are identical, pick a second queue
unsafe { logical_device.get_device_queue(v, 1) }
} else {
unsafe { logical_device.get_device_queue(v, 0) }
},
v,
))
} else {
None
};
Ok(Queues {
graphics_queue,
transfer_queue,
})
}
}

91
src/instance/surface.rs Normal file
View file

@ -0,0 +1,91 @@
use ash::{khr, vk};
use super::window::Resolution;
use super::EngineError;
use super::Window;
pub struct Surface {
pub surface: vk::SurfaceKHR,
pub surface_loader: khr::surface::Instance,
}
impl Surface {
pub fn init(
entry: &ash::Entry,
instance: &ash::Instance,
window: &Window,
) -> Result<Self, EngineError> {
let surface = unsafe {
ash_window::create_surface(
entry,
instance,
window.raw_display_handle()?,
window.raw_window_handle()?,
None,
)?
};
let surface_loader = khr::surface::Instance::new(&entry, &instance);
Ok(Self {
surface,
surface_loader,
})
}
pub fn get_capabilities(
&self,
physical_device: vk::PhysicalDevice,
) -> Result<vk::SurfaceCapabilitiesKHR, EngineError> {
Ok(unsafe {
self.surface_loader
.get_physical_device_surface_capabilities(physical_device, self.surface)?
})
}
pub fn get_present_modes(
&self,
physical_device: vk::PhysicalDevice,
) -> Result<Vec<vk::PresentModeKHR>, EngineError> {
Ok(unsafe {
self.surface_loader
.get_physical_device_surface_present_modes(physical_device, self.surface)?
})
}
pub fn get_formats(
&self,
physical_device: vk::PhysicalDevice,
) -> Result<Vec<vk::SurfaceFormatKHR>, EngineError> {
Ok(unsafe {
self.surface_loader
.get_physical_device_surface_formats(physical_device, self.surface)?
})
}
pub fn select_resolution(
surface_capabilities: &vk::SurfaceCapabilitiesKHR,
res: &Resolution,
) -> vk::Extent2D {
match surface_capabilities.current_extent.width {
u32::MAX => vk::Extent2D {
width: res.width,
height: res.height,
},
_ => surface_capabilities.current_extent,
}
}
pub fn select_format(surface_formats: &[vk::SurfaceFormatKHR]) -> &vk::SurfaceFormatKHR {
let selected_surface_format = surface_formats
.iter()
.filter(|x| x.format == vk::Format::R8G8B8A8_SRGB)
.collect::<Vec<&vk::SurfaceFormatKHR>>()
.remove(0);
selected_surface_format
}
}
impl Drop for Surface {
fn drop(&mut self) {
unsafe { self.surface_loader.destroy_surface(self.surface, None) };
}
}

141
src/instance/swapchain.rs Normal file
View file

@ -0,0 +1,141 @@
use ash::{khr, vk};
use super::{EngineError, Queue, Surface};
pub struct Swapchain {
pub swapchain_loader: khr::swapchain::Device,
pub swapchain: vk::SwapchainKHR,
pub extent: vk::Extent2D,
pub images: Vec<vk::Image>,
pub image_views: Vec<vk::ImageView>,
pub framebuffers: Vec<vk::Framebuffer>,
pub image_available: Vec<vk::Semaphore>,
pub rendering_finished: Vec<vk::Semaphore>,
pub may_begin_drawing: Vec<vk::Fence>,
pub amount_of_images: u32,
pub current_image: usize,
}
impl Swapchain {
pub fn init(
instance: &ash::Instance,
logical_device: &ash::Device,
surface: &Surface,
surface_capabilities: &vk::SurfaceCapabilitiesKHR,
surface_format: &vk::SurfaceFormatKHR,
extent: vk::Extent2D,
graphics_queue: &Queue,
) -> Result<Self, EngineError> {
let queuefamilies = [graphics_queue.index];
let swapchain_create_info = vk::SwapchainCreateInfoKHR::default()
.surface(surface.surface)
.min_image_count(
3.max(surface_capabilities.min_image_count),
// .min(surface_capabilities.max_image_count), // currently broken for this device ?
)
.image_format(surface_format.format)
.image_color_space(surface_format.color_space)
.image_extent(extent)
.image_array_layers(1)
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
.queue_family_indices(&queuefamilies)
.pre_transform(surface_capabilities.current_transform)
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
.present_mode(vk::PresentModeKHR::FIFO);
let swapchain_loader = khr::swapchain::Device::new(&instance, &logical_device);
let swapchain = unsafe { swapchain_loader.create_swapchain(&swapchain_create_info, None)? };
let swapchain_images = unsafe { swapchain_loader.get_swapchain_images(swapchain)? };
// create views
let mut swapchain_imageviews = Vec::with_capacity(swapchain_images.len());
for image in &swapchain_images {
let subresource_range = vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1);
let imageview_create_info = vk::ImageViewCreateInfo::default()
.image(*image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(surface_format.format)
.subresource_range(subresource_range);
let imageview =
unsafe { logical_device.create_image_view(&imageview_create_info, None) }?;
swapchain_imageviews.push(imageview);
}
let amount_of_images = swapchain_images.len() as u32;
let mut image_available = vec![];
let mut rendering_finished = vec![];
let mut may_begin_drawing = vec![];
let semaphoreinfo = vk::SemaphoreCreateInfo::default();
let fenceinfo = vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED);
for _ in 0..amount_of_images {
let semaphore_available =
unsafe { logical_device.create_semaphore(&semaphoreinfo, None) }?;
let semaphore_finished =
unsafe { logical_device.create_semaphore(&semaphoreinfo, None) }?;
image_available.push(semaphore_available);
rendering_finished.push(semaphore_finished);
let fence = unsafe { logical_device.create_fence(&fenceinfo, None) }?;
may_begin_drawing.push(fence);
}
Ok(Self {
swapchain_loader,
swapchain,
extent,
images: swapchain_images,
image_views: swapchain_imageviews,
framebuffers: vec![],
amount_of_images,
current_image: 0,
may_begin_drawing,
image_available,
rendering_finished,
})
}
pub unsafe fn cleanup(&mut self, logical_device: &ash::Device) {
for fence in &self.may_begin_drawing {
logical_device.destroy_fence(*fence, None);
}
for semaphore in &self.image_available {
logical_device.destroy_semaphore(*semaphore, None);
}
for semaphore in &self.rendering_finished {
logical_device.destroy_semaphore(*semaphore, None);
}
for fb in &self.framebuffers {
logical_device.destroy_framebuffer(*fb, None);
}
for iv in &self.image_views {
logical_device.destroy_image_view(*iv, None);
}
self.swapchain_loader
.destroy_swapchain(self.swapchain, None);
}
pub fn create_framebuffers(
&mut self,
logical_device: &ash::Device,
renderpass: vk::RenderPass,
) -> Result<(), vk::Result> {
for iv in &self.image_views {
let iview = [*iv];
let framebuffer_info = vk::FramebufferCreateInfo::default()
.render_pass(renderpass)
.attachments(&iview)
.width(self.extent.width)
.height(self.extent.height)
.layers(1);
let fb = unsafe { logical_device.create_framebuffer(&framebuffer_info, None) }?;
self.framebuffers.push(fb);
}
Ok(())
}
}

59
src/instance/window.rs Normal file
View file

@ -0,0 +1,59 @@
use std::cell::RefCell;
use winit::event_loop::EventLoop as WinitEventLoop;
use winit::raw_window_handle::RawDisplayHandle;
use winit::raw_window_handle::RawWindowHandle;
use winit::window::Window as WinitWindow;
use winit::window::WindowBuilder as WinitWindowBuilder;
use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use super::EngineError;
#[derive(Clone, Debug)]
pub struct Resolution {
pub width: u32,
pub height: u32,
}
pub struct Window {
pub eventloop: RefCell<WinitEventLoop<()>>,
pub window: WinitWindow,
pub res: Resolution,
}
impl Window {
pub fn init(res: Resolution) -> Result<Self, EngineError> {
let eventloop = WinitEventLoop::new()?;
let window = WinitWindowBuilder::new()
.with_title("Vulkan tutorial")
.with_inner_size(winit::dpi::LogicalSize::new(
f64::from(res.width),
f64::from(res.height),
))
.build(&eventloop)?;
Ok(Self {
eventloop: RefCell::new(eventloop),
window,
res,
})
}
pub fn extension_names(&self) -> Result<Vec<*const i8>, EngineError> {
let mut extension_name_pointers =
ash_window::enumerate_required_extensions(self.window.display_handle()?.as_raw())
.unwrap()
.to_vec();
extension_name_pointers.push(ash::ext::debug_utils::NAME.as_ptr());
Ok(extension_name_pointers)
}
pub fn raw_display_handle(&self) -> Result<RawDisplayHandle, EngineError> {
Ok(self.window.display_handle()?.as_raw())
}
pub fn raw_window_handle(&self) -> Result<RawWindowHandle, EngineError> {
Ok(self.window.window_handle()?.as_raw())
}
}

100
src/main.rs Normal file
View file

@ -0,0 +1,100 @@
pub mod instance;
pub mod render;
pub mod frame_counter;
pub use instance::EngineInstance;
use instance::{window::Resolution, Window};
use ash::vk;
pub const WINDOW_WIDTH: u32 = 1280;
pub const WINDOW_HEIGHT: u32 = 720;
pub const DEFAULT_RES: Resolution = Resolution {
width: WINDOW_WIDTH,
height: WINDOW_HEIGHT,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
env_logger::init();
let window = Window::init(DEFAULT_RES.clone())?;
let mut instance = EngineInstance::init(&window)?;
render::render_loop(&window, &mut instance, move |e| {
let next_image = (e.swapchain.current_image + 1) % e.swapchain.amount_of_images as usize;
unsafe {
e.device
.wait_for_fences(
&[e.swapchain.may_begin_drawing[e.swapchain.current_image]],
true,
std::u64::MAX,
)
.expect("fence-waiting");
e.device
.reset_fences(&[e.swapchain.may_begin_drawing[e.swapchain.current_image]])
.expect("resetting fences");
}
let (image_index, _) = unsafe {
e.swapchain
.swapchain_loader
.acquire_next_image(
e.swapchain.swapchain,
std::u64::MAX,
e.swapchain.image_available[e.swapchain.current_image],
vk::Fence::null(),
)
.expect("image acquisition trouble")
};
let semaphores_available = [e.swapchain.image_available[e.swapchain.current_image]];
let waiting_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
let semaphores_finished = [
e.swapchain.rendering_finished[e.swapchain.current_image],
// e.swapchain.next_frame[next_image],
];
let commandbuffers = [e.commandbuffers[image_index as usize]];
let submit_info = [vk::SubmitInfo::default()
.wait_semaphores(&semaphores_available)
.wait_dst_stage_mask(&waiting_stages)
.command_buffers(&commandbuffers)
.signal_semaphores(&semaphores_finished)];
let queue = e.queues.graphics_queue.as_ref().unwrap().queue;
unsafe {
e.device
.queue_submit(
queue,
&submit_info,
e.swapchain.may_begin_drawing[e.swapchain.current_image],
)
.expect("queue submission");
};
let swapchains = [e.swapchain.swapchain];
let indices = [image_index];
let present_info = vk::PresentInfoKHR::default()
.wait_semaphores(&semaphores_finished)
.swapchains(&swapchains)
.image_indices(&indices);
unsafe {
e.swapchain
.swapchain_loader
.queue_present(queue, &present_info)
.expect("queue presentation");
};
let frameid = e.framecount.frame_count();
let frametime = e.framecount.new_frame();
println!("frame {}: {} ms", frameid, frametime.as_secs_f32() * 1000.0);
e.swapchain.current_image = next_image;
})?;
Ok(())
}

40
src/render.rs Normal file
View file

@ -0,0 +1,40 @@
use crate::instance::{EngineInstance, Window};
use winit::{
event::{ElementState, Event, KeyEvent, WindowEvent},
keyboard::{Key, NamedKey},
platform::run_on_demand::EventLoopExtRunOnDemand,
};
pub fn render_loop<F: Fn(&mut EngineInstance)>(
window: &Window,
instance: &mut EngineInstance,
f: F,
) -> Result<(), impl std::error::Error> {
window
.eventloop
.borrow_mut()
.run_on_demand(move |event, elwp| {
elwp.set_control_flow(winit::event_loop::ControlFlow::Poll);
match event {
Event::WindowEvent {
event:
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
logical_key: Key::Named(NamedKey::Escape),
..
},
..
},
..
}
| Event::LoopExiting => {
elwp.exit();
}
Event::AboutToWait => f(instance),
_ => (),
}
})
}