#![deny(unsafe_code)]
use crate::error::*;
use crate::graphics_permanent::{ PermanentGraphicsState, QueueFamilyIndices };
use std::cell::RefCell;
use std::collections::BTreeSet;
use vulkanalia::{ Device, Instance };
use vulkanalia::bytecode::Bytecode;
use vulkanalia::vk::{ self, Handle, HasBuilder, DeviceV1_0,
KhrSwapchainExtensionDeviceCommands };
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ ActiveEventLoop, EventLoop };
use winit::window::{ Window, WindowId };
mod error;
mod graphics_permanent;
// TODO: use VK_KHR_swapchain_maintenance1 to put a fence on the presentation
// operation. doing that will remove the requirement that we have more
// simultaneous frames than images.
const N_SIMULTANEOUS_FRAMES: usize = 5;
struct Surreality {
permanent: RefCell>,
window_dressing: RefCell >,
frame_index: usize,
}
// The WindowDressing collects the Vulkan graphics objects which need to be
// regenerated or modified when the window changes in certain ways, such as
// resizing. The ones which don't need that are collected above, in
// PermanentGraphicsState.
#[derive(Debug)]
struct WindowDressing {
swapchain: Swapchain,
render_pass: vk::RenderPass,
pipeline: vk::Pipeline,
pipeline_layout: vk::PipelineLayout,
framebuffers: Vec,
command_pool: vk::CommandPool,
command_buffers: Vec,
concurrency: Concurrency,
}
// A swapchain is the generalized facility that is used to implement
// double buffering, triple buffering, rendering passes that feed into each
// other, and other things of that nature. It's a first-class thing but for
// now, we use at most one of it. We also support running without one.
#[derive(Debug)]
struct Swapchain {
swapchain: vk::SwapchainKHR,
images: Vec,
image_views: Vec,
format: vk::Format,
extent: vk::Extent2D,
}
#[derive(Debug)]
struct Concurrency {
image_available_semaphores: Vec,
rendering_finished_semaphores: Vec,
// Okay, the lifetime management on the fences is really subtle. There is
// one fence for each frame, and frame_fences holds the authoritative
// reference to it.
//
// There is one entry in image_fences for each image. The number of images
// is not directly related to the number of frames; it will likely be
// larger, but may be smaller or the same. At the start of execution, the
// entries are all nulls. Each time an image is acquired from the swapchain,
// the corresponding entry in image_fences is overwritten with a duplicate
// of the frame fence. This happens during rendering of the frame, so the
// frame fence is in the "signaled" state. It will be reset right before
// submitting the queue, then signaled again when the submission completes.
frame_fences: Vec,
image_fences: Vec,
}
impl Surreality {
fn new() -> Self {
Surreality {
permanent: RefCell::new(None),
window_dressing: RefCell::new(None),
frame_index: 0,
}
}
#[allow(unsafe_code)]
fn init(&mut self, event_loop: &ActiveEventLoop) -> Result<()> {
let (permanent, physical_device, indices, enable_swapchain)
= PermanentGraphicsState::new(event_loop)?;
if enable_swapchain.0 {
*self.window_dressing.get_mut()
= Some(WindowDressing::new(&permanent, physical_device, indices)?);
}
*self.permanent.get_mut() = Some(permanent);
Ok(())
}
#[allow(unsafe_code)]
fn load_spirv_shader_module(device: &Device, binary: &[u8])
-> Result
{
let bytecode = Bytecode::new(binary)?;
let module_info = vk::ShaderModuleCreateInfo::builder()
.code(bytecode.code())
.code_size(bytecode.code_size());
let module = unsafe {
device.create_shader_module(&module_info, None)
}?;
Ok(module)
}
#[allow(unsafe_code)]
fn render(&mut self, window_id: WindowId) -> Result<()> {
if let Some(permanent) = self.permanent.borrow().as_ref()
&& let Some(window_dressing)
= self.window_dressing.borrow_mut().as_mut()
&& window_id == permanent.window.id()
{
let device = &permanent.device;
let frame_index = self.frame_index;
let concurrency = &mut window_dressing.concurrency;
let image_available_semaphore
= &concurrency.image_available_semaphores[frame_index];
let rendering_finished_semaphore
= &concurrency.rendering_finished_semaphores[frame_index];
let frame_fence = &concurrency.frame_fences[frame_index];
unsafe { device.wait_for_fences(&[*frame_fence], true, u64::MAX) }?;
let image_index = unsafe {
device.acquire_next_image_khr(window_dressing.swapchain.swapchain,
u64::MAX, *image_available_semaphore,
vk::Fence::null())
}?.0 as usize;
let image_fence = &concurrency.image_fences[image_index];
if !image_fence.is_null() {
unsafe { device.wait_for_fences(&[*image_fence], true, u64::MAX) }?;
}
concurrency.image_fences[image_index] = *frame_fence;
let first_semaphores = [*image_available_semaphore];
let second_semaphores = [*rendering_finished_semaphore];
let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
let command_buffers = [window_dressing.command_buffers[image_index]];
let submit_info = vk::SubmitInfo::builder()
.wait_semaphores(&first_semaphores)
.wait_dst_stage_mask(&wait_stages)
.command_buffers(&command_buffers)
.signal_semaphores(&second_semaphores);
unsafe { device.reset_fences(&[*frame_fence]) }?;
unsafe {
device.queue_submit(permanent.graphics_queue,
&[submit_info],
*frame_fence)
}?;
let swapchains = [window_dressing.swapchain.swapchain];
let image_indices = [image_index as u32];
let present_info = vk::PresentInfoKHR::builder()
.wait_semaphores(&second_semaphores)
.swapchains(&swapchains)
.image_indices(&image_indices);
unsafe {
device.queue_present_khr(permanent.presentation_queue, &present_info)
}?;
self.frame_index = (frame_index + 1) % N_SIMULTANEOUS_FRAMES;
}
Ok(())
}
}
impl Drop for Surreality {
#[allow(unsafe_code)]
fn drop(&mut self) {
if let Some(permanent) = self.permanent.replace(None) {
unsafe { permanent.device.device_wait_idle() }.unwrap();
if let Some(window_dressing) = self.window_dressing.replace(None) {
window_dressing.destroy(&permanent);
}
permanent.destroy();
}
}
}
impl WindowDressing {
fn new(permanent: &PermanentGraphicsState,
physical_device: vk::PhysicalDevice,
indices: QueueFamilyIndices)
-> Result
{
let window = &permanent.window;
let instance = &permanent.instance;
let surface = &permanent.surface;
let device = &permanent.device;
let swapchain = Self::init_swapchain(
window, instance, surface, &physical_device, device, &indices)?;
let render_pass = Self::init_render_pass(device, &swapchain.format)?;
let (pipeline_layout, pipeline)
= Self::init_pipeline(device, &swapchain.extent, &render_pass)?;
let framebuffers = Self::init_framebuffers(
device, &swapchain.extent, &swapchain.image_views, &render_pass)?;
let (command_pool, command_buffers)
= Self::init_commands(device, &swapchain.extent, &framebuffers,
&render_pass, &pipeline, &indices)?;
let concurrency = Self::init_concurrency(device, &swapchain.images)?;
Ok(WindowDressing {
swapchain,
render_pass,
pipeline,
pipeline_layout,
framebuffers,
command_pool,
command_buffers,
concurrency,
})
}
#[allow(unsafe_code)]
fn destroy(self, permanent: &PermanentGraphicsState) {
let device = &permanent.device;
for semaphore in self.concurrency.image_available_semaphores {
unsafe { device.destroy_semaphore(semaphore, None) };
}
for semaphore in self.concurrency.rendering_finished_semaphores {
unsafe { device.destroy_semaphore(semaphore, None) };
}
for fence in self.concurrency.frame_fences {
unsafe { device.destroy_fence(fence, None) };
}
unsafe { device.destroy_command_pool(self.command_pool, None) };
for framebuffer in self.framebuffers {
unsafe { device.destroy_framebuffer(framebuffer, None) };
}
unsafe { device.destroy_pipeline(self.pipeline, None) };
unsafe { device.destroy_render_pass(self.render_pass, None) };
unsafe { device.destroy_pipeline_layout(self.pipeline_layout, None) };
for view in self.swapchain.image_views {
unsafe { device.destroy_image_view(view, None) };
}
unsafe { device.destroy_swapchain_khr(self.swapchain.swapchain, None) };
}
#[allow(unsafe_code)]
fn init_swapchain(window: &Window, instance: &Instance,
surface: &vk::SurfaceKHR,
physical_device: &vk::PhysicalDevice, device: &Device,
indices: &QueueFamilyIndices)
-> Result
{
let (capabilities, formats, presentation_modes)
= PermanentGraphicsState::find_device_swapchain_features(
instance, surface, physical_device)?.require()?;
let format = Self::pick_surface_format(&formats)?;
let presentation_mode
= Self::pick_presentation_mode(&presentation_modes)?;
let extent = Self::pick_image_extent(window, capabilities)?;
let mut image_count = capabilities.min_image_count + 1;
if capabilities.max_image_count != 0 {
image_count
= image_count.clamp(0, capabilities.max_image_count);
}
let mut unique_queue_family_indices = BTreeSet::new();
unique_queue_family_indices.insert(indices.graphics);
unique_queue_family_indices.insert(indices.presentation);
// If there's only one queue, we use exclusive sharing mode, which
// will allow things to work without locks. Otherwise we use concurrent
// mode.
let (ordered_indices, sharing_mode)
= if unique_queue_family_indices.len() < 2
{
(vec![indices.graphics], vk::SharingMode::EXCLUSIVE)
} else {
(vec![indices.graphics, indices.presentation],
vk::SharingMode::CONCURRENT)
};
let swapchain_info = vk::SwapchainCreateInfoKHR::builder()
.surface(*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(sharing_mode)
.queue_family_indices(&ordered_indices)
.pre_transform(capabilities.current_transform)
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
.present_mode(presentation_mode)
.clipped(true)
.old_swapchain(vk::SwapchainKHR::null());
let swapchain = unsafe {
device.create_swapchain_khr(&swapchain_info, None)
}?;
let images = unsafe {
device.get_swapchain_images_khr(swapchain)
}?;
let mut image_views = Vec::new();
for image in &images {
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 view_info = vk::ImageViewCreateInfo::builder()
.image(*image)
.view_type(vk::ImageViewType::_2D)
.format(format.format)
.components(components)
.subresource_range(subresource_range);
let view = unsafe {
device.create_image_view(&view_info, None)
}?;
image_views.push(view);
}
Ok(Swapchain {
swapchain, images, image_views,
format: format.format,
extent
})
}
#[allow(unsafe_code)]
fn init_render_pass(device: &Device, format: &vk::Format)
-> Result
{
let color_attachment
= vk::AttachmentDescription::builder()
.format(*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 subpass_attachments = [color_attachment_reference];
let subpass = vk::SubpassDescription::builder()
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
.color_attachments(&subpass_attachments);
let dependency
= vk::SubpassDependency::builder()
.src_subpass(vk::SUBPASS_EXTERNAL)
.src_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.src_access_mask(vk::AccessFlags::empty())
.dst_subpass(0)
.dst_stage_mask(
vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
.dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE);
let render_attachments = [color_attachment];
let subpasses = [subpass];
let dependencies = [dependency];
let render_pass_info = vk::RenderPassCreateInfo::builder()
.attachments(&render_attachments)
.subpasses(&subpasses)
.dependencies(&dependencies);
let render_pass = unsafe {
device.create_render_pass(&render_pass_info, None)
}?;
Ok(render_pass)
}
#[allow(unsafe_code)]
fn init_pipeline(device: &Device, extent: &vk::Extent2D,
render_pass: &vk::RenderPass)
-> Result<(vk::PipelineLayout, vk::Pipeline)>
{
let vertex_binary = include_bytes!(
concat!(env!("OUT_DIR"), "/shader.vert.spv"));
let fragment_binary = include_bytes!(
concat!(env!("OUT_DIR"), "/shader.frag.spv"));
let vertex_module
= Surreality::load_spirv_shader_module(device, vertex_binary)?;
let fragment_module
= Surreality::load_spirv_shader_module(device, fragment_binary)?;
let vertex_stage_info = vk::PipelineShaderStageCreateInfo::builder()
.stage(vk::ShaderStageFlags::VERTEX)
.module(vertex_module)
.name(b"main\0");
let fragment_stage_info = vk::PipelineShaderStageCreateInfo::builder()
.stage(vk::ShaderStageFlags::FRAGMENT)
.module(fragment_module)
.name(b"main\0");
let vertex_input_state_info
= vk::PipelineVertexInputStateCreateInfo::builder();
let input_assembly_state_info
= vk::PipelineInputAssemblyStateCreateInfo::builder()
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
.primitive_restart_enable(false);
let viewport = vk::Viewport::builder()
.x(0.0)
.y(0.0)
.width(extent.width as f32)
.height(extent.height as f32)
.min_depth(0.0)
.max_depth(1.0);
let viewports = [viewport];
let scissor = vk::Rect2D::builder()
.offset(vk::Offset2D { x: 0, y: 0 })
.extent(*extent);
let scissor_list = [scissor];
let viewport_state_info = vk::PipelineViewportStateCreateInfo::builder()
.viewports(&viewports)
.scissors(&scissor_list);
let rasterizer_state_info
= vk::PipelineRasterizationStateCreateInfo::builder()
.depth_clamp_enable(false)
.rasterizer_discard_enable(false)
.polygon_mode(vk::PolygonMode::FILL)
.line_width(1.0)
.cull_mode(vk::CullModeFlags::BACK)
.front_face(vk::FrontFace::CLOCKWISE)
.depth_bias_enable(false);
let multisample_state_info
= vk::PipelineMultisampleStateCreateInfo::builder()
.sample_shading_enable(false)
.rasterization_samples(vk::SampleCountFlags::_1);
let blend_attachment_info
= vk::PipelineColorBlendAttachmentState::builder()
.color_write_mask(vk::ColorComponentFlags::all())
.blend_enable(false)
.src_color_blend_factor(vk::BlendFactor::ONE)
.dst_color_blend_factor(vk::BlendFactor::ZERO)
.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 blend_attachments = [blend_attachment_info];
let blend_info = vk::PipelineColorBlendStateCreateInfo::builder()
.logic_op_enable(false)
.logic_op(vk::LogicOp::COPY)
.attachments(&blend_attachments)
.blend_constants([0.0, 0.0, 0.0, 0.0]);
let pipeline_layout_info = vk::PipelineLayoutCreateInfo::builder();
let pipeline_layout = unsafe {
device.create_pipeline_layout(&pipeline_layout_info, None)
}?;
let stages = [vertex_stage_info, fragment_stage_info];
let pipeline_info
= vk::GraphicsPipelineCreateInfo::builder()
.stages(&stages)
.vertex_input_state(&vertex_input_state_info)
.input_assembly_state(&input_assembly_state_info)
.viewport_state(&viewport_state_info)
.rasterization_state(&rasterizer_state_info)
.multisample_state(&multisample_state_info)
.color_blend_state(&blend_info)
.layout(pipeline_layout)
.render_pass(*render_pass)
.subpass(0);
let pipeline = unsafe {
device.create_graphics_pipelines(vk::PipelineCache::null(),
&[pipeline_info], None)
}?.0[0];
unsafe {
device.destroy_shader_module(vertex_module, None);
device.destroy_shader_module(fragment_module, None);
};
Ok((pipeline_layout, pipeline))
}
#[allow(unsafe_code)]
fn init_framebuffers(device: &Device, extent: &vk::Extent2D,
swapchain_image_views: &Vec,
render_pass: &vk::RenderPass)
-> Result>
{
let mut framebuffers = Vec::new();
for image_view in swapchain_image_views {
let attachments = [*image_view];
let framebuffer_info = vk::FramebufferCreateInfo::builder()
.render_pass(*render_pass)
.attachments(&attachments)
.width(extent.width)
.height(extent.height)
.layers(1);
let framebuffer = unsafe {
device.create_framebuffer(&framebuffer_info, None)
}?;
framebuffers.push(framebuffer);
}
Ok(framebuffers)
}
#[allow(unsafe_code)]
fn init_commands(device: &Device,
extent: &vk::Extent2D,
framebuffers: &Vec,
render_pass: &vk::RenderPass,
pipeline: &vk::Pipeline,
indices: &QueueFamilyIndices)
-> Result<(vk::CommandPool, Vec)>
{
// We call this one last time. It's kind of a problem.
let command_pool_info = vk::CommandPoolCreateInfo::builder()
.flags(vk::CommandPoolCreateFlags::empty())
.queue_family_index(indices.graphics);
let command_pool = unsafe {
device.create_command_pool(&command_pool_info, None)
}?;
let command_buffer_allocation_info
= vk::CommandBufferAllocateInfo::builder()
.command_pool(command_pool)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(framebuffers.len() as u32);
let command_buffers = unsafe {
device.allocate_command_buffers(&command_buffer_allocation_info)
}?;
for (index, framebuffer) in framebuffers.iter().enumerate() {
let command_buffer = command_buffers[index];
let inheritance_info = vk::CommandBufferInheritanceInfo::builder();
let command_buffer_begin_info
= vk::CommandBufferBeginInfo::builder()
.flags(vk::CommandBufferUsageFlags::empty())
.inheritance_info(&inheritance_info);
unsafe {
device.begin_command_buffer(command_buffer,
&command_buffer_begin_info)
}?;
let render_area = vk::Rect2D::builder()
.offset(vk::Offset2D::default())
.extent(*extent);
let clear_value = vk::ClearValue {
color: vk::ClearColorValue {
float32: [0.0, 0.0, 0.0, 1.0]
}
};
let clear_values = [clear_value];
let begin_pass_info = vk::RenderPassBeginInfo::builder()
.render_pass(*render_pass)
.framebuffer(*framebuffer)
.render_area(render_area)
.clear_values(&clear_values);
unsafe {
device.cmd_begin_render_pass(command_buffer, &begin_pass_info,
vk::SubpassContents::INLINE)
};
unsafe {
device.cmd_bind_pipeline(command_buffer,
vk::PipelineBindPoint::GRAPHICS,
*pipeline)
};
unsafe { device.cmd_draw(command_buffer, 3, 1, 0, 0) };
unsafe { device.cmd_end_render_pass(command_buffer) };
unsafe { device.end_command_buffer(command_buffer) }?;
}
Ok((command_pool, command_buffers))
}
#[allow(unsafe_code)]
fn init_concurrency(device: &Device,
swapchain_images: &Vec)
-> Result
{
let semaphore_info = vk::SemaphoreCreateInfo::builder();
let fence_info = vk::FenceCreateInfo::builder()
.flags(vk::FenceCreateFlags::SIGNALED);
let mut image_available_semaphores = Vec::new();
let mut rendering_finished_semaphores = Vec::new();
let mut frame_fences = Vec::new();
for _ in 0 .. N_SIMULTANEOUS_FRAMES {
image_available_semaphores.push(unsafe {
device.create_semaphore(&semaphore_info, None)
}?);
rendering_finished_semaphores.push(unsafe {
device.create_semaphore(&semaphore_info, None)
}?);
frame_fences.push(unsafe {
device.create_fence(&fence_info, None)
}?);
}
let mut image_fences = Vec::new();
for _ in 0 .. swapchain_images.len() {
image_fences.push(vk::Fence::null());
}
Ok(Concurrency {
image_available_semaphores,
rendering_finished_semaphores,
frame_fences,
image_fences: image_fences,
})
}
fn pick_surface_format(available_formats: &Vec)
-> Result
{
for format in available_formats {
if format.format == vk::Format::B8G8R8A8_SRGB
&& format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR
{
return Ok(format.clone());
}
}
return Ok(available_formats[0].clone());
}
fn pick_presentation_mode(_available_modes: &Vec)
-> Result
{
// It's guaranteed to have this one.
return Ok(vk::PresentModeKHR::FIFO);
}
fn pick_image_extent(window: &Window,
capabilities: vk::SurfaceCapabilitiesKHR)
-> Result
{
if capabilities.current_extent.width != u32::MAX
&& capabilities.current_extent.height != u32::MAX
{
Ok(capabilities.current_extent)
} else {
let window_size = window.inner_size();
let width = window_size.width
.clamp(capabilities.min_image_extent.width,
capabilities.max_image_extent.width);
let height = window_size.height
.clamp(capabilities.min_image_extent.height,
capabilities.max_image_extent.height);
Ok(vk::Extent2D::builder().width(width).height(height).build())
}
}
}
impl ApplicationHandler for Surreality {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
ignore_errors(move || {
self.init(event_loop)?;
Ok(())
});
}
fn window_event(&mut self, event_loop: &ActiveEventLoop,
window_id: WindowId, event: WindowEvent)
{
match event {
WindowEvent::RedrawRequested => {
if !event_loop.exiting() {
if let Err(e) = self.render(window_id) {
eprintln!("Error: {}", e);
}
}
}
WindowEvent::CloseRequested => {
event_loop.exit();
}
_ => { }
}
}
}
fn main() -> std::process::ExitCode {
let body: fn() -> Result<()> = || {
let event_loop = EventLoop::new()?;
let mut surreality = Surreality::new();
event_loop.run_app(&mut surreality)?;
Ok(())
};
match body() {
Ok(()) => std::process::ExitCode::SUCCESS,
Err(e) => {
eprintln!("Error: {}", e);
std::process::ExitCode::from(1)
}
}
}