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