diff options
| author | Irene Knapp <ireneista@irenes.space> | 2026-07-09 15:42:47 -0700 |
|---|---|---|
| committer | Irene Knapp <ireneista@irenes.space> | 2026-07-09 15:42:47 -0700 |
| commit | 05982c6344ec308bcd60fae9457979a20ff18bc8 (patch) | |
| tree | 55a21c3717f1ef540ddf36e968e49ce7e9d52a57 /src/graphics_permanent.rs | |
| parent | daac5191461ed84d41920ea9c80addb8dea977c2 (diff) | |
make most of the associated functions into file-level functions
now that the two halves of the graphics state are in their own files, this is easier to read the public functions remain as part of the impl Force-Push: yes Change-Id: Ic7d73be04f31b484348bbc5d62cc93f0c877717c
Diffstat (limited to 'src/graphics_permanent.rs')
| -rw-r--r-- | src/graphics_permanent.rs | 958 |
1 files changed, 482 insertions, 476 deletions
diff --git a/src/graphics_permanent.rs b/src/graphics_permanent.rs index 4df6069..d41bbfa 100644 --- a/src/graphics_permanent.rs +++ b/src/graphics_permanent.rs @@ -97,7 +97,7 @@ impl PermanentGraphicsState { -> Result<(Self, vk::PhysicalDevice, QueueFamilyIndices, EnableSwapchain)> { - let window = Self::init_window(event_loop)?; + 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. @@ -108,7 +108,7 @@ impl PermanentGraphicsState { // phases; we don't keep them around after that. let (entry, instance, debug_messager, enable_portability, enable_validation) - = Self::init_vulkan(&window)?; + = 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 @@ -125,8 +125,8 @@ impl PermanentGraphicsState { let (physical_device, device, indices, graphics_queue, presentation_queue, enable_swapchain) - = Self::init_vulkan_device(&instance, &surface, - enable_validation, enable_portability)?; + = init_vulkan_device(&instance, &surface, + enable_validation, enable_portability)?; Ok((PermanentGraphicsState { window, entry, instance, debug_messager, surface, device, @@ -154,541 +154,547 @@ impl PermanentGraphicsState { unsafe { self.instance.destroy_instance(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)); + // 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) + }?; - Ok(event_loop.create_window(window_attributes)?) + 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)] - fn init_vulkan(window: &Window) - -> Result<(Entry, Instance, Option<vk::DebugUtilsMessengerEXT>, - EnablePortability, EnableValidation)> + pub fn load_spirv_shader_module(device: &Device, binary: &[u8]) + -> Result<vk::ShaderModule> { - 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 bytecode = Bytecode::new(binary)?; - 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; + let module_info = vk::ShaderModuleCreateInfo::builder() + .code(bytecode.code()) + .code_size(bytecode.code_size()); - // 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()); - } + let module = unsafe { + device.create_shader_module(&module_info, None) + }?; - // 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) - }; + Ok(module) + } +} - // 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."); +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)?) +} - 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()); +#[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 { - eprintln!("Vulkan debug extension not available; \ - this may mean other messages don't show up."); + EnablePortability(false) } + } else { + EnablePortability(false) + }; - 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) - }?; + // 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()); - // 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) - }?) + EnableValidation(true) } else { - None - }; + eprintln!("Vulkan validation requested at build time, \ + but no validation layer available."); - Ok((entry, instance, debug_messager, - enable_portability, enable_validation)) + 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."); } - #[allow(unsafe_code)] - fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR, - enable_validation: EnableValidation, - enable_portability: EnablePortability) - -> Result<(vk::PhysicalDevice, Device, QueueFamilyIndices, vk::Queue, - vk::Queue, EnableSwapchain)> - { - let (physical_device, indices) - = Self::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 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 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::Queue, + vk::Queue, EnableSwapchain)> +{ + let (physical_device, indices) = 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 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 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) + 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)? { - // 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) - } + extensions.push(swapchain_extension_name.as_ptr()); + + EnableSwapchain(true) } else { EnableSwapchain(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])); } + } else { + EnableSwapchain(false) + }; - let device_info = vk::DeviceCreateInfo::builder() - .queue_create_infos(&queues) - .enabled_layer_names(&layers) - .enabled_extension_names(&extensions) - .enabled_features(&features); + // 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 = unsafe { - instance.create_device(physical_device, &device_info, None) - }?; + 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) + }; - // 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) + }; - let presentation_queue = unsafe { - device.get_device_queue(indices.presentation, 0) - }; + Ok((physical_device, device, + indices, graphics_queue, presentation_queue, + enable_swapchain)) +} - Ok((physical_device, device, - indices, graphics_queue, presentation_queue, - enable_swapchain)) - } - // 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)> - { - let mut best_device = None; - let mut best_score = None; - let mut best_indices = None; - let mut rejected = BTreeMap::new(); - - for device in unsafe { instance.enumerate_physical_devices() }? { - match Self::score_vulkan_device(instance, surface, &device)? { - Acceptable::Accepted((new_score, new_indices)) => { - 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); - } - } else { +// 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)> +{ + let mut best_device = None; + let mut best_score = None; + let mut best_indices = 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)) => { + 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); } + } else { + best_device = Some(device); + best_score = Some(new_score); + best_indices = Some(new_indices); } - Acceptable::Rejected(reason) => { - let properties = unsafe { - instance.get_physical_device_properties(device) - }; + } + Acceptable::Rejected(reason) => { + let properties = unsafe { + instance.get_physical_device_properties(device) + }; - let name = properties.device_name.to_string_lossy().into_owned(); + let name = properties.device_name.to_string_lossy().into_owned(); - rejected.insert(properties.device_id, (name, reason)); - } + rejected.insert(properties.device_id, (name, reason)); } } + } - if let (Some(device), Some(indices)) = (best_device, best_indices) { - Ok((device, indices)) - } 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() - }) + if let (Some(device), Some(indices)) = (best_device, best_indices) { + Ok((device, indices)) + } 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)>> + +// 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)>> +{ + // 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)? { - // 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 Self::find_device_queue_family_indices( - instance, surface, physical_device)? - { - Acceptable::Rejected(rationale) => { - return Ok(Acceptable::Rejected(rationale)); - } - Acceptable::Accepted(indices) => indices - }; + 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) - }; + // 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; - } + 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; - // 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. + 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; } - Ok(Acceptable::Accepted((score, indices))) + // 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. } - #[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); - } + Ok(Acceptable::Accepted((score, indices))) +} - if presentation.is_none() && unsafe { - instance.get_physical_device_surface_support_khr( - *device, index as u32, *surface) - }? { - presentation = Some(index as u32); - } + +#[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 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())) + if presentation.is_none() && unsafe { + instance.get_physical_device_surface_support_khr( + *device, index as u32, *surface) + }? { + presentation = Some(index as u32); } } - // 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())) + if let Some(graphics) = graphics { + if let Some(presentation) = presentation { + Ok(Acceptable::Accepted(QueueFamilyIndices { + graphics, presentation + })) } else { - Ok(Acceptable::Accepted((capabilities, formats, presentation_modes))) + Ok(Acceptable::Rejected( + "Doesn't support presenting to our window.".to_string())) } - } - - #[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) + } else { + Ok(Acceptable::Rejected("Doesn't support graphics.".to_string())) } } |