summary refs log tree commit diff
path: root/src/graphics/permanent.rs
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@irenes.space>2026-08-17 01:41:16 -0700
committerIrene Knapp <ireneista@irenes.space>2026-08-17 02:07:34 -0700
commitb52d1be4302e452a45a03595f8c70411a061f920 (patch)
tree27af0086f681df583389cfadafd65d7067d26fb2 /src/graphics/permanent.rs
parentdb64249ce8108439ada78a2d23d67d2c573a1688 (diff)
move the texture binding to a push descriptor
with a descriptor update template, even

yay

this also moves us to a minimum Vulkan version of 1.4, which we understand to be widely deployed. in theory, push descriptors and update templates were available via extensions in earlier versions, but it doesn't seem worth doing the work to switch out whether we use the extensions based on the version, so we don't.

Force-Push: yes
Change-Id: I26db8771abf5626d9614630c66e019ea99babcac
Diffstat (limited to 'src/graphics/permanent.rs')
-rw-r--r--src/graphics/permanent.rs120
1 files changed, 97 insertions, 23 deletions
diff --git a/src/graphics/permanent.rs b/src/graphics/permanent.rs
index b39dbdd..6c4e02d 100644
--- a/src/graphics/permanent.rs
+++ b/src/graphics/permanent.rs
@@ -8,7 +8,7 @@ use vulkanalia::bytecode::Bytecode;
 use vulkanalia::loader::{ LibloadingLoader, LIBRARY };
 use vulkanalia::vk::{ self, HasBuilder,
                       ApplicationInfo, InstanceCreateInfo,
-                      DeviceV1_0, EntryV1_0, InstanceV1_0,
+                      DeviceV1_0, EntryV1_0, InstanceV1_0, InstanceV1_1,
                       ExtDebugUtilsExtensionInstanceCommands,
                       KhrSurfaceExtensionInstanceCommands };
 use winit::dpi::LogicalSize;
@@ -16,6 +16,7 @@ use winit::event_loop::ActiveEventLoop;
 use winit::window::{ Window, WindowAttributes };
 
 
+const VULKAN_MINIMUM_VERSION: Version = Version::new(1, 4, 0);
 const VULKAN_FIRST_PORTABILITY_VERSION: Version = Version::new(1, 3, 216);
 
 
@@ -135,7 +136,8 @@ impl Permanent {
         = init_vulkan_device(&instance, &surface,
                              enable_validation, enable_portability)?;
 
-    let descriptor_set_layout = init_descriptor_set_layout(&device)?;
+    let (primary_descriptor_set_layout, push_descriptor_set_layout)
+        = init_descriptor_set_layouts(&device)?;
 
     let (primary_command_pool, transient_command_pool)
             = init_command_pools(&device, &indices)?;
@@ -145,7 +147,8 @@ impl Permanent {
       graphics_queue, presentation_queue,
       primary_command_pool, transient_command_pool,
     }, ForReinit {
-      indices, sample_count, descriptor_set_layout,
+      indices, sample_count,
+      primary_descriptor_set_layout, push_descriptor_set_layout,
     }, enable_anisotropy, enable_swapchain))
   }
 
@@ -236,7 +239,8 @@ impl Permanent {
 pub struct ForReinit {
   pub indices: QueueFamilyIndices,
   pub sample_count: vk::SampleCountFlags,
-  pub descriptor_set_layout: vk::DescriptorSetLayout,
+  pub primary_descriptor_set_layout: vk::DescriptorSetLayout,
+  pub push_descriptor_set_layout: vk::DescriptorSetLayout,
 }
 
 
@@ -244,7 +248,13 @@ impl ForReinit {
   #[allow(unsafe_code)]
   pub fn destroy(self, device: &Device) -> () {
     unsafe {
-      device.destroy_descriptor_set_layout(self.descriptor_set_layout, None)
+      device.destroy_descriptor_set_layout(self.primary_descriptor_set_layout,
+                                           None)
+    };
+
+    unsafe {
+      device.destroy_descriptor_set_layout(self.push_descriptor_set_layout,
+                                           None)
     };
   }
 }
@@ -366,12 +376,28 @@ fn init_vulkan(window: &Window)
                this may mean other messages don't show up.");
   }
 
+  //   The api_version field here is the version of Vulkan we actually get.
+  // The official documentation is a bit misleading, since it says "maximum",
+  // but on close reading what it means is that we guarantee that we as the
+  // application will not require anything newer than that. So from our
+  // perspective it is the MINIMUM we will accept.
+  //
+  //   We define a constant for it to make it easy to find, since it's far
+  // more important operationally than any of the other versions declared
+  // here.
+  //
+  //   The validation layer enforces this API version on us, interposing
+  // itself when we attempt to load functions. Vulkanalia will panic!() on us
+  // when loading fails, with a message in the form "could not load
+  // vkWhateverWhatever", which can be quite confusing, so this comment text
+  // is here to make that easy to diagnose. If you got here for that reason,
+  // give thanks to the Waiting and its foresight.
   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));
+          .api_version(u32::from(VULKAN_MINIMUM_VERSION));
 
   //   Deceptively, this DOES get mutated later, but Vulkanalia doesn't see
   // it that way.
@@ -454,9 +480,24 @@ fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR,
   //   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)
+  //
+  //   Dealing with this struct chain is kind of risky lifetime-wise, because
+  // Vulkanalia unsafely discards the reference for our "convenience". Alas.
+  let mut available_features_14
+          = vk::PhysicalDeviceVulkan14Features::default();
+  let mut available_features = vk::PhysicalDeviceFeatures2::builder()
+          .push_next(&mut available_features_14);
+  unsafe {
+    instance.get_physical_device_features2(physical_device,
+                                           &mut available_features)
   };
+
+  //   Another struct chain with a risky lifetime. Vulkanalia's mutation
+  // features return a copy, so we have to avoid mutating features_14 once we
+  // add it to the chain. Fortunately we only need it for one thing right now,
+  // so we do that here at the top and treat it as non-mutable.
+  let mut features_14 = vk::PhysicalDeviceVulkan14Features::builder()
+          .push_descriptor(true);
   let mut features = vk::PhysicalDeviceFeatures::builder();
   let mut extensions = Vec::new();
   let mut layers = Vec::new();
@@ -506,7 +547,7 @@ fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR,
     EnableSwapchain(false)
   };
 
-  let enable_anisotropy = if available_features.sampler_anisotropy
+  let enable_anisotropy = if available_features.features.sampler_anisotropy
                              == vk::TRUE
   {
     features = features.sampler_anisotropy(true);
@@ -535,11 +576,14 @@ fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR,
                     .queue_priorities(&[1.0]));
   }
 
+  let mut features2 = vk::PhysicalDeviceFeatures2::builder()
+          .features(features);
   let device_info = vk::DeviceCreateInfo::builder()
           .queue_create_infos(&queues)
           .enabled_layer_names(&layers)
           .enabled_extension_names(&extensions)
-          .enabled_features(&features);
+          .push_next(&mut features2)
+          .push_next(&mut features_14);
 
   let device = unsafe {
     instance.create_device(physical_device, &device_info, None)
@@ -564,8 +608,8 @@ fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR,
 
 
 #[allow(unsafe_code)]
-fn init_descriptor_set_layout(device: &Device)
-    -> Result<vk::DescriptorSetLayout>
+fn init_descriptor_set_layouts(device: &Device)
+    -> Result<(vk::DescriptorSetLayout, vk::DescriptorSetLayout)>
 {
   let uniform_block_binding = vk::DescriptorSetLayoutBinding::builder()
           .binding(0)
@@ -579,21 +623,32 @@ fn init_descriptor_set_layout(device: &Device)
           .descriptor_count(1)
           .stage_flags(vk::ShaderStageFlags::FRAGMENT);
 
+  let primary_bindings = [uniform_block_binding, sampler_binding];
+  let primary_descriptor_set_layout_info
+          = vk::DescriptorSetLayoutCreateInfo::builder()
+                .bindings(&primary_bindings);
+  let primary_descriptor_set_layout = unsafe {
+    device.create_descriptor_set_layout(&primary_descriptor_set_layout_info,
+                                        None)
+  }?;
+
   let texture_binding = vk::DescriptorSetLayoutBinding::builder()
-          .binding(2)
+          .binding(0)
           .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE)
           .descriptor_count(1)
           .stage_flags(vk::ShaderStageFlags::FRAGMENT);
 
-  let bindings = [uniform_block_binding, sampler_binding, texture_binding];
-  let descriptor_set_layout_info
+  let push_bindings = [texture_binding];
+  let push_descriptor_set_layout_info
           = vk::DescriptorSetLayoutCreateInfo::builder()
-                .bindings(&bindings);
-  let descriptor_set_layout = unsafe {
-    device.create_descriptor_set_layout(&descriptor_set_layout_info, None)
+                .bindings(&push_bindings)
+                .flags(vk::DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR);
+  let push_descriptor_set_layout = unsafe {
+    device.create_descriptor_set_layout(&push_descriptor_set_layout_info,
+                                        None)
   }?;
 
-  Ok(descriptor_set_layout)
+  Ok((primary_descriptor_set_layout, push_descriptor_set_layout))
 }
 
 
@@ -770,10 +825,15 @@ fn score_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR,
     // selected.
   }
 
-  let features = unsafe {
-    instance.get_physical_device_features(*physical_device)
+  let mut features_14
+          = vk::PhysicalDeviceVulkan14Features::default();
+  let mut features = vk::PhysicalDeviceFeatures2::builder()
+          .push_next(&mut features_14);
+  unsafe {
+    instance.get_physical_device_features2(*physical_device, &mut features)
   };
-  if features.sampler_anisotropy == vk::TRUE {
+
+  if features.features.sampler_anisotropy == vk::TRUE {
     score += 1;
   }
 
@@ -786,7 +846,17 @@ fn score_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR,
   score += shift;
   let sample_count = vk::SampleCountFlags::from_bits(1 << shift).unwrap();
 
-  Ok(Acceptable::Accepted((score, indices, sample_count)))
+  //   We prefer to report the most important reason, rather than
+  // less-fundamental ones. To make that easy, we try to keep this logic
+  // together at the end. Note that, before we get here, we may have already
+  // rejected the device thanks to find_device_queue_family_indices(), called
+  // above, but that's okay because all those reasons are in fact more
+  // important than the ones tested here.
+  if features_14.push_descriptor != vk::TRUE {
+    Ok(Acceptable::Rejected("Doesn't support push descriptors.".to_string()))
+  } else {
+    Ok(Acceptable::Accepted((score, indices, sample_count)))
+  }
 }
 
 
@@ -819,6 +889,9 @@ fn find_device_queue_family_indices(instance: &Instance,
     }
   }
 
+  //   We prefer to report the most important reason, rather than
+  // less-fundamental ones. To make that easy, we keep all this logic together
+  // at the end.
   if let Some(graphics) = graphics {
     if let Some(presentation) = presentation {
       Ok(Acceptable::Accepted(QueueFamilyIndices {
@@ -885,3 +958,4 @@ extern "system" fn debug_messager_callback(
   // test so anything it does is fine with us.
   vk::FALSE
 }
+