diff options
Diffstat (limited to 'src/graphics_window_dressing.rs')
| -rw-r--r-- | src/graphics_window_dressing.rs | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/src/graphics_window_dressing.rs b/src/graphics_window_dressing.rs new file mode 100644 index 0000000..6ffab41 --- /dev/null +++ b/src/graphics_window_dressing.rs @@ -0,0 +1,604 @@ +#![deny(unsafe_code)] +use crate::error::*; +use crate::graphics_permanent::{ PermanentGraphicsState, QueueFamilyIndices }; + +use std::collections::BTreeSet; +use vulkanalia::{ Device, Instance }; +use vulkanalia::vk::{ self, Handle, HasBuilder, DeviceV1_0, + KhrSwapchainExtensionDeviceCommands }; +use winit::window::Window; + + +// 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. +pub const N_SIMULTANEOUS_FRAMES: usize = 5; + + +// 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)] +pub struct WindowDressing { + pub swapchain: Swapchain, + + render_pass: vk::RenderPass, + + pipeline: vk::Pipeline, + pipeline_layout: vk::PipelineLayout, + + framebuffers: Vec<vk::Framebuffer>, + + command_pool: vk::CommandPool, + pub command_buffers: Vec<vk::CommandBuffer>, + + pub 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)] +pub struct Swapchain { + pub swapchain: vk::SwapchainKHR, + images: Vec<vk::Image>, + image_views: Vec<vk::ImageView>, + format: vk::Format, + extent: vk::Extent2D, +} + +#[derive(Debug)] +pub struct Concurrency { + pub image_available_semaphores: Vec<vk::Semaphore>, + pub rendering_finished_semaphores: Vec<vk::Semaphore>, + + // 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. + pub frame_fences: Vec<vk::Fence>, + pub image_fences: Vec<vk::Fence>, +} + + +impl WindowDressing { + pub fn new(permanent: &PermanentGraphicsState, + physical_device: vk::PhysicalDevice, + indices: QueueFamilyIndices) + -> Result<Self> + { + 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)] + pub 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<Swapchain> + { + 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<vk::RenderPass> + { + 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 = PermanentGraphicsState::load_spirv_shader_module( + device, vertex_binary)?; + let fragment_module = PermanentGraphicsState::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<vk::ImageView>, + render_pass: &vk::RenderPass) + -> Result<Vec<vk::Framebuffer>> + { + 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<vk::Framebuffer>, + render_pass: &vk::RenderPass, + pipeline: &vk::Pipeline, + indices: &QueueFamilyIndices) + -> Result<(vk::CommandPool, Vec<vk::CommandBuffer>)> + { + // 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<vk::Image>) + -> Result<Concurrency> + { + 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<vk::SurfaceFormatKHR>) + -> Result<vk::SurfaceFormatKHR> + { + 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<vk::PresentModeKHR>) + -> Result<vk::PresentModeKHR> + { + // It's guaranteed to have this one. + return Ok(vk::PresentModeKHR::FIFO); + } + + fn pick_image_extent(window: &Window, + capabilities: vk::SurfaceCapabilitiesKHR) + -> Result<vk::Extent2D> + { + 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()) + } + } +} |