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