#![deny(unsafe_code)] use crate::error::*; use std::cell::OnceCell; 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, Handle, HasBuilder, ApplicationInfo, InstanceCreateInfo, DeviceV1_0, EntryV1_0, InstanceV1_0, ExtDebugUtilsExtensionInstanceCommands, KhrSurfaceExtensionInstanceCommands, KhrSwapchainExtensionDeviceCommands }; use winit::dpi::LogicalSize; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::{ ActiveEventLoop, EventLoop }; use winit::window::{ Window, WindowAttributes, WindowId }; mod error; const VULKAN_FIRST_PORTABILITY_VERSION: Version = Version::new(1, 3, 216); // 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; enum Acceptable { Accepted(T), Rejected(String), } impl Acceptable { #[allow(unused)] fn is_accepted(&self) -> bool { if let Acceptable::Accepted(_) = self { true } else { false } } #[allow(unused)] fn is_rejected(&self) -> bool { if let Acceptable::Rejected(_) = self { true } else { false } } fn unwrap(self) -> T { if let Acceptable::Accepted(result) = self { result } else { panic!("Unwrapped a rejected Acceptable."); } } } #[derive(Debug)] struct QueueFamilyIndices { graphics: u32, presentation: u32, } #[derive(Debug)] struct SwapchainFeatures { capabilities: vk::SurfaceCapabilitiesKHR, formats: Vec, presentation_modes: Vec, } struct Surreality { // The "window" is the usual operating-system concept of a window; it's // provided by winit, and may be X11, Wayland, or some more curious thing. // The way we initialize Vulkan requires us to have at least one of these; // we could have more, but for now, we don't. window: OnceCell, // There are a few Vulkan features (in the informal sense of "feature") // that we want to be able to run both with and without. Here, we have // booleans describing which we're doing. These are computed during instance // and device initialization. // // In the cases of validation and portability, the booleans are only used // to communicate between initialization phases, and aren't actually needed // during rendering. We keep them around anyway though because they're small // and it's more convenient to have them all in one place. enable_validation: OnceCell, enable_portability: OnceCell, enable_swapchain: OnceCell, // The Vulkan "entry" is the part of the Vulkan library ecosystem that's // responsible for finding and loading the other parts. entry: OnceCell, // The Vulkan "instance" is the bulk of the Vulkan library, with most of // the high-level responsibilities around lifecycle management. instance: OnceCell, // The debug messager is a Vulkan object representing our callback which // Vulkan uses to tell us things. // // Vulkan spells "messager" as "messenger", but this is absurd // over-formality and we don't indulge it. debug_messager: OnceCell, // The Vulkan "surface" is the destination that rendering happens into. // It is connected to the window but distinct from it. surface: OnceCell, // The Vulkan "device" is the abstraction for a GPU. A physical one is the // actual GPU, and a logical one is our connection to it. We track both. // We'll be referencing the logical device a lot, so we follow Vulkan's lead // and let it have a short variable name. physical_device: OnceCell, device: OnceCell, // Vulkan has a first-class concept of command queues. We have two of // them, one for graphics drawing commands and one for presentation. // // While these are often the same queue, there is no guarantee of that; // sometimes there's no queue family that supports both operations together. // For simplicity's sake we treat them as if they're separate, though the // handles will alias each other when the initialization logic was able to // find a family that does both. // // Yes, this means the compiler has to deal with pointer aliasing // concerns, which have a tendency to defeat optimizations. graphics_queue: OnceCell, presentation_queue: OnceCell, // 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. swapchain: OnceCell, swapchain_images: OnceCell>, swapchain_image_views: OnceCell>, format: OnceCell, extent: OnceCell, render_pass: OnceCell, pipeline: OnceCell, pipeline_layout: OnceCell, framebuffers: OnceCell>, command_pool: OnceCell, command_buffers: OnceCell>, image_available_semaphores: OnceCell>, rendering_finished_semaphores: OnceCell>, // 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: OnceCell>, image_fences: Vec, frame_index: usize, } impl Surreality { fn new() -> Self { Surreality { window: OnceCell::new(), enable_validation: OnceCell::new(), enable_portability: OnceCell::new(), enable_swapchain: OnceCell::new(), entry: OnceCell::new(), instance: OnceCell::new(), debug_messager: OnceCell::new(), surface: OnceCell::new(), physical_device: OnceCell::new(), device: OnceCell::new(), graphics_queue: OnceCell::new(), presentation_queue: OnceCell::new(), swapchain: OnceCell::new(), swapchain_images: OnceCell::new(), swapchain_image_views: OnceCell::new(), format: OnceCell::new(), extent: OnceCell::new(), render_pass: OnceCell::new(), pipeline: OnceCell::new(), pipeline_layout: OnceCell::new(), framebuffers: OnceCell::new(), command_pool: OnceCell::new(), command_buffers: OnceCell::new(), image_available_semaphores: OnceCell::new(), rendering_finished_semaphores: OnceCell::new(), frame_fences: OnceCell::new(), image_fences: Vec::new(), frame_index: 0, } } fn init(&mut self, event_loop: &ActiveEventLoop) -> Result<()> { // TODO refactor all these to not be methods, for better isolation if self.window.get().is_none() { self.init_window(event_loop)?; } if self.entry.get().is_none() { self.init_vulkan_entry()?; } if self.instance.get().is_none() { self.init_vulkan_instance()?; } if self.surface.get().is_none() { self.init_vulkan_surface()?; } if self.device.get().is_none() { self.init_vulkan_device()?; } if self.swapchain.get().is_none() && *self.enable_swapchain.get().unwrap() { self.init_vulkan_swapchain()?; } if self.render_pass.get().is_none() { self.init_render_pass()?; } if self.pipeline.get().is_none() { self.init_pipeline()?; } if self.framebuffers.get().is_none() { self.init_framebuffers()?; } if self.command_buffers.get().is_none() { self.init_commands()?; } if self.image_available_semaphores.get().is_none() { self.init_concurrency()?; } Ok(()) } fn init_window(&mut self, event_loop: &ActiveEventLoop) -> Result<()> { // Notice that we do this before having a Vulkan instance. The window is // actually a parameter needed to create the instance; see // init_vulkan_instance(), below. let window_attributes = WindowAttributes::default() .with_title("Love, Curiosity, Justice") .with_inner_size(LogicalSize::new(1024, 768)); let window: Window = event_loop.create_window(window_attributes)?; self.window.set(window).unwrap(); Ok(()) } #[allow(unsafe_code)] fn init_vulkan_entry(&mut self) -> Result<()> { // Okay, so, a Vulkan "entry" is a small set of functions which are used // to dynamically load all the rest of Vulkan. It's our responsibility to // know how to load the entry, then it will take care of the rest. At // least, that's the theory, but also see flake.nix for all the // FHS-centric assumptions it makes that we have to correct. // // Anyway, Vulkanalia offers an integration with libloading, which is a // crate that wraps POSIX dlopen(). We use that; it's enabled by // Vulkanalia's "libloading" feature. let loader = unsafe { LibloadingLoader::new(LIBRARY) }?; let entry = unsafe { Entry::new(loader) }?; self.entry.set(entry).unwrap(); Ok(()) } #[allow(unsafe_code)] fn init_vulkan_instance(&mut self) -> Result<()> { let entry = self.entry.get().unwrap(); let enable_validation = cfg!(feature = "vulkan-validation") || cfg!(debug_assertions); // Since there's a lot of factors going into our instance creation // request, we'll build up the parameters mutably. let mut flags = vk::InstanceCreateFlags::empty(); let mut extensions = Vec::new(); let mut layers = Vec::new(); // Before we go any further, use Vulkan's introspection to list off // what's available. let mut available_extensions = HashSet::new(); for extension in unsafe { entry.enumerate_instance_extension_properties(None) }? { available_extensions.insert(extension.extension_name); } let available_extensions = available_extensions; let mut available_layers = HashSet::new(); for layer in unsafe { entry.enumerate_instance_layer_properties() }? { available_layers.insert(layer.layer_name); } let available_layers = available_layers; // There are certain extensions which are required by the nature of our // windowing system. Happily, vulanaklia knows how to deal with that based // on the type of window we give it. // // This is possible because of an integration between Vulkanalia and // winit, which is enabled by Vulkanalia's "window" feature. for extension in vulkanalia::window::get_required_instance_extensions( self.window.get().unwrap()) { extensions.push(extension.as_ptr()); } // Deal with Vulkan's thing about opting in to non-conforming // implementations. if entry.version()? >= VULKAN_FIRST_PORTABILITY_VERSION { if cfg!(target_os = "macos") { // Vulkan on the Mac is not fully conforming. extensions.push( vk::KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION.name.as_ptr()); extensions.push( vk::KHR_PORTABILITY_ENUMERATION_EXTENSION.name.as_ptr()); flags.insert(vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR); self.enable_portability.set(true).unwrap(); } else { self.enable_portability.set(false).unwrap(); } } else { self.enable_portability.set(false).unwrap(); } // Request the LunarG validation layer, when appropriate. if enable_validation { let layer_name = vk::ExtensionName::from_bytes( b"VK_LAYER_KHRONOS_validation"); if available_layers.contains(&layer_name) { layers.push(layer_name.as_ptr()); self.enable_validation.set(enable_validation).unwrap(); } else { eprintln!("Vulkan validation requested at build time, \ but no validation layer available."); self.enable_validation.set(false).unwrap(); } } else { self.enable_validation.set(false).unwrap(); } // Request the debug extension. This is the first of three bits of code // that deal with this, and has the resonsibility of making sure the // extension is in the list we ask for. let debug_extension_name = vk::EXT_DEBUG_UTILS_EXTENSION.name; if available_extensions.contains(&debug_extension_name) { extensions.push(debug_extension_name.as_ptr()); } else { eprintln!("Vulkan debug extension not available; \ this may mean other messages don't show up."); } let application_info = ApplicationInfo::builder() .application_name(b"Surreality\0") .application_version(vk::make_version(1, 0, 0)) .engine_name(b"Surreality\0") .engine_version(vk::make_version(1, 0, 0)) .api_version(vk::make_version(1, 0, 0)); // Deceptively, this DOES get mutated later, but Vulkanalia doesn't see // it that way. let instance_create_info = InstanceCreateInfo::builder() .application_info(&application_info) .flags(flags) .enabled_extension_names(&extensions) .enabled_layer_names(&layers); // Configure the debug extension. This is the middle of three bits of // code that deal with this, and has the responsibility of making sure // the callback will be available during instance creation and // destruction, which is done in a special way that doesn't rely on having // a messager, since there can't be one for those steps. let debug_info = if available_extensions.contains(&debug_extension_name) { let mut debug_info = vk::DebugUtilsMessengerCreateInfoEXT::builder() .message_severity(vk::DebugUtilsMessageSeverityFlagsEXT::all()) .message_type(vk::DebugUtilsMessageTypeFlagsEXT::GENERAL | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE) .user_callback(Some(debug_messager_callback)); // Please notice that the reference we pass here will escape Rust's // lifetime checking, since push_next() casts it to a pointer. We don't // get nearly as strong a safety guarantee as one might hope (and as [1] // naively reassures us we do). If we did, the thing we're doing would // actually be forbidden! // // [1] https://kylemayes.github.io/vulkanalia/ instance_create_info.push_next(&mut debug_info); Some(debug_info) } else { None }; let instance = unsafe { // We're promising that every struct referenced here is still alive. // Since it's all pointers, that's... not a thing we statically know. Be // aware. Only you can prevent segfaults. entry.create_instance(&instance_create_info, None) }?; // Configure the debug extension. This is the last of three bits of code // that deal with this, and has the responsibility of asking the instance, // which now exists, to create the debug messager. if let Some(debug_info) = debug_info && self.debug_messager.get().is_none() { #[allow(unsafe_code)] let debug_messager = unsafe { instance.create_debug_utils_messenger_ext(&debug_info, None) }?; self.debug_messager.set(debug_messager).unwrap(); } self.instance.set(instance).unwrap(); Ok(()) } #[allow(unsafe_code)] fn init_vulkan_surface(&mut self) -> Result<()> { let window = self.window.get().unwrap(); let instance = self.instance.get().unwrap(); // Conveniently, Vulkanalia's "window" feature allows it to get the // platform-specific stuff directly out of winit for us. This wrapper does // not correspond 1:1 to a Vulkan function; rather, it picks the Vulkan // function from the appropriate platform-specific extension. // // The reason it takes the window twice is that that first one is // actually there to reference the display (in the x11 sense of "display" // meaning the connection to the windowing system). let surface = unsafe { vulkanalia::window::create_surface(&instance, &window, &window) }?; self.surface.set(surface).unwrap(); Ok(()) } #[allow(unsafe_code)] fn init_vulkan_device(&mut self) -> Result<()> { if self.physical_device.get().is_none() { let physical_device = self.pick_vulkan_device()?; self.physical_device.set(physical_device).unwrap(); } if self.device.get().is_none() { // Usually we're content to borrow these, but here, that would give us // ownership problems. In reality they are smart pointer objects that do // not directly contain their substantive pieces, so we can freely clone // them, and in this case it's convenient to do that. let instance = self.instance.get().unwrap().clone(); let physical_device = self.physical_device.get().unwrap().clone(); // We already did the check in score_vulkan_device(), so if it fails // this second time, that's our own bug and we don't need to explain it // to our users. let indices = self.find_device_queue_family_indices(&physical_device)? .unwrap(); // We enumerate the device extensions here so they can inform // configuration. We already did this in score_vulkan_device(), but here // it is again. let mut available_extensions = HashSet::new(); for extension in unsafe { instance.enumerate_device_extension_properties(physical_device, None) }? { available_extensions.insert(extension.extension_name); } let available_extensions = available_extensions; // Old versions of Vulkan want layers to be enabled at the device // level as well. Newer ones will ignore this and just use the instance // layers. let features = vk::PhysicalDeviceFeatures::builder(); let mut extensions = Vec::new(); let mut layers = Vec::new(); if *self.enable_validation.get().unwrap() { let layer_name = vk::ExtensionName::from_bytes( b"VK_LAYER_KHRONOS_validation"); // It's not concerning if this isn't supported, because device // layers are ignored on recent versions, they're purely historical. if available_extensions.contains(&layer_name) { layers.push(layer_name.as_ptr()); } } if *self.enable_portability.get().unwrap() { // This is untested, since the only scenario where it would come up // is on a Mac, which we don't actually support. Sorry, and good luck. let layer_name = vk::ExtensionName::from_bytes( b"VK_KHR_portability_subset"); if available_extensions.contains(&layer_name) { extensions.push(layer_name.as_ptr()); } } { let layer_name = vk::KHR_SWAPCHAIN_EXTENSION.name; if available_extensions.contains(&layer_name) { // It's important that we not call the swapchain extension // functions until we've verified the extension is supported. To // emphasize that, we do it on a separate line. // // We've done this check once already, in scoring, and now here // we are discarding its results a second time. We'll do it for the // third and last time in swapchain creation. if let Acceptable::Accepted(_) = self.find_device_swapchain_features(&physical_device)? { extensions.push(layer_name.as_ptr()); self.enable_swapchain.set(true).unwrap(); } else { self.enable_swapchain.set(false).unwrap(); } } else { self.enable_swapchain.set(false).unwrap(); } } // We have one or more queue family indices; we don't know a priori // how many, because it's possible some of them are the same. We only // want to create one queue per distinct family, so we find the unique // indices... let mut unique_queue_family_indices = BTreeSet::new(); unique_queue_family_indices.insert(indices.graphics); unique_queue_family_indices.insert(indices.presentation); // ... then add a queue create info struct for each. let mut queues = Vec::new(); for index in unique_queue_family_indices { // Passing the priorities vector also implicitly sets the count of // how many queues we are creating within the family. This nicety is // one of the fun things Vulkanalia's builders do for us. queues.push(vk::DeviceQueueCreateInfo::builder() .queue_family_index(index) .queue_priorities(&[1.0])); } let device_info = vk::DeviceCreateInfo::builder() .queue_create_infos(&queues) .enabled_layer_names(&layers) .enabled_extension_names(&extensions) .enabled_features(&features); let device = unsafe { instance.create_device(*self.physical_device.get().unwrap(), &device_info, None) }?; // So, this is a little confusing. Queues are found in queue families. // The family has an index within the device, and the queue has an index // within the family. We computed the family index above, and when we // created the device we told it to create just a single queue in that // family. Now we pass both indices to find the actual queue object. let graphics_queue = unsafe { device.get_device_queue(indices.graphics, 0) }; let presentation_queue = unsafe { device.get_device_queue(indices.presentation, 0) }; self.device.set(device).unwrap(); self.graphics_queue.set(graphics_queue).unwrap(); self.presentation_queue.set(presentation_queue).unwrap(); } Ok(()) } #[allow(unsafe_code)] fn init_vulkan_swapchain(&mut self) -> Result<()> { // Here, too, we need to clone the smart pointer to simplify ownership. let physical_device = self.physical_device.get().unwrap().clone(); if let Acceptable::Accepted(features) = self.find_device_swapchain_features(&physical_device)? { let physical_device = self.physical_device.get().unwrap().clone(); let device = self.device.get().unwrap().clone(); let surface = self.surface.get().unwrap().clone(); let format = self.pick_surface_format(&features.formats)?; let presentation_mode = self.pick_presentation_mode(&features.presentation_modes)?; let extent = self.pick_image_extent(features.capabilities)?; let mut image_count = features.capabilities.min_image_count + 1; if features.capabilities.max_image_count != 0 { image_count = image_count.clamp(0, features.capabilities.max_image_count); } // We call this yet again. It needs to return the same thing here // that it did before; fortunately it's simple enough that that's a safe // assumption. let indices = self.find_device_queue_family_indices(&physical_device)? .unwrap(); 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(features.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); } self.swapchain.set(swapchain).unwrap(); self.swapchain_images.set(images).unwrap(); self.swapchain_image_views.set(image_views).unwrap(); self.format.set(format.format).unwrap(); self.extent.set(extent).unwrap(); } Ok(()) } #[allow(unsafe_code)] fn init_render_pass(&mut self) -> Result<()> { if self.render_pass.get().is_none() { let device = self.device.get().unwrap(); let format = self.format.get().unwrap(); 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) }?; self.render_pass.set(render_pass).unwrap(); } Ok(()) } #[allow(unsafe_code)] fn init_pipeline(&mut self) -> Result<()> { if self.pipeline.get().is_none() { let device = self.device.get().unwrap().clone(); let extent = self.extent.get().unwrap().clone(); let render_pass = self.render_pass.get().unwrap().clone(); 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 = self.load_spirv_shader_module(vertex_binary)?; let fragment_module = self.load_spirv_shader_module(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]; self.pipeline_layout.set(pipeline_layout).unwrap(); self.pipeline.set(pipeline).unwrap(); unsafe { device.destroy_shader_module(vertex_module, None); device.destroy_shader_module(fragment_module, None); }; } Ok(()) } #[allow(unsafe_code)] fn init_framebuffers(&mut self) -> Result<()> { if self.framebuffers.get().is_none() { let device = self.device.get().unwrap().clone(); let extent = self.extent.get().unwrap().clone(); let image_views = self.swapchain_image_views.get().unwrap(); let render_pass = self.render_pass.get().unwrap(); let mut framebuffers = Vec::new(); for image_view in 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); } self.framebuffers.set(framebuffers).unwrap(); } Ok(()) } #[allow(unsafe_code)] fn init_commands(&mut self) -> Result<()> { if self.command_pool.get().is_none() { let physical_device = self.physical_device.get().unwrap().clone(); let device = self.device.get().unwrap().clone(); // We call this one last time. It's kind of a problem. let indices = self.find_device_queue_family_indices(&physical_device)? .unwrap(); 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) }?; self.command_pool.set(command_pool).unwrap(); } if self.command_buffers.get().is_none() { let device = self.device.get().unwrap(); let extent = self.extent.get().unwrap(); let framebuffers = self.framebuffers.get().unwrap(); let render_pass = self.render_pass.get().unwrap(); let pipeline = self.pipeline.get().unwrap(); let command_pool = self.command_pool.get().unwrap(); 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) }?; } self.command_buffers.set(command_buffers).unwrap(); } Ok(()) } #[allow(unsafe_code)] fn init_concurrency(&mut self) -> Result<()> { let device = self.device.get().unwrap(); let swapchain_images = self.swapchain_images.get().unwrap(); 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) }?); } self.image_available_semaphores .set(image_available_semaphores).unwrap(); self.rendering_finished_semaphores .set(rendering_finished_semaphores).unwrap(); self.frame_fences.set(frame_fences).unwrap(); for _ in 0 .. swapchain_images.len() { self.image_fences.push(vk::Fence::null()); } Ok(()) } // To Vulkan, a "physical" device is the actual GPU, and a "logical" // device is per-process state that represents a connection to the GPU. // Before we can create a logical device, we must choose which physical // device to connect it to. #[allow(unsafe_code)] fn pick_vulkan_device(&mut self) -> Result { let mut best_device = None; let mut best_score = None; let mut rejected = BTreeMap::new(); for device in unsafe { self.instance.get().unwrap().enumerate_physical_devices() }? { match self.score_vulkan_device(&device)? { Acceptable::Accepted(new_score) => { if let Some(old_score) = best_score { if new_score > old_score { best_device = Some(device); best_score = Some(new_score); } } else { best_device = Some(device); best_score = Some(new_score); } } Acceptable::Rejected(reason) => { let properties = unsafe { self.instance.get().unwrap() .get_physical_device_properties(device) }; let name = properties.device_name.to_string_lossy().into_owned(); rejected.insert(properties.device_id, (name, reason)); } } } if let Some(device) = best_device { Ok(device) } else if rejected.is_empty() { Err(Error { message: "The system has no GPUs of any kind.".to_string() }) } else { for (_, (name, reason)) in rejected { eprintln!("Can't run on {} because: {}", name, reason); } Err(Error { message: "The system has GPUs, but none are acceptable (see above)." .to_string() }) } } // We're doing two tasks: Quantifying how strongly we prefer a device, and // deciding whether it's acceptable at all. If it's unacceptable, it's // possible there will be no acceptable devices, and in that case our caller // will want to print explanations, but otherwise it'll want to be quiet. So // the outer Result is whether we successfully evaluated the device, and the // inner Acceptable is whether we approve of it. #[allow(unsafe_code)] fn score_vulkan_device(&mut self, device: &vk::PhysicalDevice) -> Result> { // Not all devices support graphics, and not all devices support // presenting to any given surface. We check whether this one is suitable // by looking up the indices of the queue families we would use, though // we ignore the actual values and recompute them later. if let Acceptable::Rejected(rationale) = self.find_device_queue_family_indices(device)? { return Ok(Acceptable::Rejected(rationale)); } let instance = self.instance.get().unwrap(); // At this point we know the device meets our high-level requirements, // so it's just a question of scoring. let properties = unsafe { instance.get_physical_device_properties(*device) }; let mut score = 0; if properties.device_type == vk::PhysicalDeviceType::DISCRETE_GPU { // If the user has a fancy GPU, they prefer it. score += 128; } else if properties.device_type == vk::PhysicalDeviceType::INTEGRATED_GPU { // It's still hardware rendering. score += 96; } else if properties.device_type == vk::PhysicalDeviceType::VIRTUAL_GPU { // Whatever it is, the user went to some trouble to set it up. score += 64; } else if properties.device_type == vk::PhysicalDeviceType::CPU { // Software rendering is slow, but at least it's a known quantity. score += 32; } // If it's none of those, we don't have enough information to know if // that's good or bad, so we assume it's bad. // Some of our scoring will depend on what extensions the device // supports, so we enumerate those. let mut available_extensions = HashSet::new(); for extension in unsafe { instance.enumerate_device_extension_properties(*device, None) }? { available_extensions.insert(extension.extension_name); } let available_extensions = available_extensions; if available_extensions.contains(&vk::KHR_SWAPCHAIN_EXTENSION.name) { // Double buffering is both quite a nice feature to have, and a good // indicator that this is a "real" graphics card rather than some // trivial weird thing. // // With that said, however, it only counts if we're able to actually // use it on the surface we have. Let's find out... if let Acceptable::Accepted(_) = self.find_device_swapchain_features(device)? { // We don't count it for enough points to override a device type // bracket, but it's good for a lot within the bracket. score += 16; } // This isn't disqualifying, so we don't worry about tracking the // rationale. We'll deal with that later, if the device actually gets // selected. } Ok(Acceptable::Accepted(score)) } // TODO: save the result of this somewhere and only call it once #[allow(unsafe_code)] fn find_device_queue_family_indices(&mut self, device: &vk::PhysicalDevice) -> Result> { let instance = self.instance.get().unwrap(); let surface = self.surface.get().unwrap(); // We need a queue family that supports graphics drawing commands, and a // queue family that supports presentation commands. These may or may not // be the same family. let mut graphics = None; let mut presentation = None; for (index, queue_family) in (unsafe { instance.get_physical_device_queue_family_properties(*device) }).into_iter().enumerate() { if graphics.is_none() && queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS) { graphics = Some(index as u32); } if presentation.is_none() && unsafe { instance.get_physical_device_surface_support_khr( *device, index as u32, *surface) }? { presentation = Some(index as u32); } } if let Some(graphics) = graphics { if let Some(presentation) = presentation { Ok(Acceptable::Accepted(QueueFamilyIndices { graphics, presentation })) } else { Ok(Acceptable::Rejected( "Doesn't support presenting to our window.".to_string())) } } else { Ok(Acceptable::Rejected("Doesn't support graphics.".to_string())) } } // We expect our caller to have already verified that the device supports // the swapchain extension. #[allow(unsafe_code)] fn find_device_swapchain_features(&mut self, device: &vk::PhysicalDevice) -> Result> { let instance = self.instance.get().unwrap(); let surface = self.surface.get().unwrap(); let capabilities = unsafe { instance.get_physical_device_surface_capabilities_khr( *device, *surface) }?; let formats = unsafe { instance.get_physical_device_surface_formats_khr( *device, *surface) }?; let presentation_modes = unsafe { instance.get_physical_device_surface_present_modes_khr( *device, *surface) }?; if formats.is_empty() { Ok(Acceptable::Rejected("No matching surface formats.".to_string())) } else if presentation_modes.is_empty() { Ok(Acceptable::Rejected("No matching presentation modes.".to_string())) } else { Ok(Acceptable::Accepted(SwapchainFeatures { capabilities, formats, presentation_modes })) } } fn pick_surface_format(&mut self, 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(&mut self, _available_modes: &Vec) -> Result { // It's guaranteed to have this one. return Ok(vk::PresentModeKHR::FIFO); } fn pick_image_extent(&mut self, capabilities: vk::SurfaceCapabilitiesKHR) -> Result { if capabilities.current_extent.width != u32::MAX && capabilities.current_extent.height != u32::MAX { Ok(capabilities.current_extent) } else { let window = self.window.get().unwrap(); 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()) } } #[allow(unsafe_code)] fn load_spirv_shader_module(&mut self, binary: &[u8]) -> Result { let device = self.device.get().unwrap(); 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(window) = self.window.get() && window_id == window.id() { let device = self.device.get().unwrap(); let graphics_queue = self.graphics_queue.get().unwrap(); let presentation_queue = self.presentation_queue.get().unwrap(); let swapchain = self.swapchain.get().unwrap(); let command_buffers = self.command_buffers.get().unwrap(); let frame_index = self.frame_index; let image_available_semaphore = self.image_available_semaphores.get().unwrap()[frame_index]; let rendering_finished_semaphore = self.rendering_finished_semaphores.get() .unwrap()[frame_index]; let frame_fence = self.frame_fences.get().unwrap()[frame_index]; unsafe { device.wait_for_fences(&[frame_fence], true, u64::MAX) }?; let image_index = unsafe { device.acquire_next_image_khr(*swapchain, u64::MAX, image_available_semaphore, vk::Fence::null()) }?.0 as usize; let image_fence = self.image_fences[image_index]; if !image_fence.is_null() { unsafe { device.wait_for_fences(&[image_fence], true, u64::MAX) }?; } self.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 = [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(*graphics_queue, &[submit_info], frame_fence) }?; let swapchains = [*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(*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(device) = self.device.get() { unsafe { device.device_wait_idle() }.unwrap(); } if let Some(image_available_semaphores) = self.image_available_semaphores.get() && let Some(device) = self.device.get() { for semaphore in image_available_semaphores { unsafe { device.destroy_semaphore(*semaphore, None) }; } } if let Some(rendering_finished_semaphores) = self.rendering_finished_semaphores.get() && let Some(device) = self.device.get() { for semaphore in rendering_finished_semaphores { unsafe { device.destroy_semaphore(*semaphore, None) }; } } if let Some(frame_fences) = self.frame_fences.get() && let Some(device) = self.device.get() { for fence in frame_fences { unsafe { device.destroy_fence(*fence, None) }; } } if let Some(command_pool) = self.command_pool.get() && let Some(device) = self.device.get() { unsafe { device.destroy_command_pool(*command_pool, None) }; } if let Some(framebuffers) = self.framebuffers.get() && let Some(device) = self.device.get() { for framebuffer in framebuffers { unsafe { device.destroy_framebuffer(*framebuffer, None) }; } } if let Some(pipeline) = self.pipeline.get() && let Some(device) = self.device.get() { unsafe { device.destroy_pipeline(*pipeline, None) }; } if let Some(render_pass) = self.render_pass.get() && let Some(device) = self.device.get() { unsafe { device.destroy_render_pass(*render_pass, None) }; } if let Some(pipeline_layout) = self.pipeline_layout.get() && let Some(device) = self.device.get() { unsafe { device.destroy_pipeline_layout(*pipeline_layout, None) }; } if let Some(image_views) = self.swapchain_image_views.get() && let Some(device) = self.device.get() { for view in image_views { unsafe { device.destroy_image_view(*view, None) }; } } if let Some(swapchain) = self.swapchain.get() && let Some(device) = self.device.get() { unsafe { device.destroy_swapchain_khr(*swapchain, None) }; } if let Some(surface) = self.surface.get() && let Some(instance) = self.instance.get() { unsafe { instance.destroy_surface_khr(*surface, None) }; } if let Some(device) = self.device.get() { unsafe { device.destroy_device(None) }; } // Everything but the instance itself should already be destroyed, // before we destroy the debug messager. The special hook to get debug // messages while destroying the instance itself only applies to the // instance and the messager, so if we were to destroy anything we // shouldn't after this point, we'd miss out on diagnostics. if let Some(debug_messager) = self.debug_messager.get() && let Some(instance) = self.instance.get() { unsafe { instance.destroy_debug_utils_messenger_ext(*debug_messager, None); } } if let Some(instance) = self.instance.get() { unsafe { instance.destroy_instance(None) }; } } } 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) } } } #[allow(unsafe_code)] extern "system" fn debug_messager_callback( severity: vk::DebugUtilsMessageSeverityFlagsEXT, flags: vk::DebugUtilsMessageTypeFlagsEXT, data: *const vk::DebugUtilsMessengerCallbackDataEXT, _context: *mut c_void) -> vk::Bool32 { // Vulkan sends us everything, it's up to us to apply any filtering we // want. The thing about this is that games need to be debuggable by end // users, to diagnose compatibility issues and weird configurations, so we // still want SOMETHING even when we're built in release mode. // // For now, we'll see if we can get away without providing runtime config // stuff for diagnostics. We set the threshold pretty high in release mode, // on the theory that our own diagnostics should be sufficient. // // Making this strategy work does rely on us actually checking error // conditions and reporting them in useful ways, so that we only need // Vulkan's messages for things we truly couldn't have anticipated. We do // not take a more-is-better approach to diagnostics; the ideal would be to // provide all the crucial information, and nothing else. let threshold = if cfg!(feature = "vulkan-validation") || cfg!(debug_assertions) { vk::DebugUtilsMessageSeverityFlagsEXT::WARNING } else { vk::DebugUtilsMessageSeverityFlagsEXT::ERROR }; if severity >= threshold { let data = unsafe { *data }; let text = unsafe { CStr::from_ptr(data.message) }.to_string_lossy(); let label = if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::ERROR { "error" } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::WARNING { "warning" } else if severity >= vk::DebugUtilsMessageSeverityFlagsEXT::INFO { "informational message" } else { "message of unknown, very minor significance" }; eprintln!("Vulkan {}: {} (flags {:?})", label, text, flags); } // A return value of true would tell the validation layer we're unhappy // with it, for the sake of conformance testing. We're not a conformance // test so anything it does is fine with us. vk::FALSE }