Skip to main content

lgpu_hal/vulkan/
instance.rs

1//! Vulkan instance, physical device adapters, and surface creation.
2
3use ash::ext::{headless_surface, metal_surface};
4use ash::khr::{surface, swapchain, wayland_surface, win32_surface, xcb_surface, xlib_surface};
5use ash::vk;
6use std::ffi::CString;
7
8use super::{allocator, bindless, VulkanDevice};
9use crate::{AdapterInfo, Backend, DeviceType, HalError};
10
11pub struct VulkanInstance {
12    pub entry: ash::Entry,
13    pub instance: ash::Instance,
14    pub surface_loader: Option<surface::Instance>,
15}
16
17pub struct VulkanAdapter {
18    pub physical_device: vk::PhysicalDevice,
19    pub info: AdapterInfo,
20    pub queue_family_index: u32,
21    pub memory_properties: vk::PhysicalDeviceMemoryProperties,
22    pub non_coherent_atom_size: u64,
23}
24
25impl VulkanInstance {
26    pub fn new() -> Result<Self, HalError> {
27        let entry =
28            unsafe { ash::Entry::load().map_err(|e| HalError::LibraryLoadError(e.to_string()))? };
29
30        let app_name = CString::new("lgpu Application").unwrap();
31        let engine_name = CString::new("lgpu").unwrap();
32
33        let app_info = vk::ApplicationInfo::default()
34            .application_name(&app_name)
35            .application_version(vk::make_api_version(0, 1, 0, 0))
36            .engine_name(&engine_name)
37            .engine_version(vk::make_api_version(0, 1, 0, 0))
38            .api_version(vk::API_VERSION_1_3);
39
40        let supported_extensions = unsafe {
41            entry
42                .enumerate_instance_extension_properties(None)
43                .unwrap_or_default()
44        };
45        let is_ext_supported = |name: &std::ffi::CStr| {
46            supported_extensions
47                .iter()
48                .any(|ext| unsafe { std::ffi::CStr::from_ptr(ext.extension_name.as_ptr()) == name })
49        };
50
51        let mut enabled_extensions = Vec::new();
52        if is_ext_supported(surface::NAME) {
53            enabled_extensions.push(surface::NAME.as_ptr());
54        }
55        if is_ext_supported(xlib_surface::NAME) {
56            enabled_extensions.push(xlib_surface::NAME.as_ptr());
57        }
58        if is_ext_supported(xcb_surface::NAME) {
59            enabled_extensions.push(xcb_surface::NAME.as_ptr());
60        }
61        if is_ext_supported(wayland_surface::NAME) {
62            enabled_extensions.push(wayland_surface::NAME.as_ptr());
63        }
64        if is_ext_supported(win32_surface::NAME) {
65            enabled_extensions.push(win32_surface::NAME.as_ptr());
66        }
67        if is_ext_supported(metal_surface::NAME) {
68            enabled_extensions.push(metal_surface::NAME.as_ptr());
69        }
70        if is_ext_supported(headless_surface::NAME) {
71            enabled_extensions.push(headless_surface::NAME.as_ptr());
72        }
73
74        #[allow(unused_mut)]
75        let mut flags = vk::InstanceCreateFlags::empty();
76        #[cfg(any(target_os = "macos", target_os = "ios"))]
77        {
78            let portability_ext = vk::KHR_PORTABILITY_ENUMERATION_NAME;
79            if is_ext_supported(portability_ext) {
80                enabled_extensions.push(portability_ext.as_ptr());
81                flags |= vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR;
82            }
83        }
84
85        let create_info = vk::InstanceCreateInfo::default()
86            .application_info(&app_info)
87            .flags(flags)
88            .enabled_extension_names(&enabled_extensions);
89
90        let instance = unsafe {
91            entry.create_instance(&create_info, None).map_err(|e| {
92                HalError::BackendError(format!("Failed to create Vulkan instance: {e}"))
93            })?
94        };
95
96        let surface_loader = if is_ext_supported(surface::NAME) {
97            Some(surface::Instance::new(&entry, &instance))
98        } else {
99            None
100        };
101
102        Ok(Self {
103            entry,
104            instance,
105            surface_loader,
106        })
107    }
108
109    pub fn create_headless_surface(&self) -> Result<vk::SurfaceKHR, HalError> {
110        let headless_loader = headless_surface::Instance::new(&self.entry, &self.instance);
111        let create_info = vk::HeadlessSurfaceCreateInfoEXT::default();
112        unsafe { headless_loader.create_headless_surface(&create_info, None) }
113            .map_err(|e| HalError::BackendError(format!("Failed to create headless surface: {e}")))
114    }
115
116    pub fn create_surface(
117        &self,
118        display_handle: raw_window_handle::RawDisplayHandle,
119        window_handle: raw_window_handle::RawWindowHandle,
120    ) -> Result<vk::SurfaceKHR, HalError> {
121        use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
122
123        match (display_handle, window_handle) {
124            (RawDisplayHandle::Xlib(display), RawWindowHandle::Xlib(window)) => {
125                let xlib_loader = xlib_surface::Instance::new(&self.entry, &self.instance);
126                let create_info = vk::XlibSurfaceCreateInfoKHR::default()
127                    .dpy(
128                        display
129                            .display
130                            .map(|p| p.as_ptr() as *mut _)
131                            .unwrap_or(std::ptr::null_mut()),
132                    )
133                    .window(window.window as vk::Window);
134                unsafe { xlib_loader.create_xlib_surface(&create_info, None) }.map_err(|e| {
135                    HalError::BackendError(format!("Failed to create Xlib surface: {e}"))
136                })
137            }
138            (RawDisplayHandle::Xcb(display), RawWindowHandle::Xcb(window)) => {
139                let xcb_loader = xcb_surface::Instance::new(&self.entry, &self.instance);
140                let create_info = vk::XcbSurfaceCreateInfoKHR::default()
141                    .connection(
142                        display
143                            .connection
144                            .map(|p| p.as_ptr() as *mut _)
145                            .unwrap_or(std::ptr::null_mut()),
146                    )
147                    .window(window.window.get());
148                unsafe { xcb_loader.create_xcb_surface(&create_info, None) }.map_err(|e| {
149                    HalError::BackendError(format!("Failed to create XCB surface: {e}"))
150                })
151            }
152            (RawDisplayHandle::Wayland(display), RawWindowHandle::Wayland(window)) => {
153                let wayland_loader = wayland_surface::Instance::new(&self.entry, &self.instance);
154                let create_info = vk::WaylandSurfaceCreateInfoKHR::default()
155                    .display(display.display.as_ptr() as *mut _)
156                    .surface(window.surface.as_ptr() as *mut _);
157                unsafe { wayland_loader.create_wayland_surface(&create_info, None) }.map_err(|e| {
158                    HalError::BackendError(format!("Failed to create Wayland surface: {e}"))
159                })
160            }
161            (RawDisplayHandle::Windows(_), RawWindowHandle::Win32(window)) => {
162                let win32_loader = win32_surface::Instance::new(&self.entry, &self.instance);
163                let create_info = vk::Win32SurfaceCreateInfoKHR::default()
164                    .hinstance(window.hinstance.map(|p| p.get() as isize).unwrap_or(0))
165                    .hwnd(window.hwnd.get() as isize);
166                unsafe { win32_loader.create_win32_surface(&create_info, None) }.map_err(|e| {
167                    HalError::BackendError(format!("Failed to create Win32 surface: {e}"))
168                })
169            }
170            (RawDisplayHandle::AppKit(_), RawWindowHandle::AppKit(window)) => {
171                let metal_loader = metal_surface::Instance::new(&self.entry, &self.instance);
172                let create_info = vk::MetalSurfaceCreateInfoEXT::default()
173                    .layer(window.ns_view.as_ptr() as *const _);
174                unsafe { metal_loader.create_metal_surface(&create_info, None) }.map_err(|e| {
175                    HalError::BackendError(format!("Failed to create Metal surface: {e}"))
176                })
177            }
178            (RawDisplayHandle::UiKit(_), RawWindowHandle::UiKit(window)) => {
179                let metal_loader = metal_surface::Instance::new(&self.entry, &self.instance);
180                let create_info = vk::MetalSurfaceCreateInfoEXT::default()
181                    .layer(window.ui_view.as_ptr() as *const _);
182                unsafe { metal_loader.create_metal_surface(&create_info, None) }.map_err(|e| {
183                    HalError::BackendError(format!("Failed to create iOS Metal surface: {e}"))
184                })
185            }
186            _ => Err(HalError::BackendError(
187                "Unsupported display/window handle pair for Vulkan surface creation".to_string(),
188            )),
189        }
190    }
191
192    pub fn destroy_surface(&self, surface: vk::SurfaceKHR) {
193        if let Some(loader) = &self.surface_loader {
194            unsafe {
195                loader.destroy_surface(surface, None);
196            }
197        }
198    }
199
200    pub fn enumerate_adapters(&self) -> Result<Vec<VulkanAdapter>, HalError> {
201        let physical_devices = unsafe {
202            self.instance
203                .enumerate_physical_devices()
204                .map_err(|e| HalError::BackendError(e.to_string()))?
205        };
206
207        if physical_devices.is_empty() {
208            return Err(HalError::NoAdapterFound);
209        }
210
211        let mut adapters = Vec::new();
212        for pd in physical_devices {
213            let props = unsafe { self.instance.get_physical_device_properties(pd) };
214            let mem_props = unsafe { self.instance.get_physical_device_memory_properties(pd) };
215            let queue_props = unsafe {
216                self.instance
217                    .get_physical_device_queue_family_properties(pd)
218            };
219
220            // Find a queue family that supports both graphics and compute (or at least compute)
221            let mut selected_queue = None;
222            for (idx, qp) in queue_props.iter().enumerate() {
223                if qp.queue_flags.contains(vk::QueueFlags::COMPUTE) {
224                    selected_queue = Some(idx as u32);
225                    if qp.queue_flags.contains(vk::QueueFlags::GRAPHICS) {
226                        break;
227                    }
228                }
229            }
230
231            let queue_family_index = match selected_queue {
232                Some(idx) => idx,
233                None => continue,
234            };
235
236            let name_bytes: Vec<u8> = props
237                .device_name
238                .iter()
239                .take_while(|&&c| c != 0)
240                .map(|&c| c as u8)
241                .collect();
242            let name = String::from_utf8_lossy(&name_bytes).to_string();
243
244            let device_type = match props.device_type {
245                vk::PhysicalDeviceType::DISCRETE_GPU => DeviceType::DiscreteGpu,
246                vk::PhysicalDeviceType::INTEGRATED_GPU => DeviceType::IntegratedGpu,
247                vk::PhysicalDeviceType::VIRTUAL_GPU => DeviceType::VirtualGpu,
248                vk::PhysicalDeviceType::CPU => DeviceType::Cpu,
249                _ => DeviceType::Other,
250            };
251
252            let mut supports_dynamic_rendering = false;
253            let mut supports_timeline_semaphores = false;
254            let mut supports_descriptor_indexing = false;
255
256            if props.api_version >= vk::make_api_version(0, 1, 1, 0) {
257                let mut features13 = vk::PhysicalDeviceVulkan13Features::default();
258                let mut features12 = vk::PhysicalDeviceVulkan12Features::default();
259
260                if props.api_version >= vk::make_api_version(0, 1, 3, 0) {
261                    let mut features2 = vk::PhysicalDeviceFeatures2::default()
262                        .push_next(&mut features12)
263                        .push_next(&mut features13);
264                    unsafe {
265                        self.instance
266                            .get_physical_device_features2(pd, &mut features2);
267                    }
268                    supports_dynamic_rendering = features13.dynamic_rendering == vk::TRUE;
269                    supports_timeline_semaphores = features12.timeline_semaphore == vk::TRUE;
270                    supports_descriptor_indexing = features12.descriptor_indexing == vk::TRUE;
271                } else if props.api_version >= vk::make_api_version(0, 1, 2, 0) {
272                    let mut features2 =
273                        vk::PhysicalDeviceFeatures2::default().push_next(&mut features12);
274                    unsafe {
275                        self.instance
276                            .get_physical_device_features2(pd, &mut features2);
277                    }
278                    supports_timeline_semaphores = features12.timeline_semaphore == vk::TRUE;
279                    supports_descriptor_indexing = features12.descriptor_indexing == vk::TRUE;
280                }
281            }
282
283            let supported_dev_extensions = unsafe {
284                self.instance
285                    .enumerate_device_extension_properties(pd)
286                    .unwrap_or_default()
287            };
288            let is_dev_ext_supported_bytes = |name: &[u8]| {
289                supported_dev_extensions.iter().any(|ext| {
290                    let ext_name = unsafe { std::ffi::CStr::from_ptr(ext.extension_name.as_ptr()) };
291                    ext_name.to_bytes_with_nul() == name
292                })
293            };
294
295            if !supports_dynamic_rendering
296                && is_dev_ext_supported_bytes(b"VK_KHR_dynamic_rendering\0")
297            {
298                supports_dynamic_rendering = true;
299            }
300            if !supports_timeline_semaphores
301                && is_dev_ext_supported_bytes(b"VK_KHR_timeline_semaphore\0")
302            {
303                supports_timeline_semaphores = true;
304            }
305            if !supports_descriptor_indexing
306                && is_dev_ext_supported_bytes(b"VK_EXT_descriptor_indexing\0")
307            {
308                supports_descriptor_indexing = true;
309            }
310
311            let adapter_info = AdapterInfo {
312                name,
313                backend: Backend::Vulkan,
314                device_type,
315                vendor_id: props.vendor_id,
316                device_id: props.device_id,
317                supports_dynamic_rendering,
318                supports_timeline_semaphores,
319                supports_descriptor_indexing,
320                max_compute_workgroup_invocations: props.limits.max_compute_work_group_invocations,
321                max_compute_workgroup_size: props.limits.max_compute_work_group_size,
322                limits: crate::DeviceLimits {
323                    max_buffer_size: props.limits.max_storage_buffer_range as u64,
324                    max_texture_dimension_1d: props.limits.max_image_dimension1_d,
325                    max_texture_dimension_2d: props.limits.max_image_dimension2_d,
326                    max_texture_dimension_3d: props.limits.max_image_dimension3_d,
327                    max_texture_array_layers: props.limits.max_image_array_layers,
328                    min_uniform_buffer_offset_alignment: props
329                        .limits
330                        .min_uniform_buffer_offset_alignment,
331                    min_storage_buffer_offset_alignment: props
332                        .limits
333                        .min_storage_buffer_offset_alignment,
334                    max_compute_workgroup_invocations: props
335                        .limits
336                        .max_compute_work_group_invocations,
337                    max_compute_workgroup_size: props.limits.max_compute_work_group_size,
338                },
339            };
340
341            adapters.push(VulkanAdapter {
342                physical_device: pd,
343                info: adapter_info,
344                queue_family_index,
345                memory_properties: mem_props,
346                non_coherent_atom_size: props.limits.non_coherent_atom_size,
347            });
348        }
349
350        Ok(adapters)
351    }
352
353    pub fn create_device(&self, adapter: &VulkanAdapter) -> Result<VulkanDevice, HalError> {
354        let queue_priorities = [1.0f32];
355        let queue_create_info = vk::DeviceQueueCreateInfo::default()
356            .queue_family_index(adapter.queue_family_index)
357            .queue_priorities(&queue_priorities);
358
359        let queue_infos = [queue_create_info];
360
361        let mut features13 = vk::PhysicalDeviceVulkan13Features::default()
362            .dynamic_rendering(true)
363            .synchronization2(true);
364
365        let mut features12 = vk::PhysicalDeviceVulkan12Features::default()
366            .timeline_semaphore(true)
367            .descriptor_indexing(true)
368            .shader_sampled_image_array_non_uniform_indexing(true)
369            .shader_storage_buffer_array_non_uniform_indexing(true)
370            .descriptor_binding_sampled_image_update_after_bind(true)
371            .descriptor_binding_storage_buffer_update_after_bind(true)
372            .descriptor_binding_partially_bound(true)
373            .runtime_descriptor_array(true);
374
375        let mut features = vk::PhysicalDeviceFeatures::default();
376        features.shader_int64 = vk::TRUE;
377
378        let supported_dev_extensions = unsafe {
379            self.instance
380                .enumerate_device_extension_properties(adapter.physical_device)
381                .unwrap_or_default()
382        };
383        let is_dev_ext_supported = |name: &std::ffi::CStr| {
384            supported_dev_extensions
385                .iter()
386                .any(|ext| unsafe { std::ffi::CStr::from_ptr(ext.extension_name.as_ptr()) == name })
387        };
388
389        let mut enabled_extensions = Vec::new();
390        let swapchain_supported = is_dev_ext_supported(swapchain::NAME);
391        if swapchain_supported {
392            enabled_extensions.push(swapchain::NAME.as_ptr());
393        }
394        let portability_subset_ext = vk::KHR_PORTABILITY_SUBSET_NAME;
395        if is_dev_ext_supported(portability_subset_ext) {
396            enabled_extensions.push(portability_subset_ext.as_ptr());
397        }
398
399        let device_create_info = vk::DeviceCreateInfo::default()
400            .queue_create_infos(&queue_infos)
401            .enabled_features(&features)
402            .enabled_extension_names(&enabled_extensions)
403            .push_next(&mut features12)
404            .push_next(&mut features13);
405
406        if !adapter.info.supports_dynamic_rendering
407            || !adapter.info.supports_timeline_semaphores
408            || !adapter.info.supports_descriptor_indexing
409        {
410            return Err(HalError::DeviceCreationFailed(
411                "Adapter does not support required features: Vulkan 1.3 dynamic rendering, timeline semaphores, and descriptor indexing are required".to_string(),
412            ));
413        }
414
415        let device = unsafe {
416            self.instance
417                .create_device(adapter.physical_device, &device_create_info, None)
418                .map_err(|e| HalError::DeviceCreationFailed(format!(
419                    "Failed to create Vulkan logical device with required features (Vulkan 1.3+, Dynamic Rendering, Timeline Semaphores, Descriptor Indexing): {e}"
420                )))?
421        };
422
423        let queue = unsafe { device.get_device_queue(adapter.queue_family_index, 0) };
424
425        let swapchain_loader = if swapchain_supported {
426            Some(swapchain::Device::new(&self.instance, &device))
427        } else {
428            None
429        };
430
431        let bindless_heap = bindless::VulkanBindlessHeap::new(
432            &device,
433            bindless::DEFAULT_MAX_STORAGE_BUFFERS,
434            bindless::DEFAULT_MAX_TEXTURES,
435            bindless::DEFAULT_MAX_SAMPLERS,
436            bindless::DEFAULT_MAX_STORAGE_IMAGES,
437        )
438        .ok();
439
440        let mut type_info = vk::SemaphoreTypeCreateInfo::default()
441            .semaphore_type(vk::SemaphoreType::TIMELINE)
442            .initial_value(0);
443        let sem_create_info = vk::SemaphoreCreateInfo::default().push_next(&mut type_info);
444        let internal_frame_semaphore = unsafe {
445            device
446                .create_semaphore(&sem_create_info, None)
447                .map_err(|e| {
448                    HalError::DeviceCreationFailed(format!(
449                        "Failed to create internal timeline semaphore: {e}"
450                    ))
451                })?
452        };
453
454        let pipeline_cache = unsafe {
455            let cache_info = vk::PipelineCacheCreateInfo::default();
456            device
457                .create_pipeline_cache(&cache_info, None)
458                .map_err(|e| {
459                    HalError::DeviceCreationFailed(format!("Failed to create pipeline cache: {e}"))
460                })?
461        };
462
463        Ok(VulkanDevice {
464            device,
465            physical_device: adapter.physical_device,
466            queue: std::sync::Mutex::new(queue),
467            queue_family_index: adapter.queue_family_index,
468            memory_properties: adapter.memory_properties,
469            non_coherent_atom_size: adapter.non_coherent_atom_size,
470            bindless_heap,
471            swapchain_loader,
472            command_recorder_cache: std::sync::Mutex::new(Vec::new()),
473            fence_cache: std::sync::Mutex::new(Vec::new()),
474            internal_frame_semaphore,
475            internal_frame_counter: std::sync::atomic::AtomicU64::new(0),
476            pending_async_recorders: std::sync::Mutex::new(Vec::new()),
477            allocator: std::sync::Mutex::new(allocator::VulkanSuballocator::new()),
478            pipeline_cache,
479        })
480    }
481}
482
483impl VulkanAdapter {
484    pub fn info(&self) -> &AdapterInfo {
485        &self.info
486    }
487}
488
489impl Drop for VulkanInstance {
490    fn drop(&mut self) {
491        unsafe {
492            self.instance.destroy_instance(None);
493        }
494    }
495}