diff options
| author | Irene Knapp <ireneista@irenes.space> | 2026-08-06 16:49:10 -0700 |
|---|---|---|
| committer | Irene Knapp <ireneista@irenes.space> | 2026-08-06 16:49:10 -0700 |
| commit | 26e89267c0d745793d777ad0b1157a5596258515 (patch) | |
| tree | de3d5f79a06a649a46803ba0ecc88375acc2821c /src/graphics | |
| parent | 2c8110d93e04a1bfd976fe20c8c7493a41d51eae (diff) | |
move some modules into a new graphics submodule
Force-Push: yes Change-Id: I5cbdf59870258f099fc6dd47ff2f1566376c1585
Diffstat (limited to 'src/graphics')
| -rw-r--r-- | src/graphics/mod.rs | 5 | ||||
| -rw-r--r-- | src/graphics/permanent.rs | 842 | ||||
| -rw-r--r-- | src/graphics/scene.rs | 118 | ||||
| -rw-r--r-- | src/graphics/window_dressing.rs | 1738 |
4 files changed, 2703 insertions, 0 deletions
diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs new file mode 100644 index 0000000..f164588 --- /dev/null +++ b/src/graphics/mod.rs @@ -0,0 +1,5 @@ +#![deny(unsafe_code)] + +pub mod permanent; +pub mod scene; +pub mod window_dressing; diff --git a/src/graphics/permanent.rs b/src/graphics/permanent.rs new file mode 100644 index 0000000..5282e21 --- /dev/null +++ b/src/graphics/permanent.rs @@ -0,0 +1,842 @@ +#![deny(unsafe_code)] +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, + DeviceV1_0, EntryV1_0, InstanceV1_0, + ExtDebugUtilsExtensionInstanceCommands, + KhrSurfaceExtensionInstanceCommands }; +use winit::dpi::LogicalSize; +use winit::event_loop::ActiveEventLoop; +use winit::window::{ Window, WindowAttributes }; + + +const VULKAN_FIRST_PORTABILITY_VERSION: Version = Version::new(1, 3, 216); + + +// The PermanentGraphicsState collects the various windowing-system and +// Vulkan objects which never need to be regenerated once they're created. The +// ones which do need that are collected below, in WindowDressing. +pub struct PermanentGraphicsState { + // 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. + pub window: Window, + + // The Vulkan "entry" is the part of the Vulkan library ecosystem that's + // responsible for finding and loading the other parts. Once we have the + // instance, the entry is never directly used again, but we retain it + // because doing otherwise would segfault. + #[allow(unused)] + entry: Entry, + + // The Vulkan "instance" is the bulk of the Vulkan library, with most of + // the high-level responsibilities around lifecycle management. + pub instance: Instance, + + // 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. + // + // Once we've created this, we never actually need to do anything with + // it, but we do need to retain it, so here it is. + debug_messager: Option<vk::DebugUtilsMessengerEXT>, + + // The Vulkan "surface" is the destination that rendering happens into. + // It is connected to the window but distinct from it. Since we always have + // exactly one window, this is permanent state. + pub surface: vk::SurfaceKHR, + + // 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 pick a physical + // device during initialization, but only the logical one is used later, so + // it's all we track. We'll be referencing the logical device a lot, so we + // follow Vulkan's lead and let it have a short variable name. + pub device: Device, + + // 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. Alas. + pub graphics_queue: vk::Queue, + pub presentation_queue: vk::Queue, +} + + +#[derive(Debug)] +pub struct QueueFamilyIndices { + pub graphics: u32, + pub presentation: u32, +} + +// These structs exist for use in function calling, to remove the potential +// for accidentally passing or returning one boolean as if it's another. +struct EnablePortability(bool); +struct EnableValidation(bool); +pub struct EnableAnisotropy(pub bool); +pub struct EnableSwapchain(pub bool); + + +impl PermanentGraphicsState { + #[allow(unsafe_code)] + pub fn new(event_loop: &ActiveEventLoop) + -> Result<(Self, GraphicsStateForReinit, EnableAnisotropy, + EnableSwapchain)> + { + let window = init_window(event_loop)?; + + // There are a few Vulkan features (in the informal sense of + // "feature") that we want to be able to run both with and without. + // The enable_* values, here and below, are wrapped booleans that describe + // those choices. + // + // These are only used to communicate between initialization + // phases; we don't keep them around after that. + let (entry, instance, debug_messager, + enable_portability, enable_validation) + = init_vulkan(&window)?; + + // 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) + }?; + + let (physical_device, device, indices, sample_count, graphics_queue, + presentation_queue, enable_anisotropy, enable_swapchain) + = init_vulkan_device(&instance, &surface, + enable_validation, enable_portability)?; + + let descriptor_set_layout = init_descriptor_set_layout(&device)?; + + Ok((PermanentGraphicsState { + window, entry, instance, debug_messager, surface, device, + graphics_queue, presentation_queue + }, GraphicsStateForReinit { + physical_device, indices, sample_count, descriptor_set_layout, + }, enable_anisotropy, enable_swapchain)) + } + + #[allow(unsafe_code)] + pub fn destroy(self) -> () { + unsafe { self.device.destroy_device(None) }; + + unsafe { self.instance.destroy_surface_khr(self.surface, 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 { + unsafe { + self.instance.destroy_debug_utils_messenger_ext(debug_messager, None); + } + } + + unsafe { self.instance.destroy_instance(None) }; + } + + // We expect our caller to have already verified that the device supports + // the swapchain extension. + #[allow(unsafe_code)] + pub fn find_device_swapchain_features(instance: &Instance, + surface: &vk::SurfaceKHR, + physical_device: &vk::PhysicalDevice) + -> Result<Acceptable<(vk::SurfaceCapabilitiesKHR, + Vec<vk::SurfaceFormatKHR>, + Vec<vk::PresentModeKHR>)>> + { + let capabilities = unsafe { + instance.get_physical_device_surface_capabilities_khr( + *physical_device, *surface) + }?; + let formats = unsafe { + instance.get_physical_device_surface_formats_khr( + *physical_device, *surface) + }?; + let presentation_modes = unsafe { + instance.get_physical_device_surface_present_modes_khr( + *physical_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((capabilities, formats, presentation_modes))) + } + } + + + #[allow(unsafe_code)] + pub fn load_spirv_shader_module(device: &Device, binary: &[u8]) + -> Result<vk::ShaderModule> + { + 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) + } +} + + +// The GraphicsStateForReinit connects Vulkan objects which are only needed +// during the creation of the window-dressing objects. They are used during +// initial startup, and again any time the window-dressing needs to be +// reinitialized. Most notably, they are not needed when rendering. +pub struct GraphicsStateForReinit { + pub physical_device: vk::PhysicalDevice, + pub indices: QueueFamilyIndices, + pub sample_count: vk::SampleCountFlags, + pub descriptor_set_layout: vk::DescriptorSetLayout, +} + + +impl GraphicsStateForReinit { + #[allow(unsafe_code)] + pub fn destroy(self, device: &Device) -> () { + unsafe { + device.destroy_descriptor_set_layout(self.descriptor_set_layout, None) + }; + } +} + + +fn init_window(event_loop: &ActiveEventLoop) -> Result<Window> { + // Notice that we do this before having a Vulkan instance. The window is + // actually a parameter needed to create the instance; see + // init_vulkan(), below. + let window_attributes = WindowAttributes::default() + .with_title("Love, Curiosity, Justice") + .with_inner_size(LogicalSize::new(1024, 768)); + + Ok(event_loop.create_window(window_attributes)?) +} + + +#[allow(unsafe_code)] +fn init_vulkan(window: &Window) + -> Result<(Entry, Instance, Option<vk::DebugUtilsMessengerEXT>, + EnablePortability, EnableValidation)> +{ + let enable_validation = cfg!(feature = "vulkan-validation") + || cfg!(debug_assertions); + + // 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) }?; + + // 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( + window) + { + extensions.push(extension.as_ptr()); + } + + // Deal with Vulkan's thing about opting in to non-conforming + // implementations. + let enable_portability = 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); + + EnablePortability(true) + } else { + EnablePortability(false) + } + } else { + EnablePortability(false) + }; + + // Request the LunarG validation layer, when appropriate. + let validation_layer_name = vk::ExtensionName::from_bytes( + b"VK_LAYER_KHRONOS_validation"); + let enable_validation = if enable_validation { + if available_layers.contains(&validation_layer_name) { + layers.push(validation_layer_name.as_ptr()); + + EnableValidation(true) + } else { + eprintln!("Vulkan validation requested at build time, \ + but no validation layer available."); + + EnableValidation(false) + } + } else { + EnableValidation(false) + }; + + // 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. + let debug_messager = if let Some(debug_info) = debug_info { + #[allow(unsafe_code)] + Some(unsafe { + instance.create_debug_utils_messenger_ext(&debug_info, None) + }?) + } else { + None + }; + + Ok((entry, instance, debug_messager, + enable_portability, enable_validation)) +} + + +#[allow(unsafe_code)] +fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR, + enable_validation: EnableValidation, + enable_portability: EnablePortability) + -> Result<(vk::PhysicalDevice, Device, QueueFamilyIndices, + vk::SampleCountFlags, vk::Queue, vk::Queue, EnableAnisotropy, + EnableSwapchain)> +{ + let (physical_device, indices, sample_count) + = pick_vulkan_device(instance, surface)?; + + // 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 available_features = unsafe { + instance.get_physical_device_features(physical_device) + }; + let mut features = vk::PhysicalDeviceFeatures::builder(); + let mut extensions = Vec::new(); + let mut layers = Vec::new(); + + let validation_layer_name = vk::ExtensionName::from_bytes( + b"VK_LAYER_KHRONOS_validation"); + if enable_validation.0 { + // 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(&validation_layer_name) { + layers.push(validation_layer_name.as_ptr()); + } + } + + let portability_extension_name = vk::ExtensionName::from_bytes( + b"VK_KHR_portability_subset"); + if enable_portability.0 { + // 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. + if available_extensions.contains(&portability_extension_name) { + extensions.push(portability_extension_name.as_ptr()); + } + } + + let swapchain_extension_name = vk::KHR_SWAPCHAIN_EXTENSION.name; + let enable_swapchain = if available_extensions.contains( + &swapchain_extension_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(_) + = PermanentGraphicsState::find_device_swapchain_features( + &instance, &surface, &physical_device)? + { + extensions.push(swapchain_extension_name.as_ptr()); + + EnableSwapchain(true) + } else { + EnableSwapchain(false) + } + } else { + EnableSwapchain(false) + }; + + let enable_anisotropy = if available_features.sampler_anisotropy + == vk::TRUE + { + features = features.sampler_anisotropy(true); + + EnableAnisotropy(true) + } else { + EnableAnisotropy(false) + }; + + // 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(physical_device, &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) + }; + + Ok((physical_device, device, indices, sample_count, graphics_queue, + presentation_queue, enable_anisotropy, enable_swapchain)) +} + + +#[allow(unsafe_code)] +fn init_descriptor_set_layout(device: &Device) + -> Result<vk::DescriptorSetLayout> +{ + let uniform_block_binding = vk::DescriptorSetLayoutBinding::builder() + .binding(0) + .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::VERTEX); + + let sampler_binding = vk::DescriptorSetLayoutBinding::builder() + .binding(1) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::FRAGMENT); + + let bindings = [uniform_block_binding, sampler_binding]; + let descriptor_set_layout_info + = vk::DescriptorSetLayoutCreateInfo::builder() + .bindings(&bindings); + let descriptor_set_layout = unsafe { + device.create_descriptor_set_layout(&descriptor_set_layout_info, None) + }?; + + Ok(descriptor_set_layout) +} + + + +// 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(instance: &Instance, surface: &vk::SurfaceKHR) + -> Result<(vk::PhysicalDevice, QueueFamilyIndices, vk::SampleCountFlags)> +{ + let mut best_device = None; + let mut best_score = None; + let mut best_indices = None; + let mut best_sample_count = None; + let mut rejected = BTreeMap::new(); + + for device in unsafe { instance.enumerate_physical_devices() }? { + match score_vulkan_device(instance, surface, &device)? { + Acceptable::Accepted((new_score, new_indices, new_sample_count)) => { + if let Some(old_score) = best_score { + if new_score > old_score { + best_device = Some(device); + best_score = Some(new_score); + best_indices = Some(new_indices); + best_sample_count = Some(new_sample_count); + } + } else { + best_device = Some(device); + best_score = Some(new_score); + best_indices = Some(new_indices); + best_sample_count = Some(new_sample_count); + } + } + Acceptable::Rejected(reason) => { + let properties = unsafe { + instance.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), Some(indices), Some(sample_count)) + = (best_device, best_indices, best_sample_count) + { + Ok((device, indices, sample_count)) + } 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. +// +// In the event that we find the device acceptable, we also return the +// queue family indices we'd be using if we ultimately go with it. While +// this is not strictly necessary, it's better to return them from here +// than to recompute them later on the assumption it'll work out the same. +#[allow(unsafe_code)] +fn score_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR, + physical_device: &vk::PhysicalDevice) + -> Result<Acceptable<(u64, QueueFamilyIndices, vk::SampleCountFlags)>> +{ + // 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. If we + // ultimately use this device, we'll need these, so we make sure to return + // them. + let indices = match find_device_queue_family_indices( + instance, surface, physical_device)? + { + Acceptable::Rejected(rationale) => { + return Ok(Acceptable::Rejected(rationale)); + } + Acceptable::Accepted(indices) => indices + }; + + // 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(*physical_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(*physical_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(_) + = PermanentGraphicsState::find_device_swapchain_features( + instance, surface, physical_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. + } + + let features = unsafe { + instance.get_physical_device_features(*physical_device) + }; + if features.sampler_anisotropy == vk::TRUE { + score += 1; + } + + let sample_count = properties.limits.framebuffer_color_sample_counts + & properties.limits.framebuffer_depth_sample_counts; + // Happily, these bit flags are arranged in the obvious way, which lets us + // do some math on them. The max sample count is 64, so the max score bonus + // we give is 4. + let shift = sample_count.bits().ilog2() as u64; + score += shift; + let sample_count = vk::SampleCountFlags::from_bits(1 << shift).unwrap(); + + Ok(Acceptable::Accepted((score, indices, sample_count))) +} + + +#[allow(unsafe_code)] +fn find_device_queue_family_indices(instance: &Instance, + surface: &vk::SurfaceKHR, + device: &vk::PhysicalDevice) + -> Result<Acceptable<QueueFamilyIndices>> +{ + // 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())) + } +} + + +#[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 +} diff --git a/src/graphics/scene.rs b/src/graphics/scene.rs new file mode 100644 index 0000000..e59ae89 --- /dev/null +++ b/src/graphics/scene.rs @@ -0,0 +1,118 @@ +#![deny(unsafe_code)] +use crate::error::*; +use crate::graphics::window_dressing::RenderState; +use crate::linear_algebra::{ Vec3, Vec4, Transformation }; +use crate::shader_data::VertexPushBlock; + +use std::f32::consts::{ TAU }; +use std::mem::size_of; + +use vulkanalia::Device; +use vulkanalia::vk::{ self, HasBuilder, DeviceV1_0 }; + + +#[allow(unsafe_code)] +pub fn generate_scene_commands<'a>(image_index: usize, time: f32, + render_state: &'a RenderState, + device: &Device, extent: &vk::Extent2D) + -> Result<&'a vk::CommandBuffer> +{ + let command_buffer = &render_state.command_buffers[image_index]; + let framebuffer = &render_state.framebuffers[image_index]; + let descriptor_set = &render_state.descriptor_sets[image_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 color_clear_value = vk::ClearValue { + color: vk::ClearColorValue { + float32: [0.0, 0.0, 0.0, 1.0] + } + }; + let depth_clear_value = vk::ClearValue { + depth_stencil: vk::ClearDepthStencilValue { + depth: 1.0, + stencil: 0, + } + }; + let clear_values = [color_clear_value, depth_clear_value]; + + let begin_pass_info = vk::RenderPassBeginInfo::builder() + .render_pass(render_state.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, + render_state.pipeline) + }; + + unsafe { + device.cmd_bind_vertex_buffers(*command_buffer, 0, + &[render_state.vertex_buffer], &[0]) + }; + + unsafe { + device.cmd_bind_index_buffer(*command_buffer, render_state.index_buffer, + 0, vk::IndexType::UINT32) + }; + + unsafe { + device.cmd_bind_descriptor_sets(*command_buffer, + vk::PipelineBindPoint::GRAPHICS, + render_state.pipeline_layout, + 0, + &[*descriptor_set], + &[]) + }; + + let scale = Vec3::new(1.0, 1.0, 1.0); + let model = Transformation { + rotation: Vec4::rotation_quaternion(&Vec3::new(0.0, 1.0, 0.0), time % TAU), + translation: Vec3::new(0.0, 0.0, 0.0), + }; + let push_block = VertexPushBlock::<f32> { scale, model }; + let size = size_of::<VertexPushBlock::<f32>>(); + let push_block_bytes = unsafe { + std::slice::from_raw_parts(&push_block as *const VertexPushBlock<f32> + as *const u8, + size) + }; + + unsafe { + device.cmd_push_constants(*command_buffer, render_state.pipeline_layout, + vk::ShaderStageFlags::VERTEX, + 0, + push_block_bytes) + }; + + unsafe { + device.cmd_draw_indexed(*command_buffer, render_state.index_count as u32, + 1, 0, 0, 0) + }; + + unsafe { device.cmd_end_render_pass(*command_buffer) }; + + unsafe { device.end_command_buffer(*command_buffer) }?; + + Ok(command_buffer) +} + diff --git a/src/graphics/window_dressing.rs b/src/graphics/window_dressing.rs new file mode 100644 index 0000000..dc7f47f --- /dev/null +++ b/src/graphics/window_dressing.rs @@ -0,0 +1,1738 @@ +#![deny(unsafe_code)] +use crate::error::*; +use crate::graphics::permanent::{ + PermanentGraphicsState, GraphicsStateForReinit, QueueFamilyIndices, + EnableAnisotropy +}; +use crate::model_loader::load_model; +use crate::shader_data::{ Vertex, UniformBlock, VertexPushBlock }; + +use std::collections::BTreeSet; +use std::io::Cursor; +use std::mem::size_of; +use std::ptr::copy_nonoverlapping; + +use png::Decoder; +use vulkanalia::{ Device, Instance }; +use vulkanalia::vk::{ self, Handle, HasBuilder, InstanceV1_0, 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, but are not needed during rendering. The ones which don't need to +// be regenerated are collected in PermanentGraphicsState. The ones which are +// needed during rendering are collected in RenderState, below. +#[derive(Debug)] +pub struct WindowDressing { + pub swapchain: Swapchain, + + color_image: vk::Image, + color_image_memory: vk::DeviceMemory, + color_image_view: vk::ImageView, + + depth_image: vk::Image, + depth_image_memory: vk::DeviceMemory, + depth_image_view: vk::ImageView, + depth_format: vk::Format, + + primary_command_pool: vk::CommandPool, + transient_command_pool: vk::CommandPool, + + texture_image: vk::Image, + texture_image_memory: vk::DeviceMemory, + texture_image_view: vk::ImageView, + mip_count: u32, + sampler: vk::Sampler, + + uniform_buffers: Vec<vk::Buffer>, + pub uniform_buffer_memory: Vec<vk::DeviceMemory>, + + descriptor_pool: vk::DescriptorPool, + + pub concurrency: Concurrency, +} + +// The RenderState collects the Vulkan graphics objects which need to be +// regenerated or modified when the window changes, as with WindowDressing, +// and which are also used as part of rendering. +#[derive(Debug)] +pub struct RenderState { + pub render_pass: vk::RenderPass, + + pub pipeline: vk::Pipeline, + pub pipeline_layout: vk::PipelineLayout, + + pub vertex_buffer: vk::Buffer, + vertex_buffer_memory: vk::DeviceMemory, + + pub index_buffer: vk::Buffer, + index_buffer_memory: vk::DeviceMemory, + pub index_count: usize, + + pub framebuffers: Vec<vk::Framebuffer>, + pub command_buffers: Vec<vk::CommandBuffer>, + pub descriptor_sets: Vec<vk::DescriptorSet>, +} + +// 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, + pub 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, + for_reinit: &GraphicsStateForReinit, + enable_anisotropy: EnableAnisotropy) + -> Result<Self> + { + let window = &permanent.window; + let instance = &permanent.instance; + let surface = &permanent.surface; + let device = &permanent.device; + let graphics_queue = &permanent.graphics_queue; + let physical_device = &for_reinit.physical_device; + let sample_count = for_reinit.sample_count; + let indices = &for_reinit.indices; + + let swapchain = init_swapchain( + window, instance, surface, &physical_device, device, &indices)?; + + let (color_image, color_image_memory, color_image_view) + = init_color(instance, &physical_device, device, + &swapchain.extent, sample_count, swapchain.format)?; + + let (depth_image, depth_image_memory, depth_image_view, depth_format) + = init_depth(instance, &physical_device, device, + &swapchain.extent, sample_count)?; + + let (primary_command_pool, transient_command_pool) + = init_command_pools(device, indices)?; + + let (texture_image, texture_image_memory, texture_image_view, mip_count) + = init_texture(instance, physical_device, device, + graphics_queue, &transient_command_pool)?; + + let sampler = init_sampler(&device, &enable_anisotropy, mip_count)?; + + let (uniform_buffers, uniform_buffer_memory) + = init_uniform_buffers(instance, physical_device, device, + swapchain.images.len())?; + + let descriptor_pool + = init_descriptor_pool(device, swapchain.images.len())?; + + let concurrency = init_concurrency(device, &swapchain.images)?; + + Ok(WindowDressing { + swapchain, + color_image, + color_image_memory, + color_image_view, + depth_image, + depth_image_memory, + depth_image_view, + depth_format, + texture_image, + texture_image_memory, + texture_image_view, + mip_count, + sampler, + uniform_buffers, + uniform_buffer_memory, + descriptor_pool, + primary_command_pool, + transient_command_pool, + concurrency, + }) + } + + + #[allow(unsafe_code)] + pub fn reinit(&mut self, permanent: &PermanentGraphicsState, + for_reinit: &GraphicsStateForReinit) + -> Result<()> + { + let window = &permanent.window; + let instance = &permanent.instance; + let surface = &permanent.surface; + let device = &permanent.device; + let physical_device = &for_reinit.physical_device; + let sample_count = for_reinit.sample_count; + let indices = &for_reinit.indices; + let descriptor_set_layout = &for_reinit.descriptor_set_layout; + + unsafe { device.device_wait_idle() }.unwrap(); + + self.destroy_replaceable(device); + + let swapchain = init_swapchain( + window, instance, surface, &physical_device, device, &indices)?; + + let (color_image, color_image_memory, color_image_view) + = init_color(instance, &physical_device, device, + &swapchain.extent, sample_count, swapchain.format)?; + + let (depth_image, depth_image_memory, depth_image_view, depth_format) + = init_depth(instance, &physical_device, device, + &swapchain.extent, sample_count)?; + + let (uniform_buffers, uniform_buffer_memory) + = init_uniform_buffers(instance, physical_device, device, + swapchain.images.len())?; + + // Notice that we did NOT reuse the descriptor pool. + let descriptor_pool + = init_descriptor_pool(device, swapchain.images.len())?; + + self.concurrency.image_fences.resize(swapchain.images.len(), + vk::Fence::null()); + + self.swapchain = swapchain; + self.color_image = color_image; + self.color_image_memory = color_image_memory; + self.color_image_view = color_image_view; + self.depth_image = depth_image; + self.depth_image_memory = depth_image_memory; + self.depth_image_view = depth_image_view; + self.depth_format = depth_format; + self.uniform_buffers = uniform_buffers; + self.uniform_buffer_memory = uniform_buffer_memory; + self.descriptor_pool = descriptor_pool; + + Ok(()) + } + + + // This relies on its caller to have already waited for the device to be + // idle. + #[allow(unsafe_code)] + pub fn destroy(mut self, device: &Device) { + self.destroy_replaceable(device); + + unsafe { device.destroy_image(self.texture_image, None) }; + unsafe { device.free_memory(self.texture_image_memory, None) }; + unsafe { device.destroy_image_view(self.texture_image_view, None) }; + unsafe { device.destroy_sampler(self.sampler, None) }; + + 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) }; + } + + // Notice that destroy_replaceable() freed the buffers in the pools, but + // did not destroy the pools. + unsafe { device.destroy_command_pool(self.primary_command_pool, None) }; + unsafe { device.destroy_command_pool(self.transient_command_pool, None) }; + } + + + #[allow(unsafe_code)] + fn destroy_replaceable(&mut self, device: &Device) { + // While the descriptor pool is also a pool, it has a preallocated size + // which will be different next time. So, we destroy it all the way. + unsafe { device.destroy_descriptor_pool(self.descriptor_pool, None) }; + + // Notice that, unlike the vertex and index buffers, we destroy and + // re-create these on every reinitialization. That's because the number of + // them depends on how many images the swapchain has. + for buffer in &self.uniform_buffers { + unsafe { device.destroy_buffer(*buffer, None) }; + } + for memory in &self.uniform_buffer_memory { + unsafe { device.free_memory(*memory, None) }; + } + + unsafe { device.destroy_image(self.color_image, None) }; + unsafe { device.free_memory(self.color_image_memory, None) }; + unsafe { device.destroy_image_view(self.color_image_view, None) }; + unsafe { device.destroy_image(self.depth_image, None) }; + unsafe { device.free_memory(self.depth_image_memory, None) }; + unsafe { device.destroy_image_view(self.depth_image_view, None) }; + + for view in &self.swapchain.image_views { + unsafe { device.destroy_image_view(*view, None) }; + } + + unsafe { device.destroy_swapchain_khr(self.swapchain.swapchain, None) }; + } +} + + +impl RenderState { + pub fn new(permanent: &PermanentGraphicsState, + for_reinit: &GraphicsStateForReinit, + window_dressing: &WindowDressing) + -> Result<Self> + { + let device = &permanent.device; + let instance = &permanent.instance; + let graphics_queue = &permanent.graphics_queue; + let physical_device = &for_reinit.physical_device; + let sample_count = for_reinit.sample_count; + let descriptor_set_layout = &for_reinit.descriptor_set_layout; + let primary_command_pool = &window_dressing.primary_command_pool; + let transient_command_pool = &window_dressing.transient_command_pool; + let swapchain = &window_dressing.swapchain; + let depth_format = &window_dressing.depth_format; + let color_image_view = &window_dressing.color_image_view; + let depth_image_view = &window_dressing.depth_image_view; + let texture_image_view = &window_dressing.texture_image_view; + let uniform_buffers = &window_dressing.uniform_buffers; + let descriptor_pool = &window_dressing.descriptor_pool; + let sampler = &window_dressing.sampler; + + let render_pass = init_render_pass(device, sample_count, + &swapchain.format, &depth_format)?; + + let (pipeline_layout, pipeline) + = init_pipeline(device, descriptor_set_layout, &swapchain.extent, + sample_count, &render_pass)?; + + let framebuffers = init_framebuffers( + device, &swapchain.extent, &swapchain.image_views, + &color_image_view, &depth_image_view, &render_pass)?; + + let (vertices, indices) = load_model()?; + let index_count = indices.len(); + + let command_buffers = init_command_buffers(device, &framebuffers, + primary_command_pool)?; + + let (vertex_buffer, vertex_buffer_memory) + = init_vertex_buffer(vertices, instance, physical_device, device, + graphics_queue, &transient_command_pool)?; + let (index_buffer, index_buffer_memory) + = init_index_buffer(indices, instance, physical_device, device, + graphics_queue, &transient_command_pool)?; + + let descriptor_sets + = init_descriptor_sets(device, descriptor_set_layout, + &uniform_buffers, &descriptor_pool, + swapchain.images.len(), + &texture_image_view, &sampler)?; + + Ok(RenderState { + render_pass, + pipeline, + pipeline_layout, + vertex_buffer, + vertex_buffer_memory, + index_buffer, + index_buffer_memory, + index_count, + framebuffers, + command_buffers, + descriptor_sets, + }) + } + + // This relies on its caller to have already waited for the device to be + // idle. + pub fn reinit(&mut self, permanent: &PermanentGraphicsState, + for_reinit: &GraphicsStateForReinit, + window_dressing: &WindowDressing) + -> Result<()> + { + let device = &permanent.device; + let sample_count = for_reinit.sample_count; + let descriptor_set_layout = &for_reinit.descriptor_set_layout; + let primary_command_pool = &window_dressing.primary_command_pool; + let swapchain = &window_dressing.swapchain; + let depth_format = &window_dressing.depth_format; + let color_image_view = &window_dressing.color_image_view; + let depth_image_view = &window_dressing.depth_image_view; + let texture_image_view = &window_dressing.texture_image_view; + let uniform_buffers = &window_dressing.uniform_buffers; + let descriptor_pool = &window_dressing.descriptor_pool; + let sampler = &window_dressing.sampler; + + self.destroy_replaceable(device, primary_command_pool); + + let render_pass = init_render_pass(device, sample_count, + &swapchain.format, &depth_format)?; + + let (pipeline_layout, pipeline) + = init_pipeline(device, descriptor_set_layout, &swapchain.extent, + sample_count, &render_pass)?; + + let framebuffers = init_framebuffers( + device, &swapchain.extent, &swapchain.image_views, + &color_image_view, &depth_image_view, &render_pass)?; + + // Notice that we reused the command pool. + let command_buffers = init_command_buffers(device, &framebuffers, + primary_command_pool)?; + + let descriptor_sets + = init_descriptor_sets(device, descriptor_set_layout, + &uniform_buffers, &descriptor_pool, + swapchain.images.len(), + texture_image_view, sampler)?; + + self.render_pass = render_pass; + self.pipeline = pipeline; + self.pipeline_layout = pipeline_layout; + self.framebuffers = framebuffers; + self.command_buffers = command_buffers; + self.descriptor_sets = descriptor_sets; + + Ok(()) + } + + // This relies on its caller to have already waited for the device to be + // idle. + #[allow(unsafe_code)] + pub fn destroy(mut self, device: &Device, + window_dressing: &WindowDressing) + { + self.destroy_replaceable(device, &window_dressing.primary_command_pool); + + unsafe { device.destroy_buffer(self.vertex_buffer, None) }; + unsafe { device.free_memory(self.vertex_buffer_memory, None) }; + + unsafe { device.destroy_buffer(self.index_buffer, None) }; + unsafe { device.free_memory(self.index_buffer_memory, None) }; + } + + #[allow(unsafe_code)] + fn destroy_replaceable(&mut self, device: &Device, + primary_command_pool: &vk::CommandPool) + { + for framebuffer in &self.framebuffers { + unsafe { device.destroy_framebuffer(*framebuffer, None) }; + } + + // Notice that we free the buffers in the pool, but do not destroy the + // pool itself. Notice also that we only do this for the primary command + // pool, because that's the only one where we've kept track of the + // buffers. We promise ourselves to free buffers in the transient pool + // immediately after using them. + unsafe { + device.free_command_buffers(*primary_command_pool, + &self.command_buffers) + }; + + unsafe { device.destroy_pipeline(self.pipeline, None) }; + unsafe { device.destroy_pipeline_layout(self.pipeline_layout, None) }; + unsafe { device.destroy_render_pass(self.render_pass, 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 = pick_surface_format(&formats)?; + + let presentation_mode + = pick_presentation_mode(&presentation_modes)?; + let extent = 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 view = init_image_view(device, image, 1, format.format, + vk::ImageAspectFlags::COLOR)?; + image_views.push(view); + } + + Ok(Swapchain { + swapchain, images, image_views, + format: format.format, + extent + }) +} + + +#[allow(unsafe_code)] +fn init_color(instance: &Instance, physical_device: &vk::PhysicalDevice, + device: &Device, extent: &vk::Extent2D, + sample_count: vk::SampleCountFlags, format: vk::Format) + -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView)> +{ + let (image, image_memory) + = allocate_image(instance, physical_device, device, + extent.width, extent.height, 1, sample_count, + format, + vk::ImageTiling::OPTIMAL, + vk::ImageUsageFlags::COLOR_ATTACHMENT + | vk::ImageUsageFlags::TRANSIENT_ATTACHMENT, + vk::MemoryPropertyFlags::DEVICE_LOCAL)?; + + let image_view = init_image_view(device, &image, 1, format, + vk::ImageAspectFlags::COLOR)?; + + Ok((image, image_memory, image_view)) +} + + +#[allow(unsafe_code)] +fn init_depth(instance: &Instance, physical_device: &vk::PhysicalDevice, + device: &Device, extent: &vk::Extent2D, + sample_count: vk::SampleCountFlags) + -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, vk::Format)> +{ + let format = pick_depth_format(instance, physical_device)?; + + let (image, image_memory) + = allocate_image(instance, physical_device, device, + extent.width, extent.height, 1, sample_count, + format, + vk::ImageTiling::OPTIMAL, + vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT, + vk::MemoryPropertyFlags::DEVICE_LOCAL)?; + + let image_view = init_image_view(device, &image, 1, format, + vk::ImageAspectFlags::DEPTH)?; + + Ok((image, image_memory, image_view, format)) +} + + +#[allow(unsafe_code)] +fn init_render_pass(device: &Device, sample_count: vk::SampleCountFlags, + color_format: &vk::Format, depth_format: &vk::Format) + -> Result<vk::RenderPass> +{ + let color_attachment = vk::AttachmentDescription::builder() + .format(*color_format) + .samples(sample_count) + .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::COLOR_ATTACHMENT_OPTIMAL); + + let color_attachment_reference = vk::AttachmentReference::builder() + .attachment(0) + .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); + + let depth_attachment = vk::AttachmentDescription::builder() + .format(*depth_format) + .samples(sample_count) + .load_op(vk::AttachmentLoadOp::CLEAR) + .store_op(vk::AttachmentStoreOp::DONT_CARE) + .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE) + .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE) + .initial_layout(vk::ImageLayout::UNDEFINED) + .final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + let depth_attachment_reference = vk::AttachmentReference::builder() + .attachment(1) + .layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + let color_resolve_attachment = vk::AttachmentDescription::builder() + .format(*color_format) + .samples(vk::SampleCountFlags::_1) + .load_op(vk::AttachmentLoadOp::DONT_CARE) + .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_resolve_attachment_reference = vk::AttachmentReference::builder() + .attachment(2) + .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); + + let color_attachments = [color_attachment_reference]; + let resolve_attachments = [color_resolve_attachment_reference]; + let subpass = vk::SubpassDescription::builder() + .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) + .color_attachments(&color_attachments) + .depth_stencil_attachment(&depth_attachment_reference) + .resolve_attachments(&resolve_attachments); + + let dependency = vk::SubpassDependency::builder() + .src_subpass(vk::SUBPASS_EXTERNAL) + .src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT + | vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS) + .src_access_mask(vk::AccessFlags::empty()) + .dst_subpass(0) + .dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT + | vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS) + .dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE + | vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE); + + let render_attachments = [color_attachment, + depth_attachment, + color_resolve_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, + descriptor_set_layout: &vk::DescriptorSetLayout, + extent: &vk::Extent2D, sample_count: vk::SampleCountFlags, + 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 binding_descriptions = [Vertex::<f32>::binding_description()]; + let attribute_descriptions = Vertex::<f32>::attribute_descriptions(); + let vertex_input_state_info + = vk::PipelineVertexInputStateCreateInfo::builder() + .vertex_binding_descriptions(&binding_descriptions) + .vertex_attribute_descriptions(&attribute_descriptions); + + 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(sample_count); + + let depth_state_info = vk::PipelineDepthStencilStateCreateInfo::builder() + .depth_test_enable(true) + .depth_write_enable(true) + .depth_compare_op(vk::CompareOp::LESS) + .depth_bounds_test_enable(false) + .min_depth_bounds(0.0) + .max_depth_bounds(1.0) + .stencil_test_enable(false); + + 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 vertex_push_constant_range = vk::PushConstantRange::builder() + .stage_flags(vk::ShaderStageFlags::VERTEX) + .offset(0) + .size(size_of::<VertexPushBlock<f32>>() as u32); + + let layouts = [*descriptor_set_layout]; + let push_constant_ranges = [vertex_push_constant_range]; + let pipeline_layout_info = vk::PipelineLayoutCreateInfo::builder() + .set_layouts(&layouts) + .push_constant_ranges(&push_constant_ranges); + + 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) + .depth_stencil_state(&depth_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>, + color_image_view: &vk::ImageView, + depth_image_view: &vk::ImageView, + render_pass: &vk::RenderPass) + -> Result<Vec<vk::Framebuffer>> +{ + let mut framebuffers = Vec::new(); + + for color_resolve_image_view in swapchain_image_views { + let attachments = [*color_image_view, + *depth_image_view, + *color_resolve_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) +} + + +fn init_vertex_buffer(vertices: Vec<Vertex<f32>>, instance: &Instance, + physical_device: &vk::PhysicalDevice, device: &Device, + queue: &vk::Queue, command_pool: &vk::CommandPool) + -> Result<(vk::Buffer, vk::DeviceMemory)> +{ + init_buffer(instance, physical_device, device, queue, command_pool, + vk::BufferUsageFlags::VERTEX_BUFFER, &vertices) +} + + +fn init_index_buffer(indices: Vec<u32>, instance: &Instance, + physical_device: &vk::PhysicalDevice, device: &Device, + queue: &vk::Queue, command_pool: &vk::CommandPool) + -> Result<(vk::Buffer, vk::DeviceMemory)> +{ + init_buffer(instance, physical_device, device, queue, command_pool, + vk::BufferUsageFlags::INDEX_BUFFER, &indices) +} + + +#[allow(unsafe_code)] +fn init_texture(instance: &Instance, + physical_device: &vk::PhysicalDevice, device: &Device, + queue: &vk::Queue, command_pool: &vk::CommandPool) + -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, u32)> +{ + let png = include_bytes!("../../textures/forest_leaves_04_diff.png"); + + let decoder = Decoder::new(Cursor::new(png)); + let mut reader = decoder.read_info()?; + + let (width, height) = reader.info().size(); + + let format_properties = unsafe { + instance.get_physical_device_format_properties(*physical_device, + vk::Format::R8G8B8A8_SRGB) + }; + let has_linear_filter = format_properties + .optimal_tiling_features + .contains(vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR); + let mip_count = if has_linear_filter { + // This will generate mips all the way down to 1x1. It is not clear + // whether there's a benefit to that. + (width.max(height)).ilog2() + 1 + } else { + 1 + }; + + let mut pixels = vec![0; reader.info().raw_bytes()]; + reader.next_frame(&mut pixels)?; + + let (staging_buffer, staging_memory, _byte_size) + = stage_in_buffer(instance, physical_device, device, &pixels)?; + + let (image, image_memory) + = allocate_image(instance, physical_device, device, + width, height, mip_count, vk::SampleCountFlags::_1, + vk::Format::R8G8B8A8_SRGB, + vk::ImageTiling::OPTIMAL, + vk::ImageUsageFlags::SAMPLED + | vk::ImageUsageFlags::TRANSFER_SRC + | vk::ImageUsageFlags::TRANSFER_DST, + vk::MemoryPropertyFlags::DEVICE_LOCAL)?; + + change_image_layout(device, queue, command_pool, &image, mip_count, + vk::Format::R8G8B8A8_SRGB, + vk::ImageLayout::UNDEFINED, + vk::ImageLayout::TRANSFER_DST_OPTIMAL)?; + + copy_buffer_to_image(device, queue, command_pool, &staging_buffer, &image, + width, height)?; + + // This will also change the layout to SHADER_READ_ONLY_OPTIMAL. + + fill_mip_levels(device, queue, command_pool, &image, + width, height, mip_count)?; + + let view = init_image_view(device, &image, mip_count, + vk::Format::R8G8B8A8_SRGB, + vk::ImageAspectFlags::COLOR)?; + + unsafe { device.destroy_buffer(staging_buffer, None) }; + unsafe { device.free_memory(staging_memory, None) }; + + Ok((image, image_memory, view, mip_count)) +} + + +fn init_uniform_buffers(instance: &Instance, + physical_device: &vk::PhysicalDevice, device: &Device, + count: usize) + -> Result<(Vec<vk::Buffer>, Vec<vk::DeviceMemory>)> +{ + let mut buffers = Vec::new(); + let mut all_memory = Vec::new(); + + for _ in 0 .. count { + let (buffer, memory) = allocate_buffer( + instance, physical_device, device, + size_of::<UniformBlock<f32>>() as vk::DeviceSize, + vk::BufferUsageFlags::UNIFORM_BUFFER, + vk::MemoryPropertyFlags::HOST_COHERENT + | vk::MemoryPropertyFlags::HOST_VISIBLE)?; + buffers.push(buffer); + all_memory.push(memory); + } + + Ok((buffers, all_memory)) +} + + +#[allow(unsafe_code)] +fn init_buffer<T>(instance: &Instance, + physical_device: &vk::PhysicalDevice, device: &Device, + queue: &vk::Queue, command_pool: &vk::CommandPool, + usage: vk::BufferUsageFlags, contents: &[T]) + -> Result<(vk::Buffer, vk::DeviceMemory)> +{ + let (staging_buffer, staging_memory, size) + = stage_in_buffer(instance, physical_device, device, contents)?; + + let final_usage = vk::BufferUsageFlags::TRANSFER_DST | usage; + let final_memory_flags = vk::MemoryPropertyFlags::DEVICE_LOCAL; + let (final_buffer, device_memory) + = allocate_buffer(instance, physical_device, device, + size as vk::DeviceSize, final_usage, + final_memory_flags)?; + + copy_buffer(device, queue, command_pool, &staging_buffer, &final_buffer, + size as vk::DeviceSize)?; + + unsafe { device.destroy_buffer(staging_buffer, None) }; + unsafe { device.free_memory(staging_memory, None) }; + + Ok((final_buffer, device_memory)) +} + + +#[allow(unsafe_code)] +fn init_sampler(device: &Device, enable_anisotropy: &EnableAnisotropy, + mip_count: u32) + -> Result<vk::Sampler> +{ + let mut sampler_info = vk::SamplerCreateInfo::builder() + .mag_filter(vk::Filter::LINEAR) + .min_filter(vk::Filter::LINEAR) + .address_mode_u(vk::SamplerAddressMode::REPEAT) + .address_mode_v(vk::SamplerAddressMode::REPEAT) + .address_mode_w(vk::SamplerAddressMode::REPEAT) + .border_color(vk::BorderColor::INT_OPAQUE_BLACK) + .unnormalized_coordinates(false) + .compare_enable(false) + .compare_op(vk::CompareOp::ALWAYS) + .mipmap_mode(vk::SamplerMipmapMode::LINEAR) + .mip_lod_bias(0.0) + .min_lod(0.0) + .max_lod(mip_count as f32); + sampler_info = if enable_anisotropy.0 { + sampler_info.anisotropy_enable(true) + .max_anisotropy(16.0) + } else { + sampler_info.anisotropy_enable(false) + .max_anisotropy(1.0) + }; + + let sampler = unsafe { device.create_sampler(&sampler_info, None) }?; + + Ok(sampler) +} + + +#[allow(unsafe_code)] +fn init_descriptor_pool(device: &Device, count: usize) + -> Result<vk::DescriptorPool> +{ + let uniform_block_size = vk::DescriptorPoolSize::builder() + .type_(vk::DescriptorType::UNIFORM_BUFFER) + .descriptor_count(count as u32); + + let sampler_size = vk::DescriptorPoolSize::builder() + .type_(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .descriptor_count(count as u32); + + let sizes = [uniform_block_size, sampler_size]; + let pool_info = vk::DescriptorPoolCreateInfo::builder() + .pool_sizes(&sizes) + .max_sets(count as u32); + let pool = unsafe { device.create_descriptor_pool(&pool_info, None) }?; + + Ok(pool) +} + + +#[allow(unsafe_code)] +fn init_descriptor_sets(device: &Device, layout: &vk::DescriptorSetLayout, + buffers: &Vec<vk::Buffer>, pool: &vk::DescriptorPool, + count: usize, texture_image_view: &vk::ImageView, + sampler: &vk::Sampler) + -> Result<Vec<vk::DescriptorSet>> +{ + let layouts = vec![*layout; count]; + let set_info = vk::DescriptorSetAllocateInfo::builder() + .descriptor_pool(*pool) + .set_layouts(&layouts); + let sets = unsafe { device.allocate_descriptor_sets(&set_info) }?; + + for index in 0 .. count { + let buffer_info = vk::DescriptorBufferInfo::builder() + .buffer(buffers[index]) + .offset(0) + .range(size_of::<UniformBlock<f32>>() as vk::DeviceSize); + + let buffer_info_list = [buffer_info]; + let uniform_block_write_info = vk::WriteDescriptorSet::builder() + .dst_set(sets[index]) + .dst_binding(0) + .dst_array_element(0) + .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) + .buffer_info(&buffer_info_list); + + let image_info = vk::DescriptorImageInfo::builder() + .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .image_view(*texture_image_view) + .sampler(*sampler); + let image_info_list = [image_info]; + let sampler_write_info = vk::WriteDescriptorSet::builder() + .dst_set(sets[index]) + .dst_binding(1) + .dst_array_element(0) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(&image_info_list); + + let write_info_list = [uniform_block_write_info, sampler_write_info]; + let copy_info_list: [vk::CopyDescriptorSet; 0] = []; + + unsafe { + device.update_descriptor_sets(&write_info_list, ©_info_list) + }; + } + + Ok(sets) +} + + +#[allow(unsafe_code)] +fn init_command_pools(device: &Device, indices: &QueueFamilyIndices) + -> Result<(vk::CommandPool, vk::CommandPool)> +{ + let command_pool_info = vk::CommandPoolCreateInfo::builder() + .flags(vk::CommandPoolCreateFlags::TRANSIENT + | vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER) + .queue_family_index(indices.graphics); + + let primary = unsafe { + device.create_command_pool(&command_pool_info, None) + }?; + + command_pool_info.flags(vk::CommandPoolCreateFlags::TRANSIENT); + let transient = unsafe { + device.create_command_pool(&command_pool_info, None) + }?; + + Ok((primary, transient)) +} + + +#[allow(unsafe_code)] +fn init_command_buffers(device: &Device, + framebuffers: &Vec<vk::Framebuffer>, + command_pool: &vk::CommandPool) + -> Result<Vec<vk::CommandBuffer>> +{ + 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) + }?; + + Ok(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, + }) +} + + +#[allow(unsafe_code)] +fn init_image_view(device: &Device, image: &vk::Image, mip_count: u32, + format: vk::Format, aspects: vk::ImageAspectFlags) + -> Result<vk::ImageView> +{ + // Component mapping is only for color components (not, for example, depth + // or stencil components), so we always just want it like this. + 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(aspects) + .base_mip_level(0) + .level_count(mip_count) + .base_array_layer(0) + .layer_count(1); + + let view_info = vk::ImageViewCreateInfo::builder() + .image(*image) + .view_type(vk::ImageViewType::_2D) + .format(format) + .components(components) + .subresource_range(subresource_range); + + let view = unsafe { + device.create_image_view(&view_info, None) + }?; + + Ok(view) +} + + +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()); +} + + +#[allow(unsafe_code)] +fn pick_depth_format(instance: &Instance, + physical_device: &vk::PhysicalDevice) + -> Result<vk::Format> +{ + let required_features = vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT; + + for format in [vk::Format::D32_SFLOAT, + vk::Format::D32_SFLOAT_S8_UINT, + vk::Format::D24_UNORM_S8_UINT] + { + let properties = unsafe { + instance.get_physical_device_format_properties( + *physical_device, format) + }; + + if properties.optimal_tiling_features.contains(required_features) { + return Ok(format); + } + } + + Err(Error { + message: "There is no supported depth-buffer sample format.".to_string() + }) +} + + +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()) + } +} + + +#[allow(unsafe_code)] +fn pick_memory_type(instance: &Instance, + physical_device: &vk::PhysicalDevice, + properties: &vk::MemoryPropertyFlags, + requirements: &vk::MemoryRequirements) + -> Result<u32> +{ + let memory_map = unsafe { + instance.get_physical_device_memory_properties(*physical_device) + }; + + // So. The memory_type_bits field is a map of which indices are suitable, + // based on the buffer our caller passed to + // get_buffer_memory_requirements(). Yes, that means there's a hard cap on + // how many memory types there can be, based on the size of the bitfield. + for index in 0 .. memory_map.memory_type_count { + if requirements.memory_type_bits & (1 << index) == 0 { + continue; + } + + let memory_type = memory_map.memory_types[index as usize]; + + if memory_type.property_flags.contains(*properties) { + return Ok(index); + } + } + + Err(Error { + message: "The system has no suitable memory for a buffer.".to_string() + }) +} + + +#[allow(unsafe_code)] +fn stage_in_buffer<T>(instance: &Instance, + physical_device: &vk::PhysicalDevice, device: &Device, + contents: &[T]) + -> Result<(vk::Buffer, vk::DeviceMemory, usize)> +{ + let size = size_of::<T>() * contents.len(); + + let staging_usage = vk::BufferUsageFlags::TRANSFER_SRC; + let staging_memory_flags = vk::MemoryPropertyFlags::HOST_COHERENT + | vk::MemoryPropertyFlags::HOST_VISIBLE; + let (staging_buffer, staging_memory) + = allocate_buffer(instance, physical_device, device, + size as vk::DeviceSize, staging_usage, + staging_memory_flags)?; + + let host_memory = unsafe { + device.map_memory(staging_memory, 0, size as vk::DeviceSize, + vk::MemoryMapFlags::empty()) + }?; + + unsafe { + copy_nonoverlapping(contents.as_ptr(), host_memory.cast(), contents.len()) + }; + + unsafe { device.unmap_memory(staging_memory) }; + + Ok((staging_buffer, staging_memory, size)) +} + + +#[allow(unsafe_code)] +fn allocate_buffer(instance: &Instance, + physical_device: &vk::PhysicalDevice, device: &Device, + size: vk::DeviceSize, usage: vk::BufferUsageFlags, + memory_flags: vk::MemoryPropertyFlags) + -> Result<(vk::Buffer, vk::DeviceMemory)> +{ + let buffer_info = vk::BufferCreateInfo::builder() + .size(size) + .usage(usage) + .sharing_mode(vk::SharingMode::EXCLUSIVE); + + let buffer = unsafe { device.create_buffer(&buffer_info, None) }?; + + // The requirements are mostly what you'd think: size and alignment. The + // bits field is something special; see pick_memory_type() for the + // explanation. Despite the simplicity of this data, Vulkan wants to be the + // one to tell us about it, and we let it. + let requirements = unsafe { device.get_buffer_memory_requirements(buffer) }; + + let type_index = pick_memory_type(instance, physical_device, + &memory_flags, &requirements)?; + + let memory_info = vk::MemoryAllocateInfo::builder() + .allocation_size(requirements.size) + .memory_type_index(type_index); + + let device_memory = unsafe { device.allocate_memory(&memory_info, None) }?; + + unsafe { device.bind_buffer_memory(buffer, device_memory, 0) }?; + + Ok((buffer, device_memory)) +} + + +#[allow(unsafe_code)] +fn copy_buffer(device: &Device, queue: &vk::Queue, + command_pool: &vk::CommandPool, source: &vk::Buffer, + destination: &vk::Buffer, size: vk::DeviceSize) + -> Result<()> +{ + let command_buffer = begin_transient_commands(device, command_pool)?; + + let copy_info = vk::BufferCopy::builder().size(size); + unsafe { + device.cmd_copy_buffer(command_buffer, *source, *destination, + &[copy_info]) + }; + + end_transient_commands(command_buffer, device, queue, command_pool)?; + + Ok(()) +} + + +#[allow(unsafe_code)] +fn allocate_image(instance: &Instance, physical_device: &vk::PhysicalDevice, + device: &Device, width: u32, height: u32, mip_count: u32, + sample_count: vk::SampleCountFlags, format: vk::Format, + tiling: vk::ImageTiling, usage: vk::ImageUsageFlags, + memory_flags: vk::MemoryPropertyFlags) + -> Result<(vk::Image, vk::DeviceMemory)> +{ + let image_info = vk::ImageCreateInfo::builder() + .image_type(vk::ImageType::_2D) + .extent(vk::Extent3D { width, height, depth: 1 }) + .mip_levels(mip_count) + .samples(sample_count) + .array_layers(1) + .format(format) + .tiling(tiling) + .initial_layout(vk::ImageLayout::UNDEFINED) + .usage(usage) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .flags(vk::ImageCreateFlags::empty()); + let image = unsafe { device.create_image(&image_info, None) }?; + + let requirements = unsafe { device.get_image_memory_requirements(image) }; + + let type_index = pick_memory_type(instance, physical_device, + &memory_flags, &requirements)?; + + let image_memory_info = vk::MemoryAllocateInfo::builder() + .allocation_size(requirements.size) + .memory_type_index(type_index); + let image_memory = unsafe { + device.allocate_memory(&image_memory_info, None) + }?; + + unsafe { device.bind_image_memory(image, image_memory, 0) }?; + + Ok((image, image_memory)) +} + + +#[allow(unsafe_code)] +fn copy_buffer_to_image(device: &Device, queue: &vk::Queue, + command_pool: &vk::CommandPool, source: &vk::Buffer, + destination: &vk::Image, width: u32, height: u32) + -> Result<()> +{ + let command_buffer = begin_transient_commands(device, command_pool)?; + + let subresource_layers = vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(0) + .base_array_layer(0) + .layer_count(1); + + let copy_info = vk::BufferImageCopy::builder() + .buffer_offset(0) + .buffer_row_length(0) + .buffer_image_height(0) + .image_subresource(subresource_layers) + .image_offset(vk::Offset3D { x: 0, y: 0, z: 0 }) + .image_extent(vk::Extent3D { width, height, depth: 1 }); + + unsafe { + device.cmd_copy_buffer_to_image(command_buffer, *source, *destination, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + &[copy_info]) + }; + + end_transient_commands(command_buffer, device, queue, command_pool)?; + + Ok(()) +} + + +#[allow(unsafe_code)] +fn change_image_layout(device: &Device, queue: &vk::Queue, + command_pool: &vk::CommandPool, image: &vk::Image, + mip_count: u32, format: vk::Format, + old: vk::ImageLayout, new: vk::ImageLayout) + -> Result<()> +{ + let command_buffer = begin_transient_commands(device, command_pool)?; + + let subresource_range = vk::ImageSubresourceRange::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(mip_count) + .base_array_layer(0) + .layer_count(1); + + // Notionally this is a property that our caller is in a better position + // to know than we are, but in practice the nature of the transition + // strongly implies a particular phase of the image's lifecycle, so we just + // compute it here. + let (source_access, source_stage, destination_access, destination_stage) + = match (old, new) + { + (vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL) + => (vk::AccessFlags::empty(), + vk::PipelineStageFlags::TOP_OF_PIPE, + vk::AccessFlags::TRANSFER_WRITE, + vk::PipelineStageFlags::TRANSFER), + (vk::ImageLayout::TRANSFER_DST_OPTIMAL, + vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + => (vk::AccessFlags::TRANSFER_WRITE, + vk::PipelineStageFlags::TRANSFER, + vk::AccessFlags::SHADER_READ, + vk::PipelineStageFlags::FRAGMENT_SHADER), + _ => return Err(Error { + message: + format!("Don't know how to change from image layout {:?} to {:?}", + old, new) + }) + }; + + let barrier_info = vk::ImageMemoryBarrier::builder() + .image(*image) + .subresource_range(subresource_range) + .old_layout(old) + .new_layout(new) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .src_access_mask(source_access) + .dst_access_mask(destination_access); + + unsafe { + device.cmd_pipeline_barrier(command_buffer, + source_stage, destination_stage, + vk::DependencyFlags::empty(), + &[] as &[vk::MemoryBarrier], + &[] as &[vk::BufferMemoryBarrier], + &[barrier_info]) + }; + + end_transient_commands(command_buffer, device, queue, command_pool)?; + + Ok(()) +} + + +// An Image can store multiple mip levels within it, as one of several kinds +// of subresource it has. We deal with this by +#[allow(unsafe_code)] +fn fill_mip_levels(device: &Device, queue: &vk::Queue, + command_pool: &vk::CommandPool, image: &vk::Image, + original_width: u32, original_height: u32, + mip_count: u32) + -> Result<()> +{ + let command_buffer = begin_transient_commands(device, command_pool)?; + + // We'll be mutating these two builders as we loop through the mip levels, + // because we need to construct a lot of similar things. Remember, the + // builder methods don't mutate in-place, they return a new builder; to + // avoid confusion we always assign that result back to the same variable. + let mut barrier_subresource_range = vk::ImageSubresourceRange::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .level_count(1) + .base_array_layer(0) + .layer_count(1); + + let mut blit_barrier_info = vk::ImageMemoryBarrier::builder() + .image(*image) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED); + + // Now we loop through the mip levels from largest (low numbers) to + // smallest (high numbers). Conceptually, the only thing we're doing is a + // blit that copies each mip level from the one immediately before. Recall + // though that we don't just want to fill in the pixels, we also care about + // pixel format and memory sharing. There are additional operations to deal + // with that. These are best done together, as detailed below. + // + // This loop has a lot of code in it, so we make the "paragraphs" a little + // more dense than usual to make sure the logical grouping is clear. + let mut source_width = original_width; + let mut source_height = original_height; + for destination_mip_level in 1 .. mip_count { + let source_mip_level = destination_mip_level - 1; + let destination_width = (source_width / 2).max(1); + let destination_height = (source_height / 2).max(1); + + // So. The name pipeline_barrier is a little misleading; it does indeed + // mean "barrier" in the concurrency sense, but it isn't just initiating + // a wait, it's also performing any needed mutation. We do one of them + // here, acting on this iteration's source level, to set it up for + // reading. + barrier_subresource_range = barrier_subresource_range + .base_mip_level(source_mip_level as u32); + blit_barrier_info = blit_barrier_info + .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) + .new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) + .src_access_mask(vk::AccessFlags::TRANSFER_WRITE) + .dst_access_mask(vk::AccessFlags::TRANSFER_READ) + .subresource_range(barrier_subresource_range); + unsafe { + device.cmd_pipeline_barrier(command_buffer, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::TRANSFER, + vk::DependencyFlags::empty(), + &[] as &[vk::MemoryBarrier], + &[] as &[vk::BufferMemoryBarrier], + &[blit_barrier_info]) + }; + + // Now we do the actual blit. Nice and easy, though specifying the + // coordinates is a bit verbose. + let blit_source_layer_info = vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(source_mip_level as u32) + .base_array_layer(0) + .layer_count(1); + let blit_destination_layer_info = vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(destination_mip_level as u32) + .base_array_layer(0) + .layer_count(1); + let blit_info = vk::ImageBlit::builder() + .src_offsets([vk::Offset3D { x: 0, y: 0, z: 0 }, + vk::Offset3D { + x: source_width as i32, + y: source_height as i32, + z: 1 + }]) + .src_subresource(blit_source_layer_info) + .dst_offsets([vk::Offset3D { x: 0, y: 0, z: 0 }, + vk::Offset3D { + x: destination_width as i32, + y: destination_height as i32, + z: 1 + }]) + .dst_subresource(blit_destination_layer_info); + unsafe { + device.cmd_blit_image(command_buffer, + *image, vk::ImageLayout::TRANSFER_SRC_OPTIMAL, + *image, vk::ImageLayout::TRANSFER_DST_OPTIMAL, + &[blit_info], + vk::Filter::LINEAR) + }; + + // Now we do another pipeline_barrier. We're still acting on this + // iteration's source level, not on the destination. We'll never need to + // use it again except from the shader, so we set it appropriately for + // that. + blit_barrier_info = blit_barrier_info + .old_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) + .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .src_access_mask(vk::AccessFlags::TRANSFER_READ) + .dst_access_mask(vk::AccessFlags::SHADER_READ); + unsafe { + device.cmd_pipeline_barrier(command_buffer, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::FRAGMENT_SHADER, + vk::DependencyFlags::empty(), + &[] as &[vk::MemoryBarrier], + &[] as &[vk::BufferMemoryBarrier], + &[blit_barrier_info]) + }; + + source_width = destination_width; + source_height = destination_height; + } + + let final_mip_level = mip_count - 1; + + // We need to do one final pipeline_barrier, because the loop didn't do it + // to the smallest (last) mip level. We change it to have the same settings + // the loop left the rest of them in. The barrier source properties for this + // barrier are different from the others because this level was never useds + // as a blit source, only as a blit destination. The barrier destination + // properties are the same as the rest, so after this all the subresourcess + // will be in their fully-ready state. + barrier_subresource_range = barrier_subresource_range + .base_mip_level(final_mip_level as u32); + blit_barrier_info = blit_barrier_info + .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) + .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .src_access_mask(vk::AccessFlags::TRANSFER_WRITE) + .dst_access_mask(vk::AccessFlags::SHADER_READ) + .subresource_range(barrier_subresource_range); + unsafe { + device.cmd_pipeline_barrier(command_buffer, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::FRAGMENT_SHADER, + vk::DependencyFlags::empty(), + &[] as &[vk::MemoryBarrier], + &[] as &[vk::BufferMemoryBarrier], + &[blit_barrier_info]) + }; + + end_transient_commands(command_buffer, device, queue, command_pool)?; + + Ok(()) +} + + +#[allow(unsafe_code)] +fn begin_transient_commands(device: &Device, command_pool: &vk::CommandPool) + -> Result<vk::CommandBuffer> +{ + let command_buffer_allocation_info + = vk::CommandBufferAllocateInfo::builder() + .command_pool(*command_pool) + .level(vk::CommandBufferLevel::PRIMARY) + .command_buffer_count(1); + let command_buffer = unsafe { + device.allocate_command_buffers(&command_buffer_allocation_info) + }?[0]; + + let command_buffer_begin_info = vk::CommandBufferBeginInfo::builder() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); + + unsafe { + device.begin_command_buffer(command_buffer, &command_buffer_begin_info) + }?; + + Ok(command_buffer) +} + + +#[allow(unsafe_code)] +fn end_transient_commands(command_buffer: vk::CommandBuffer, + device: &Device, queue: &vk::Queue, + command_pool: &vk::CommandPool) + -> Result<()> +{ + unsafe { device.end_command_buffer(command_buffer) }?; + + let command_buffers = [command_buffer]; + let submit_info = vk::SubmitInfo::builder() + .command_buffers(&command_buffers); + unsafe { device.queue_submit(*queue, &[submit_info], vk::Fence::null()) }?; + + unsafe { device.queue_wait_idle(*queue) }?; + + unsafe { device.free_command_buffers(*command_pool, &command_buffers) }; + + Ok(()) +} + |