From daac5191461ed84d41920ea9c80addb8dea977c2 Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Thu, 9 Jul 2026 14:46:02 -0700 Subject: move all the window dressing stuff into its own file Force-Push: yes Change-Id: Ib2db6c2af91698678c99969f575636abb4215b54 --- src/graphics_permanent.rs | 18 ++ src/graphics_window_dressing.rs | 604 ++++++++++++++++++++++++++++++++++++++ src/main.rs | 621 +--------------------------------------- 3 files changed, 628 insertions(+), 615 deletions(-) create mode 100644 src/graphics_window_dressing.rs diff --git a/src/graphics_permanent.rs b/src/graphics_permanent.rs index a4ece9d..4df6069 100644 --- a/src/graphics_permanent.rs +++ b/src/graphics_permanent.rs @@ -4,6 +4,7 @@ use crate::error::*; use std::collections::{ BTreeMap, BTreeSet, HashSet }; use std::ffi::{ c_void, CStr }; use vulkanalia::{ Device, Entry, Instance, Version }; +use vulkanalia::bytecode::Bytecode; use vulkanalia::loader::{ LibloadingLoader, LIBRARY }; use vulkanalia::vk::{ self, HasBuilder, ApplicationInfo, InstanceCreateInfo, @@ -672,6 +673,23 @@ impl PermanentGraphicsState { Ok(Acceptable::Accepted((capabilities, formats, presentation_modes))) } } + + #[allow(unsafe_code)] + pub 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) + } } 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, + + command_pool: vk::CommandPool, + pub command_buffers: Vec, + + 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, + image_views: Vec, + format: vk::Format, + extent: vk::Extent2D, +} + +#[derive(Debug)] +pub struct Concurrency { + pub image_available_semaphores: Vec, + pub 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. + pub frame_fences: Vec, + pub image_fences: Vec, +} + + +impl WindowDressing { + pub 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)] + 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 + { + 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 = 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, + 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()) + } + } +} diff --git a/src/main.rs b/src/main.rs index b073b7c..5dea456 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,21 @@ #![deny(unsafe_code)] use crate::error::*; -use crate::graphics_permanent::{ PermanentGraphicsState, QueueFamilyIndices }; +use crate::graphics_permanent::PermanentGraphicsState; +use crate::graphics_window_dressing::{ + WindowDressing, N_SIMULTANEOUS_FRAMES +}; 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 }; +use winit::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; +mod graphics_window_dressing; struct Surreality { @@ -29,62 +24,6 @@ struct Surreality { 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 { @@ -109,23 +48,6 @@ impl Surreality { 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() @@ -207,537 +129,6 @@ impl Drop for Surreality { } } -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) { -- cgit 1.4.1