1use 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 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}