ailloli_ui_openxr/openxr_runtime/
vulkan.rs

1//! Vulkan objects created through OpenXR's Vulkan-enable2 extension.
2
3use std::ffi::CString;
4
5use ash::vk::{self, Handle};
6use ash::{Device, Entry as VkEntry, Instance as VkInstance};
7use openxr as xr;
8
9use super::error::OpenXrRuntimeError;
10use super::instance::OpenXrInstance;
11
12/// Vulkan instance/device and graphics submission resources owned by OpenXR host.
13///
14/// Drop destroys the command pool, logical device, then Vulkan instance. The
15/// selected physical device and queue are dictated by the OpenXR runtime.
16///
17/// # Examples
18///
19/// ```no_run
20/// use ailloli_ui_openxr::OpenXrVulkanContext;
21/// fn family(context: &OpenXrVulkanContext) -> u32 { context.queue_family_index }
22/// ```
23pub struct OpenXrVulkanContext {
24    /// Dynamically loaded Vulkan entry table.
25    pub vk_entry: VkEntry,
26    /// Vulkan instance created through OpenXR.
27    pub vk_instance: VkInstance,
28    /// Vulkan logical device created through OpenXR.
29    pub vk_device: Device,
30    /// Runtime-selected Vulkan physical device.
31    pub physical_device: vk::PhysicalDevice,
32    /// Memory properties queried from the selected physical device.
33    pub memory_properties: vk::PhysicalDeviceMemoryProperties,
34    /// Graphics queue at index zero of `queue_family_index`.
35    pub queue: vk::Queue,
36    /// First physical-device queue family advertising graphics capability.
37    pub queue_family_index: u32,
38    /// Resettable command pool for the selected graphics family.
39    pub command_pool: vk::CommandPool,
40}
41
42impl OpenXrVulkanContext {
43    /// Creates Vulkan 1.1 resources through the OpenXR instance.
44    ///
45    /// Both names are converted to C strings and must contain no NUL. The runtime
46    /// must advertise Vulkan 1.1 within its supported range. Queue index zero of
47    /// the first graphics-capable family is selected.
48    ///
49    /// # Errors
50    ///
51    /// Returns graphics requirement/version, loader/name, instance/device,
52    /// physical-device, queue-family, or command-pool failures.
53    ///
54    /// # Examples
55    ///
56    /// ```no_run
57    /// use ailloli_ui_openxr::{OpenXrRuntime, OpenXrRuntimeOptions};
58    /// let runtime = OpenXrRuntime::new(OpenXrRuntimeOptions::default())?;
59    /// let _device: &ash::Device = &runtime.vk.vk_device;
60    /// # Ok::<(), ailloli_ui_openxr::OpenXrRuntimeError>(())
61    /// ```
62    pub(crate) fn new(
63        xr: &OpenXrInstance,
64        application_name: &str,
65        engine_name: &str,
66    ) -> Result<Self, OpenXrRuntimeError> {
67        let vk_target_version = vk::make_api_version(0, 1, 1, 0);
68        let xr_target_version = xr::Version::new(1, 1, 0);
69
70        let requirements = xr
71            .instance
72            .graphics_requirements::<xr::Vulkan>(xr.system)
73            .map_err(|result| OpenXrRuntimeError::GraphicsRequirements { result })?;
74        if xr_target_version < requirements.min_api_version_supported
75            || xr_target_version > requirements.max_api_version_supported
76        {
77            return Err(OpenXrRuntimeError::UnsupportedVulkanVersion {
78                requested: xr_target_version,
79                min: requirements.min_api_version_supported,
80                max: requirements.max_api_version_supported,
81            });
82        }
83
84        let vk_entry =
85            unsafe { VkEntry::load() }.map_err(|err| OpenXrRuntimeError::LoadVulkanEntry {
86                message: err.to_string(),
87            })?;
88
89        let app_name = CString::new(application_name).map_err(|err| {
90            OpenXrRuntimeError::InvalidVulkanName {
91                field: "application_name",
92                message: err.to_string(),
93            }
94        })?;
95        let engine_name =
96            CString::new(engine_name).map_err(|err| OpenXrRuntimeError::InvalidVulkanName {
97                field: "engine_name",
98                message: err.to_string(),
99            })?;
100        let vk_app_info = vk::ApplicationInfo::default()
101            .application_name(app_name.as_c_str())
102            .engine_name(engine_name.as_c_str())
103            .api_version(vk_target_version);
104        let vk_instance_create_info =
105            vk::InstanceCreateInfo::default().application_info(&vk_app_info);
106
107        let vk_instance_raw = unsafe {
108            xr.instance.create_vulkan_instance(
109                xr.system,
110                std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr),
111                &vk_instance_create_info as *const _ as *const _,
112            )
113        }
114        .map_err(|result| OpenXrRuntimeError::CreateVulkanInstance { result })?
115        .map_err(vk::Result::from_raw)
116        .map_err(|result| OpenXrRuntimeError::CreateVulkanInstanceVk { result })?;
117
118        let vk_instance = unsafe {
119            VkInstance::load(
120                vk_entry.static_fn(),
121                vk::Instance::from_raw(vk_instance_raw as _),
122            )
123        };
124
125        let physical_device = unsafe {
126            vk::PhysicalDevice::from_raw(
127                xr.instance
128                    .vulkan_graphics_device(xr.system, vk_instance.handle().as_raw() as _)
129                    .map_err(|result| OpenXrRuntimeError::VulkanGraphicsDevice { result })?
130                    as _,
131            )
132        };
133        let memory_properties =
134            unsafe { vk_instance.get_physical_device_memory_properties(physical_device) };
135
136        let queue_family_index = unsafe {
137            vk_instance
138                .get_physical_device_queue_family_properties(physical_device)
139                .into_iter()
140                .enumerate()
141                .find_map(|(index, info)| {
142                    if info.queue_flags.contains(vk::QueueFlags::GRAPHICS) {
143                        Some(index as u32)
144                    } else {
145                        None
146                    }
147                })
148        }
149        .ok_or(OpenXrRuntimeError::NoGraphicsQueueFamily)?;
150
151        let queue_priorities = [1.0];
152        let queue_infos = [vk::DeviceQueueCreateInfo::default()
153            .queue_family_index(queue_family_index)
154            .queue_priorities(&queue_priorities)];
155        let device_create_info = vk::DeviceCreateInfo::default().queue_create_infos(&queue_infos);
156
157        let vk_device_raw = unsafe {
158            xr.instance.create_vulkan_device(
159                xr.system,
160                std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr),
161                physical_device.as_raw() as _,
162                &device_create_info as *const _ as *const _,
163            )
164        }
165        .map_err(|result| OpenXrRuntimeError::CreateVulkanDevice { result })?
166        .map_err(vk::Result::from_raw)
167        .map_err(|result| OpenXrRuntimeError::CreateVulkanDeviceVk { result })?;
168
169        let vk_device = unsafe {
170            Device::load(
171                vk_instance.fp_v1_0(),
172                vk::Device::from_raw(vk_device_raw as _),
173            )
174        };
175        let queue = unsafe { vk_device.get_device_queue(queue_family_index, 0) };
176
177        let command_pool = unsafe {
178            vk_device.create_command_pool(
179                &vk::CommandPoolCreateInfo::default()
180                    .queue_family_index(queue_family_index)
181                    .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER),
182                None,
183            )
184        }
185        .map_err(|result| OpenXrRuntimeError::CreateCommandPool { result })?;
186
187        Ok(Self {
188            vk_entry,
189            vk_instance,
190            vk_device,
191            physical_device,
192            memory_properties,
193            queue,
194            queue_family_index,
195            command_pool,
196        })
197    }
198
199    /// Records and synchronously submits one transient graphics command buffer.
200    ///
201    /// The callback runs between successful begin/end operations. The queue is
202    /// waited idle, and the command buffer is freed even when recording or submit
203    /// handling returns an error after allocation.
204    ///
205    /// # Errors
206    ///
207    /// Returns allocation, begin, end, submit, or queue-idle failures.
208    ///
209    /// # Examples
210    ///
211    /// ```no_run
212    /// use ailloli_ui_openxr::{OpenXrRuntimeError, OpenXrVulkanContext};
213    /// fn submit(context: &OpenXrVulkanContext) -> Result<(), OpenXrRuntimeError> { context.render_context(); Ok(()) }
214    /// ```
215    pub(crate) fn submit_one_time_commands<F>(&self, record: F) -> Result<(), OpenXrRuntimeError>
216    where
217        F: FnOnce(vk::CommandBuffer),
218    {
219        let command_buffer = unsafe {
220            self.vk_device.allocate_command_buffers(
221                &vk::CommandBufferAllocateInfo::default()
222                    .command_pool(self.command_pool)
223                    .level(vk::CommandBufferLevel::PRIMARY)
224                    .command_buffer_count(1),
225            )
226        }
227        .map_err(|result| OpenXrRuntimeError::AllocateCommandBuffer { result })?[0];
228
229        let result = (|| {
230            unsafe {
231                self.vk_device.begin_command_buffer(
232                    command_buffer,
233                    &vk::CommandBufferBeginInfo::default()
234                        .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
235                )
236            }
237            .map_err(|result| OpenXrRuntimeError::BeginCommandBuffer { result })?;
238
239            record(command_buffer);
240
241            unsafe { self.vk_device.end_command_buffer(command_buffer) }
242                .map_err(|result| OpenXrRuntimeError::EndCommandBuffer { result })?;
243
244            let command_buffers = [command_buffer];
245            let submit_infos = [vk::SubmitInfo::default().command_buffers(&command_buffers)];
246            unsafe {
247                self.vk_device
248                    .queue_submit(self.queue, &submit_infos, vk::Fence::null())
249            }
250            .map_err(|result| OpenXrRuntimeError::QueueSubmit { result })?;
251
252            unsafe { self.vk_device.queue_wait_idle(self.queue) }
253                .map_err(|result| OpenXrRuntimeError::QueueWaitIdle { result })?;
254
255            Ok(())
256        })();
257
258        unsafe {
259            self.vk_device
260                .free_command_buffers(self.command_pool, &[command_buffer]);
261        }
262
263        result
264    }
265
266    /// Borrows device, queue, pool, and memory properties for the Vulkan renderer.
267    ///
268    /// # Examples
269    ///
270    /// ```no_run
271    /// use ailloli_ui_openxr::OpenXrVulkanContext;
272    /// use ailloli_ui_render_vulkan::VulkanRenderContext;
273    /// fn render_context(context: &OpenXrVulkanContext) -> VulkanRenderContext<'_> { context.render_context() }
274    /// ```
275    pub fn render_context(&self) -> ailloli_ui_render_vulkan::VulkanRenderContext<'_> {
276        ailloli_ui_render_vulkan::VulkanRenderContext::with_memory_properties(
277            &self.vk_device,
278            self.queue,
279            self.queue_family_index,
280            self.command_pool,
281            &self.memory_properties,
282        )
283    }
284}
285
286impl Drop for OpenXrVulkanContext {
287    fn drop(&mut self) {
288        unsafe {
289            if self.command_pool != vk::CommandPool::null() {
290                self.vk_device.destroy_command_pool(self.command_pool, None);
291            }
292            self.vk_device.destroy_device(None);
293            self.vk_instance.destroy_instance(None);
294        }
295    }
296}