443 lines
15 KiB
Rust
443 lines
15 KiB
Rust
use std::ffi::{c_char, CString};
|
|
|
|
use ash::vk;
|
|
use buffer::{Buffer, BufferAllocator};
|
|
|
|
pub mod buffer;
|
|
pub mod debug;
|
|
pub mod error;
|
|
pub mod pipeline;
|
|
pub mod pools;
|
|
pub mod queue;
|
|
pub mod surface;
|
|
pub mod swapchain;
|
|
pub mod window;
|
|
|
|
pub use error::EngineError;
|
|
|
|
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";
|
|
|
|
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 allocator: std::mem::ManuallyDrop<BufferAllocator>,
|
|
pub buffers: Vec<Buffer>,
|
|
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!("Create allocator");
|
|
let mut allocator = BufferAllocator::init(&ash_instance, physical_device, &device)?;
|
|
let mut buffer1 = allocator.create_buffer(&device, 16)?;
|
|
|
|
unsafe {
|
|
let data: [u8; 16] = std::mem::transmute([-0.5f32, 0.0f32, 0.0f32, 1.0f32]);
|
|
buffer1.fill(&data);
|
|
}
|
|
|
|
let mut buffer2 = allocator.create_buffer(&device, 20)?;
|
|
unsafe {
|
|
let data: [u8; 20] = std::mem::transmute([5.0f32, 1.0f32, 1.0f32, 0.0f32, 1.0f32]);
|
|
buffer2.fill(&data);
|
|
}
|
|
|
|
let vertex_attrib_descs = [
|
|
vk::VertexInputAttributeDescription {
|
|
binding: 0,
|
|
location: 0,
|
|
offset: 0,
|
|
format: vk::Format::R32G32B32A32_SFLOAT,
|
|
},
|
|
vk::VertexInputAttributeDescription {
|
|
binding: 1,
|
|
location: 1,
|
|
offset: 0,
|
|
format: vk::Format::R32_SFLOAT,
|
|
},
|
|
vk::VertexInputAttributeDescription {
|
|
binding: 1,
|
|
location: 2,
|
|
offset: 4,
|
|
format: vk::Format::R32G32B32A32_SFLOAT,
|
|
},
|
|
];
|
|
let vertex_binding_descs = [
|
|
vk::VertexInputBindingDescription {
|
|
binding: 0,
|
|
stride: 16,
|
|
input_rate: vk::VertexInputRate::VERTEX,
|
|
},
|
|
vk::VertexInputBindingDescription {
|
|
binding: 1,
|
|
stride: 20,
|
|
input_rate: vk::VertexInputRate::VERTEX,
|
|
},
|
|
];
|
|
|
|
log::debug!("Init pipeline");
|
|
let pipeline = Pipeline::init(
|
|
&device,
|
|
&swapchain,
|
|
&renderpass,
|
|
&vertex_attrib_descs,
|
|
&vertex_binding_descs,
|
|
)?;
|
|
|
|
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,
|
|
buffer1.buffer,
|
|
buffer2.buffer,
|
|
)?;
|
|
}
|
|
|
|
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,
|
|
allocator: std::mem::ManuallyDrop::new(allocator),
|
|
buffers: vec![buffer1, buffer2],
|
|
framecount: FrameCounter::new(),
|
|
})
|
|
}
|
|
|
|
fn destroy(&mut self) {
|
|
unsafe {
|
|
self.device
|
|
.device_wait_idle()
|
|
.expect("something wrong while waiting");
|
|
self.pools.cleanup(&self.device);
|
|
self.pipeline.cleanup(&self.device);
|
|
|
|
for buffer in &mut self.buffers {
|
|
self.allocator.free_buffer_allocation(&self.device, buffer);
|
|
}
|
|
std::mem::ManuallyDrop::drop(&mut self.allocator);
|
|
|
|
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,
|
|
// buffers: &[vk::Buffer],
|
|
buffer1: vk::Buffer,
|
|
buffer2: vk::Buffer,
|
|
) -> 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_bind_vertex_buffers(commandbuffer, 0, &[buffer1], &[0]);
|
|
device.cmd_bind_vertex_buffers(commandbuffer, 1, &[buffer2], &[0]);
|
|
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()
|
|
}
|