1use std::ffi::{CStr, CString};
6use std::os::unix::ffi::OsStrExt;
7use std::path::{Path, PathBuf};
8use std::fs;
9use std::env;
10use libc::{c_void, c_char};
11use std::collections::HashMap;
12use std::sync::{Arc, Mutex, Weak};
13use log::{info, warn, debug};
14use serde::{Deserialize, Serialize};
15use crate::sys::*;
16use crate::core::*;
17use crate::ffi::*;
18use super::error::IcdError;
19
20fn get_icd_search_paths() -> Vec<PathBuf> {
22 let mut paths = Vec::new();
23
24 if let Ok(custom_paths) = env::var("KRONOS_ICD_SEARCH_PATHS") {
26 for path in custom_paths.split(if cfg!(windows) { ';' } else { ':' }) {
27 paths.push(PathBuf::from(path));
28 }
29 return paths;
30 }
31
32 #[cfg(target_os = "linux")]
34 {
35 paths.extend([
36 PathBuf::from("/usr/share/vulkan/icd.d"),
37 PathBuf::from("/usr/local/share/vulkan/icd.d"),
38 PathBuf::from("/etc/vulkan/icd.d"),
39 PathBuf::from("/usr/share/vulkan/implicit_layer.d"),
40 ]);
41 }
42
43 #[cfg(target_os = "windows")]
44 {
45 if let Ok(system_root) = env::var("SYSTEMROOT") {
47 paths.push(PathBuf::from(system_root).join("System32").join("vulkan"));
48 }
49 paths.push(PathBuf::from("C:\\Windows\\System32\\vulkan"));
50
51 if let Ok(program_files) = env::var("PROGRAMFILES") {
53 paths.push(PathBuf::from(program_files).join("Vulkan").join("Config"));
54 }
55 }
56
57 #[cfg(target_os = "macos")]
58 {
59 paths.extend([
60 PathBuf::from("/usr/local/share/vulkan/icd.d"),
61 PathBuf::from("/System/Library/Extensions"),
62 PathBuf::from("/Library/Extensions"),
63 ]);
64
65 if let Ok(home) = env::var("HOME") {
67 paths.push(PathBuf::from(home).join(".local/share/vulkan/icd.d"));
68 }
69 }
70
71 let mut canon = Vec::new();
73 for p in paths {
74 match fs::canonicalize(&p) {
75 Ok(cp) => canon.push(cp),
76 Err(_) => canon.push(p), }
78 }
79 log::info!("ICD search paths: {:#?}", canon);
80 canon
81}
82
83#[derive(Clone)]
85pub struct LoadedICD {
86 pub library_path: PathBuf,
87 pub handle: *mut c_void,
88 pub api_version: u32,
89
90 pub vk_get_instance_proc_addr: PFN_vkGetInstanceProcAddr,
92
93 pub create_instance: PFN_vkCreateInstance,
95 pub destroy_instance: PFN_vkDestroyInstance,
96 pub enumerate_physical_devices: PFN_vkEnumeratePhysicalDevices,
97 pub get_physical_device_properties: PFN_vkGetPhysicalDeviceProperties,
98 pub get_physical_device_queue_family_properties: PFN_vkGetPhysicalDeviceQueueFamilyProperties,
99 pub get_physical_device_memory_properties: PFN_vkGetPhysicalDeviceMemoryProperties,
100
101 pub create_device: PFN_vkCreateDevice,
103 pub destroy_device: PFN_vkDestroyDevice,
104 pub get_device_proc_addr: PFN_vkGetDeviceProcAddr,
105 pub get_device_queue: PFN_vkGetDeviceQueue,
106
107 pub queue_submit: PFN_vkQueueSubmit,
109 pub queue_wait_idle: PFN_vkQueueWaitIdle,
110 pub device_wait_idle: PFN_vkDeviceWaitIdle,
111
112 pub allocate_memory: PFN_vkAllocateMemory,
114 pub free_memory: PFN_vkFreeMemory,
115 pub map_memory: PFN_vkMapMemory,
116 pub unmap_memory: PFN_vkUnmapMemory,
117
118 pub create_buffer: PFN_vkCreateBuffer,
120 pub destroy_buffer: PFN_vkDestroyBuffer,
121 pub get_buffer_memory_requirements: PFN_vkGetBufferMemoryRequirements,
122 pub bind_buffer_memory: PFN_vkBindBufferMemory,
123
124 pub create_descriptor_set_layout: PFN_vkCreateDescriptorSetLayout,
126 pub destroy_descriptor_set_layout: PFN_vkDestroyDescriptorSetLayout,
127 pub create_descriptor_pool: PFN_vkCreateDescriptorPool,
128 pub destroy_descriptor_pool: PFN_vkDestroyDescriptorPool,
129 pub reset_descriptor_pool: Option<unsafe extern "C" fn(VkDevice, VkDescriptorPool, VkDescriptorPoolResetFlags) -> VkResult>,
130 pub allocate_descriptor_sets: PFN_vkAllocateDescriptorSets,
131 pub free_descriptor_sets: Option<unsafe extern "C" fn(VkDevice, VkDescriptorPool, u32, *const VkDescriptorSet) -> VkResult>,
132 pub update_descriptor_sets: PFN_vkUpdateDescriptorSets,
133
134 pub create_pipeline_layout: PFN_vkCreatePipelineLayout,
136 pub destroy_pipeline_layout: PFN_vkDestroyPipelineLayout,
137 pub create_compute_pipelines: PFN_vkCreateComputePipelines,
138 pub destroy_pipeline: PFN_vkDestroyPipeline,
139
140 pub create_shader_module: PFN_vkCreateShaderModule,
142 pub destroy_shader_module: PFN_vkDestroyShaderModule,
143
144 pub create_command_pool: PFN_vkCreateCommandPool,
146 pub destroy_command_pool: PFN_vkDestroyCommandPool,
147 pub allocate_command_buffers: PFN_vkAllocateCommandBuffers,
148 pub free_command_buffers: Option<unsafe extern "C" fn(VkDevice, VkCommandPool, u32, *const VkCommandBuffer)>,
149 pub begin_command_buffer: PFN_vkBeginCommandBuffer,
150 pub end_command_buffer: PFN_vkEndCommandBuffer,
151 pub cmd_bind_pipeline: PFN_vkCmdBindPipeline,
152 pub cmd_bind_descriptor_sets: PFN_vkCmdBindDescriptorSets,
153 pub cmd_dispatch: PFN_vkCmdDispatch,
154 pub cmd_dispatch_indirect: Option<unsafe extern "C" fn(VkCommandBuffer, VkBuffer, VkDeviceSize)>,
155 pub cmd_pipeline_barrier: PFN_vkCmdPipelineBarrier,
156 pub cmd_copy_buffer: Option<unsafe extern "C" fn(VkCommandBuffer, VkBuffer, VkBuffer, u32, *const VkBufferCopy)>,
157 pub cmd_push_constants: Option<unsafe extern "C" fn(VkCommandBuffer, VkPipelineLayout, VkShaderStageFlags, u32, u32, *const c_void)>,
158
159 pub create_fence: PFN_vkCreateFence,
161 pub destroy_fence: PFN_vkDestroyFence,
162 pub reset_fences: PFN_vkResetFences,
163 pub get_fence_status: PFN_vkGetFenceStatus,
164 pub wait_for_fences: PFN_vkWaitForFences,
165 pub create_semaphore: PFN_vkCreateSemaphore,
166 pub destroy_semaphore: PFN_vkDestroySemaphore,
167 pub create_event: PFN_vkCreateEvent,
168 pub destroy_event: PFN_vkDestroyEvent,
169 pub get_event_status: PFN_vkGetEventStatus,
170 pub set_event: PFN_vkSetEvent,
171 pub reset_event: PFN_vkResetEvent,
172 pub cmd_set_event: PFN_vkCmdSetEvent,
173 pub cmd_reset_event: PFN_vkCmdResetEvent,
174 pub cmd_wait_events: PFN_vkCmdWaitEvents,
175
176 pub wait_semaphores: Option<unsafe extern "C" fn(VkDevice, *const VkSemaphoreWaitInfo, u64) -> VkResult>,
178}
179
180unsafe impl Send for LoadedICD {}
186unsafe impl Sync for LoadedICD {}
187
188#[derive(Debug, Clone)]
190pub struct IcdInfo {
191 pub library_path: PathBuf,
192 pub manifest_path: Option<PathBuf>,
193 pub api_version: u32,
194 pub is_software: bool,
195}
196
197#[derive(Debug, Deserialize, Serialize)]
199struct ICDManifestRoot {
200 file_format_version: String,
201 #[serde(rename = "ICD")]
202 icd: ICDManifest,
203}
204
205#[derive(Debug, Deserialize, Serialize)]
207struct ICDManifest {
208 library_path: String,
209 api_version: Option<String>,
210}
211
212lazy_static::lazy_static! {
213 pub static ref ICD_LOADER: Mutex<Option<Arc<LoadedICD>>> = Mutex::new(None);
215 static ref REG_INSTANCES: Mutex<HashMap<u64, Weak<LoadedICD>>> = Mutex::new(HashMap::new());
217 static ref REG_PHYS_DEVS: Mutex<HashMap<u64, Weak<LoadedICD>>> = Mutex::new(HashMap::new());
218 static ref REG_DEVICES: Mutex<HashMap<u64, Weak<LoadedICD>>> = Mutex::new(HashMap::new());
219 static ref REG_QUEUES: Mutex<HashMap<u64, Weak<LoadedICD>>> = Mutex::new(HashMap::new());
220 static ref REG_CMD_POOLS: Mutex<HashMap<u64, Weak<LoadedICD>>> = Mutex::new(HashMap::new());
221 static ref REG_CMD_BUFFERS: Mutex<HashMap<u64, Weak<LoadedICD>>> = Mutex::new(HashMap::new());
222 static ref ALL_ICDS: Mutex<Vec<Arc<LoadedICD>>> = Mutex::new(Vec::new());
224 static ref META_INSTANCES: Mutex<HashMap<u64, Vec<(Arc<LoadedICD>, VkInstance)>>> = Mutex::new(HashMap::new());
225 static ref NEXT_META_INSTANCE: Mutex<u64> = Mutex::new(0xBEEF_0000_0000_0000);
226}
227
228pub fn aggregated_mode_enabled() -> bool {
229 std::env::var("KRONOS_AGGREGATE_ICD").map(|v| v == "1").unwrap_or(false)
230}
231
232pub fn new_meta_instance_id() -> u64 {
233 let mut g = NEXT_META_INSTANCE.lock().unwrap();
234 *g += 1;
235 *g
236}
237
238pub fn set_meta_instance(meta_id: u64, inners: Vec<(Arc<LoadedICD>, VkInstance)>) {
239 let _ = META_INSTANCES.lock().map(|mut m| { m.insert(meta_id, inners); });
240}
241
242pub fn take_meta_instance(meta_id: u64) -> Option<Vec<(Arc<LoadedICD>, VkInstance)>> {
243 META_INSTANCES.lock().ok()?.remove(&meta_id)
244}
245
246pub fn meta_instance_for(meta_id: u64) -> Option<Vec<(Arc<LoadedICD>, VkInstance)>> {
247 META_INSTANCES.lock().ok()?.get(&meta_id).cloned()
248}
249
250pub fn get_all_icds() -> Vec<Arc<LoadedICD>> {
251 if let Ok(guard) = ALL_ICDS.lock() {
252 guard.clone()
253 } else {
254 Vec::new()
255 }
256}
257
258pub fn discover_and_load_all_icds() -> Vec<Arc<LoadedICD>> {
260 let existing = get_all_icds();
262 if !existing.is_empty() {
263 info!("Using {} already loaded ICDs for aggregated mode", existing.len());
264 return existing;
265 }
266
267 let mut out = Vec::new();
269 let icd_files = discover_icds();
270 if icd_files.is_empty() { return out; }
271
272 let prefer_hardware = env::var("KRONOS_PREFER_HARDWARE").map(|v| v != "0").unwrap_or(true);
273
274 for icd_file in &icd_files {
275 if let Some(manifest) = parse_icd_manifest(icd_file) {
276 let mut candidates: Vec<PathBuf> = Vec::new();
277 if Path::new(&manifest.library_path).is_absolute() {
278 candidates.push(PathBuf::from(&manifest.library_path));
279 } else {
280 candidates.push(PathBuf::from(&manifest.library_path));
281 if let Some(parent) = icd_file.parent() { candidates.push(parent.join(&manifest.library_path)); }
282 }
283 for cand in &candidates {
284 let can = fs::canonicalize(cand).unwrap_or(cand.clone());
285 if let Ok(icd) = load_icd(&can) {
286 let arc = Arc::new(icd);
287 out.push(arc);
288 break;
289 }
290 }
291 }
292 }
293
294 if prefer_hardware {
295 let any_hw = out.iter().any(|icd| {
296 let s = icd.library_path.to_string_lossy();
297 !(s.contains("lvp") || s.contains("swrast") || s.contains("llvmpipe"))
298 });
299 if any_hw {
300 out.retain(|icd| {
301 let s = icd.library_path.to_string_lossy();
302 !(s.contains("lvp") || s.contains("swrast") || s.contains("llvmpipe"))
303 });
304 }
305 }
306 out
307}
308
309pub fn discover_icds() -> Vec<PathBuf> {
311 let mut icd_files = Vec::new();
312 let mut env_icds = Vec::new();
313
314 if let Ok(icd_filenames) = env::var("VK_ICD_FILENAMES") {
316 let separator = if cfg!(windows) { ';' } else { ':' };
317 for path in icd_filenames.split(separator) {
318 let raw = PathBuf::from(path);
319 let can = fs::canonicalize(&raw).unwrap_or(raw);
320 if can.exists() {
321 env_icds.push(can.clone());
322 icd_files.push(can);
323 } else {
324 warn!("VK_ICD_FILENAMES contains non-existent path: {}", path);
325 }
326 }
327 if !env_icds.is_empty() {
328 info!("Found {} ICD files from VK_ICD_FILENAMES (will be prioritized)", env_icds.len());
329 }
330 }
331
332 let search_paths = get_icd_search_paths();
334 for search_path in &search_paths {
335 if let Ok(entries) = fs::read_dir(search_path) {
336 let mut path_count = 0;
337 for entry in entries.flatten() {
338 let path = entry.path();
339 if path.extension().and_then(|s| s.to_str()) == Some("json") {
340 if !env_icds.contains(&path) {
342 let can = fs::canonicalize(&path).unwrap_or(path);
343 log::debug!("Discovered ICD candidate: {}", can.display());
344 icd_files.push(can);
345 path_count += 1;
346 }
347 }
348 }
349 if path_count > 0 {
350 info!("Found {} additional ICD manifest files in {}", path_count, search_path.display());
351 }
352 }
353 }
354
355 if icd_files.is_empty() {
356 warn!("No ICD manifest files found in any search paths: {:#?}", search_paths);
357 }
358
359 icd_files
360}
361
362fn parse_icd_manifest(path: &Path) -> Option<ICDManifest> {
364 let content = fs::read_to_string(path).ok()?;
365
366 match serde_json::from_str::<ICDManifestRoot>(&content) {
368 Ok(manifest_root) => {
369 if manifest_root.icd.library_path.is_empty() {
370 warn!("ICD manifest has empty library_path: {}", path.display());
371 return None;
372 }
373 debug!("Successfully parsed ICD manifest: {} -> {}", path.display(), manifest_root.icd.library_path);
374 Some(manifest_root.icd)
375 }
376 Err(e) => {
377 warn!("Failed to parse ICD manifest {}: {}", path.display(), e);
378 None
379 }
380 }
381}
382
383fn parse_api_version(version: &str) -> Option<u32> {
385 let mut parts = version.split('.');
386 let major = parts.next()?.parse::<u32>().ok()?;
387 let minor = parts.next().and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
388 let patch = parts.next().and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
389 Some(VK_MAKE_VERSION(major, minor, patch))
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395
396 #[test]
397 fn test_parse_api_version() {
398 assert_eq!(parse_api_version("1.2.176"), Some(VK_MAKE_VERSION(1,2,176)));
399 assert_eq!(parse_api_version("1.3"), Some(VK_MAKE_VERSION(1,3,0)));
400 assert_eq!(parse_api_version("2"), Some(VK_MAKE_VERSION(2,0,0)));
401 assert_eq!(parse_api_version(""), None);
402 assert_eq!(parse_api_version("a.b.c"), None);
403 }
404
405 #[test]
406 fn test_aggregated_mode_default_off() {
407 std::env::remove_var("KRONOS_AGGREGATE_ICD");
409 assert!(!aggregated_mode_enabled());
410 }
411}
412
413pub fn available_icds() -> Vec<IcdInfo> {
415 let mut out = Vec::new();
416 let icd_files = discover_icds();
417
418 for icd_file in &icd_files {
419 if let Some(manifest) = parse_icd_manifest(icd_file) {
420 let mut candidates: Vec<PathBuf> = Vec::new();
422 if Path::new(&manifest.library_path).is_absolute() {
423 candidates.push(PathBuf::from(&manifest.library_path));
424 } else {
425 candidates.push(PathBuf::from(&manifest.library_path));
426 if let Some(parent) = icd_file.parent() {
427 candidates.push(parent.join(&manifest.library_path));
428 }
429 }
430
431 for cand in &candidates {
433 if let Ok(icd) = load_icd(cand) {
434 let path_str = icd.library_path.to_string_lossy();
435 let is_software = path_str.contains("lvp") || path_str.contains("swrast") || path_str.contains("llvmpipe");
436 let api_version = manifest
437 .api_version
438 .as_deref()
439 .and_then(parse_api_version)
440 .unwrap_or(icd.api_version);
441
442 out.push(IcdInfo {
443 library_path: icd.library_path,
444 manifest_path: Some(icd_file.clone()),
445 api_version,
446 is_software,
447 });
448 break; }
450 }
451 }
452 }
453
454 out
455}
456
457#[derive(Debug, Clone)]
459enum IcdPreference {
460 Path(PathBuf),
461 Index(usize),
462}
463
464lazy_static::lazy_static! {
465 static ref PREFERRED_ICD: Mutex<Option<IcdPreference>> = Mutex::new(None);
466}
467
468pub fn set_preferred_icd_path<P: Into<PathBuf>>(path: P) {
469 if let Ok(mut pref) = PREFERRED_ICD.lock() {
470 *pref = Some(IcdPreference::Path(path.into()));
471 }
472}
473
474pub fn set_preferred_icd_index(index: usize) {
475 if let Ok(mut pref) = PREFERRED_ICD.lock() {
476 *pref = Some(IcdPreference::Index(index));
477 }
478}
479
480pub fn clear_preferred_icd() {
481 if let Ok(mut pref) = PREFERRED_ICD.lock() {
482 *pref = None;
483 }
484}
485
486pub fn selected_icd_info() -> Option<IcdInfo> {
488 let icd = get_icd()?;
489 let path = icd.library_path.clone();
490 let path_str = path.to_string_lossy();
491 let is_software = path_str.contains("lvp") || path_str.contains("swrast") || path_str.contains("llvmpipe");
492 Some(IcdInfo {
493 library_path: path,
494 manifest_path: None,
495 api_version: icd.api_version,
496 is_software,
497 })
498}
499
500fn upgrade_icd(w: &Weak<LoadedICD>) -> Option<Arc<LoadedICD>> { w.upgrade() }
503
504pub fn register_instance_icd(instance: VkInstance, icd: &Arc<LoadedICD>) {
505 let _ = REG_INSTANCES.lock().map(|mut m| { m.insert(instance.as_raw(), Arc::downgrade(icd)); });
506}
507pub fn register_physical_device_icd(phys: VkPhysicalDevice, icd: &Arc<LoadedICD>) {
508 let _ = REG_PHYS_DEVS.lock().map(|mut m| { m.insert(phys.as_raw(), Arc::downgrade(icd)); });
509}
510pub fn register_device_icd(device: VkDevice, icd: &Arc<LoadedICD>) {
511 let _ = REG_DEVICES.lock().map(|mut m| { m.insert(device.as_raw(), Arc::downgrade(icd)); });
512}
513pub fn register_queue_icd(queue: VkQueue, icd: &Arc<LoadedICD>) {
514 let _ = REG_QUEUES.lock().map(|mut m| { m.insert(queue.as_raw(), Arc::downgrade(icd)); });
515}
516pub fn register_command_pool_icd(pool: VkCommandPool, icd: &Arc<LoadedICD>) {
517 let _ = REG_CMD_POOLS.lock().map(|mut m| { m.insert(pool.as_raw(), Arc::downgrade(icd)); });
518}
519pub fn register_command_buffer_icd(cb: VkCommandBuffer, icd: &Arc<LoadedICD>) {
520 let _ = REG_CMD_BUFFERS.lock().map(|mut m| { m.insert(cb.as_raw(), Arc::downgrade(icd)); });
521}
522
523pub fn unregister_instance(instance: VkInstance) { let _ = REG_INSTANCES.lock().map(|mut m| { m.remove(&instance.as_raw()); }); }
524pub fn unregister_physical_device(phys: VkPhysicalDevice) { let _ = REG_PHYS_DEVS.lock().map(|mut m| { m.remove(&phys.as_raw()); }); }
525pub fn unregister_device(device: VkDevice) { let _ = REG_DEVICES.lock().map(|mut m| { m.remove(&device.as_raw()); }); }
526pub fn unregister_queue(queue: VkQueue) { let _ = REG_QUEUES.lock().map(|mut m| { m.remove(&queue.as_raw()); }); }
527pub fn unregister_command_pool(pool: VkCommandPool) { let _ = REG_CMD_POOLS.lock().map(|mut m| { m.remove(&pool.as_raw()); }); }
528pub fn unregister_command_buffer(cb: VkCommandBuffer) { let _ = REG_CMD_BUFFERS.lock().map(|mut m| { m.remove(&cb.as_raw()); }); }
529
530pub fn icd_for_instance(instance: VkInstance) -> Option<Arc<LoadedICD>> {
531 REG_INSTANCES.lock().ok()?.get(&instance.as_raw()).and_then(upgrade_icd)
532 .or_else(|| get_icd())
533}
534pub fn icd_for_physical_device(phys: VkPhysicalDevice) -> Option<Arc<LoadedICD>> {
535 REG_PHYS_DEVS.lock().ok()?.get(&phys.as_raw()).and_then(upgrade_icd)
536 .or_else(|| icd_for_instance(VkInstance::NULL))
537}
538pub fn icd_for_device(device: VkDevice) -> Option<Arc<LoadedICD>> {
539 REG_DEVICES.lock().ok()?.get(&device.as_raw()).and_then(upgrade_icd)
540 .or_else(|| get_icd())
541}
542pub fn icd_for_queue(queue: VkQueue) -> Option<Arc<LoadedICD>> {
543 REG_QUEUES.lock().ok()?.get(&queue.as_raw()).and_then(upgrade_icd)
544 .or_else(|| get_icd())
545}
546pub fn icd_for_command_pool(pool: VkCommandPool) -> Option<Arc<LoadedICD>> {
547 REG_CMD_POOLS.lock().ok()?.get(&pool.as_raw()).and_then(upgrade_icd)
548 .or_else(|| get_icd())
549}
550pub fn icd_for_command_buffer(cb: VkCommandBuffer) -> Option<Arc<LoadedICD>> {
551 REG_CMD_BUFFERS.lock().ok()?.get(&cb.as_raw()).and_then(upgrade_icd)
552 .or_else(|| get_icd())
553}
554
555fn is_trusted_library(path: &Path) -> bool {
557 if env::var("KRONOS_ALLOW_UNTRUSTED_LIBS").map(|v| v == "1").unwrap_or(false) {
558 return true;
559 }
560 #[cfg(target_os = "linux")]
561 {
562 const TRUSTED_PREFIXES: &[&str] = &[
563 "/usr/lib",
564 "/usr/lib64",
565 "/usr/local/lib",
566 "/lib",
567 "/lib64",
568 "/usr/lib/x86_64-linux-gnu",
569 ];
570 let p = path.to_string_lossy();
571 return TRUSTED_PREFIXES.iter().any(|prefix| p.starts_with(prefix));
572 }
573 #[cfg(target_os = "windows")]
574 {
575 let p = path.to_string_lossy().to_lowercase();
577 let sysroot = env::var("SYSTEMROOT").unwrap_or_else(|_| String::from("C:\\Windows"));
578 let sys32 = format!("{}\\System32", sysroot);
579 let sys32_l = sys32.to_lowercase();
580 let pf = env::var("PROGRAMFILES").unwrap_or_default().to_lowercase();
582 let pf86 = env::var("PROGRAMFILES(X86)").unwrap_or_default().to_lowercase();
583 let trusted = p.starts_with(&sys32_l)
584 || (p.starts_with(&pf) && p.contains("vulkan"))
585 || (!pf86.is_empty() && p.starts_with(&pf86) && p.contains("vulkan"));
586 return trusted;
587 }
588 #[cfg(not(any(target_os = "linux", target_os = "windows")))]
589 {
590 true
592 }
593}
594
595pub fn load_icd(library_path: &Path) -> Result<LoadedICD, IcdError> {
596 unsafe {
601 let canon = fs::canonicalize(library_path).unwrap_or_else(|_| library_path.to_path_buf());
603 let meta = fs::metadata(&canon)
604 .map_err(|_| IcdError::LibraryLoadFailed(format!("{} (metadata not found)", canon.display())))?;
605 if !meta.is_file() {
606 return Err(IcdError::LibraryLoadFailed(format!("{} is not a regular file", canon.display())));
607 }
608 if !is_trusted_library(&canon) {
609 return Err(IcdError::LibraryLoadFailed(format!(
610 "{} rejected by trust policy (set KRONOS_ALLOW_UNTRUSTED_LIBS=1 to override)",
611 canon.display()
612 )));
613 }
614
615 #[cfg(windows)]
617 let lib = {
618 libloading::Library::new(&canon)
619 .map_err(|e| IcdError::LibraryLoadFailed(format!("{}: {}", canon.display(), e)))?
620 };
621 #[cfg(not(windows))]
622 let (handle, lib) = {
623 let lib_cstr = CString::new(canon.as_os_str().as_bytes())?;
624 let handle = libc::dlopen(lib_cstr.as_ptr(), libc::RTLD_NOW | libc::RTLD_LOCAL);
625 if handle.is_null() {
626 let error = unsafe { CStr::from_ptr(libc::dlerror()) }.to_string_lossy().into_owned();
627 return Err(IcdError::LibraryLoadFailed(format!("{}: {}", library_path.display(), error)));
628 }
629 (handle, ())
630 };
631
632 #[cfg(windows)]
634 let get_proc = unsafe {
635 let sym: libloading::Symbol<unsafe extern "C" fn(VkInstance, *const c_char) -> PFN_vkVoidFunction> =
636 lib.get(b"vk_icdGetInstanceProcAddr\0")
637 .map_err(|e| IcdError::LibraryLoadFailed(format!("{}: missing vk_icdGetInstanceProcAddr ({})", canon.display(), e)))?;
638 Some(*sym)
639 };
640 #[cfg(not(windows))]
641 let get_proc = {
642 let get_instance_proc_addr_name = CString::new("vk_icdGetInstanceProcAddr")?;
643 let ptr = unsafe { libc::dlsym(handle, get_instance_proc_addr_name.as_ptr()) };
644 if ptr.is_null() {
645 unsafe { libc::dlclose(handle); }
646 return Err(IcdError::MissingFunction("vk_icdGetInstanceProcAddr"));
647 }
648 let f: PFN_vkGetInstanceProcAddr = unsafe { std::mem::transmute(ptr) };
649 f
650 };
651 let vk_get_instance_proc_addr: PFN_vkGetInstanceProcAddr = get_proc;
652
653 let mut icd = LoadedICD {
655 library_path: canon,
656 handle: {
658 #[cfg(windows)]
659 {
660 let boxed = Box::new(lib);
661 Box::into_raw(boxed) as *mut c_void
662 }
663 #[cfg(not(windows))]
664 { handle as *mut c_void }
665 },
666 api_version: VK_API_VERSION_1_0,
667 vk_get_instance_proc_addr,
668 create_instance: None,
669 destroy_instance: None,
670 enumerate_physical_devices: None,
671 get_physical_device_properties: None,
672 get_physical_device_queue_family_properties: None,
673 get_physical_device_memory_properties: None,
674 create_device: None,
675 destroy_device: None,
676 get_device_proc_addr: None,
677 get_device_queue: None,
678 queue_submit: None,
679 queue_wait_idle: None,
680 device_wait_idle: None,
681 allocate_memory: None,
682 free_memory: None,
683 map_memory: None,
684 unmap_memory: None,
685 create_buffer: None,
686 destroy_buffer: None,
687 get_buffer_memory_requirements: None,
688 bind_buffer_memory: None,
689 create_descriptor_set_layout: None,
690 destroy_descriptor_set_layout: None,
691 create_descriptor_pool: None,
692 destroy_descriptor_pool: None,
693 reset_descriptor_pool: None,
694 allocate_descriptor_sets: None,
695 free_descriptor_sets: None,
696 update_descriptor_sets: None,
697 create_pipeline_layout: None,
698 destroy_pipeline_layout: None,
699 create_compute_pipelines: None,
700 destroy_pipeline: None,
701 create_shader_module: None,
702 destroy_shader_module: None,
703 create_command_pool: None,
704 destroy_command_pool: None,
705 allocate_command_buffers: None,
706 free_command_buffers: None,
707 begin_command_buffer: None,
708 end_command_buffer: None,
709 cmd_bind_pipeline: None,
710 cmd_bind_descriptor_sets: None,
711 cmd_dispatch: None,
712 cmd_dispatch_indirect: None,
713 cmd_pipeline_barrier: None,
714 cmd_copy_buffer: None,
715 cmd_push_constants: None,
716 create_fence: None,
717 destroy_fence: None,
718 reset_fences: None,
719 get_fence_status: None,
720 wait_for_fences: None,
721 create_semaphore: None,
722 destroy_semaphore: None,
723 create_event: None,
724 destroy_event: None,
725 get_event_status: None,
726 set_event: None,
727 reset_event: None,
728 cmd_set_event: None,
729 cmd_reset_event: None,
730 cmd_wait_events: None,
731 wait_semaphores: None,
732 };
733
734 load_global_functions_inner(&mut icd)?;
736 Ok(icd)
737 }
738}
739
740unsafe fn load_global_functions_inner(icd: &mut LoadedICD) -> Result<(), IcdError> {
752 let get_proc_addr = icd.vk_get_instance_proc_addr
753 .ok_or(IcdError::MissingFunction("vkGetInstanceProcAddr not loaded"))?;
754
755 macro_rules! load_fn {
757 ($name:ident, $fn_name:expr) => {
758 let name = CString::new($fn_name)
760 .expect(concat!("Invalid function name: ", $fn_name));
761 if let Some(addr) = get_proc_addr(VkInstance::NULL, name.as_ptr()) {
762 icd.$name = std::mem::transmute(addr);
763 }
764 };
765 }
766
767 load_fn!(create_instance, "vkCreateInstance");
769
770 debug!("Loaded global functions - create_instance: {:?}",
771 icd.create_instance.is_some());
772
773 Ok(())
774}
775
776pub unsafe fn load_instance_functions_inner(icd: &mut LoadedICD, instance: VkInstance) -> Result<(), IcdError> {
789 let get_proc_addr = icd.vk_get_instance_proc_addr
790 .ok_or(IcdError::MissingFunction("vkGetInstanceProcAddr not loaded"))?;
791
792 macro_rules! load_fn {
793 ($name:ident, $fn_name:expr) => {
794 let name = CString::new($fn_name)
795 .expect(concat!("Invalid function name: ", $fn_name));
796 if let Some(addr) = get_proc_addr(instance, name.as_ptr()) {
797 icd.$name = std::mem::transmute(addr);
798 }
799 };
800 }
801
802 load_fn!(destroy_instance, "vkDestroyInstance");
804 load_fn!(enumerate_physical_devices, "vkEnumeratePhysicalDevices");
805 load_fn!(get_physical_device_properties, "vkGetPhysicalDeviceProperties");
806 load_fn!(get_physical_device_queue_family_properties, "vkGetPhysicalDeviceQueueFamilyProperties");
807 load_fn!(get_physical_device_memory_properties, "vkGetPhysicalDeviceMemoryProperties");
808 load_fn!(create_device, "vkCreateDevice");
809 load_fn!(get_device_proc_addr, "vkGetDeviceProcAddr");
810
811 debug!("Loaded instance functions - enumerate_physical_devices: {:?}",
812 icd.enumerate_physical_devices.is_some());
813
814 Ok(())
815}
816
817pub unsafe fn load_device_functions_inner(icd: &mut LoadedICD, device: VkDevice) -> Result<(), IcdError> {
831 let get_device_proc_fn = icd
833 .get_device_proc_addr
834 .ok_or(IcdError::MissingFunction("vkGetDeviceProcAddr not loaded"))?;
835
836 let get_proc_addr_helper = |name: *const c_char| -> PFN_vkVoidFunction {
838 get_device_proc_fn(device, name)
839 };
840
841 macro_rules! load_fn {
842 ($name:ident, $fn_name:expr) => {
843 let name = CString::new($fn_name)
844 .expect(concat!("Invalid function name: ", $fn_name));
845 if let Some(addr) = get_proc_addr_helper(name.as_ptr()) {
846 icd.$name = std::mem::transmute(addr);
847 }
848 };
849 }
850
851 load_fn!(destroy_device, "vkDestroyDevice");
853 load_fn!(get_device_queue, "vkGetDeviceQueue");
854 load_fn!(device_wait_idle, "vkDeviceWaitIdle");
855
856 load_fn!(queue_submit, "vkQueueSubmit");
858 load_fn!(queue_wait_idle, "vkQueueWaitIdle");
859
860 load_fn!(allocate_memory, "vkAllocateMemory");
862 load_fn!(free_memory, "vkFreeMemory");
863 load_fn!(map_memory, "vkMapMemory");
864 load_fn!(unmap_memory, "vkUnmapMemory");
865
866 load_fn!(create_buffer, "vkCreateBuffer");
868 load_fn!(destroy_buffer, "vkDestroyBuffer");
869 load_fn!(get_buffer_memory_requirements, "vkGetBufferMemoryRequirements");
870 load_fn!(bind_buffer_memory, "vkBindBufferMemory");
871
872 load_fn!(create_descriptor_set_layout, "vkCreateDescriptorSetLayout");
874 load_fn!(destroy_descriptor_set_layout, "vkDestroyDescriptorSetLayout");
875 load_fn!(create_descriptor_pool, "vkCreateDescriptorPool");
876 load_fn!(destroy_descriptor_pool, "vkDestroyDescriptorPool");
877 load_fn!(reset_descriptor_pool, "vkResetDescriptorPool");
878 load_fn!(allocate_descriptor_sets, "vkAllocateDescriptorSets");
879 load_fn!(free_descriptor_sets, "vkFreeDescriptorSets");
880 load_fn!(update_descriptor_sets, "vkUpdateDescriptorSets");
881
882 load_fn!(create_pipeline_layout, "vkCreatePipelineLayout");
883 load_fn!(destroy_pipeline_layout, "vkDestroyPipelineLayout");
884 load_fn!(create_compute_pipelines, "vkCreateComputePipelines");
885 load_fn!(destroy_pipeline, "vkDestroyPipeline");
886
887 load_fn!(create_shader_module, "vkCreateShaderModule");
888 load_fn!(destroy_shader_module, "vkDestroyShaderModule");
889
890 load_fn!(create_command_pool, "vkCreateCommandPool");
891 load_fn!(destroy_command_pool, "vkDestroyCommandPool");
892 load_fn!(allocate_command_buffers, "vkAllocateCommandBuffers");
893 load_fn!(free_command_buffers, "vkFreeCommandBuffers");
894 load_fn!(begin_command_buffer, "vkBeginCommandBuffer");
895 load_fn!(end_command_buffer, "vkEndCommandBuffer");
896
897 load_fn!(cmd_bind_pipeline, "vkCmdBindPipeline");
898 load_fn!(cmd_bind_descriptor_sets, "vkCmdBindDescriptorSets");
899 load_fn!(cmd_dispatch, "vkCmdDispatch");
900 load_fn!(cmd_dispatch_indirect, "vkCmdDispatchIndirect");
901 load_fn!(cmd_pipeline_barrier, "vkCmdPipelineBarrier");
902 load_fn!(cmd_copy_buffer, "vkCmdCopyBuffer");
903 load_fn!(cmd_push_constants, "vkCmdPushConstants");
904
905 load_fn!(create_fence, "vkCreateFence");
907 load_fn!(destroy_fence, "vkDestroyFence");
908 load_fn!(reset_fences, "vkResetFences");
909 load_fn!(get_fence_status, "vkGetFenceStatus");
910 load_fn!(wait_for_fences, "vkWaitForFences");
911
912 load_fn!(create_semaphore, "vkCreateSemaphore");
913 load_fn!(destroy_semaphore, "vkDestroySemaphore");
914
915 load_fn!(create_event, "vkCreateEvent");
916 load_fn!(destroy_event, "vkDestroyEvent");
917 load_fn!(get_event_status, "vkGetEventStatus");
918 load_fn!(set_event, "vkSetEvent");
919 load_fn!(reset_event, "vkResetEvent");
920
921 load_fn!(cmd_set_event, "vkCmdSetEvent");
922 load_fn!(cmd_reset_event, "vkCmdResetEvent");
923 load_fn!(cmd_wait_events, "vkCmdWaitEvents");
924
925 load_fn!(wait_semaphores, "vkWaitSemaphores");
927
928 Ok(())
929}
930
931pub fn initialize_icd_loader() -> Result<(), IcdError> {
933 info!("Initializing ICD loader...");
934 let icd_files = discover_icds();
935
936 if icd_files.is_empty() {
937 warn!("No ICD manifest files found");
938 return Err(IcdError::NoManifestsFound);
939 }
940
941 info!("Found {} ICD manifest files", icd_files.len());
942
943 let env_icd_count = if let Ok(icd_filenames) = env::var("VK_ICD_FILENAMES") {
945 let separator = if cfg!(windows) { ';' } else { ':' };
946 icd_filenames.split(separator).filter(|s| !s.is_empty()).count()
947 } else {
948 0
949 };
950
951 let mut loaded_icds = Vec::new();
953
954 for (idx, icd_file) in icd_files.iter().enumerate() {
956 if let Some(manifest) = parse_icd_manifest(&icd_file) {
957 let mut candidates: Vec<PathBuf> = Vec::new();
961 if Path::new(&manifest.library_path).is_absolute() {
962 candidates.push(PathBuf::from(&manifest.library_path));
963 } else {
964 candidates.push(PathBuf::from(&manifest.library_path));
966 if let Some(parent) = icd_file.parent() {
968 candidates.push(parent.join(&manifest.library_path));
969 }
970 }
971
972 let mut loaded_ok: Option<LoadedICD> = None;
973 for cand in &candidates {
974 let can = fs::canonicalize(cand).unwrap_or(cand.clone());
976 log::info!("Attempting to load ICD library: {} (from {})", can.display(), icd_file.display());
977 match load_icd(&can) {
978 Ok(icd) => {
979 loaded_ok = Some(icd);
980 break;
981 }
982 Err(e) => {
983 warn!("Failed to load candidate {}: {}", can.display(), e);
984 }
985 }
986 }
987
988 if let Some(icd) = loaded_ok {
989 let path_str = icd.library_path.to_string_lossy();
991 let is_software = path_str.contains("lvp") ||
992 path_str.contains("swrast") ||
993 path_str.contains("llvmpipe");
994
995 let is_env_priority = idx < env_icd_count;
997
998 let icd_type = if is_software { "software" } else { "hardware" };
999 let priority_str = if is_env_priority { " (VK_ICD_FILENAMES priority)" } else { "" };
1000 info!("Successfully loaded {} Vulkan ICD: {}{}", icd_type, icd.library_path.display(), priority_str);
1001
1002 loaded_icds.push((icd, is_software, is_env_priority));
1003 } else {
1004 warn!("Failed to load ICD from any candidate for manifest {}", icd_file.display());
1005 }
1006 }
1007 }
1008
1009 if loaded_icds.is_empty() {
1010 return Err(IcdError::InvalidManifest("Failed to load any Vulkan ICD".to_string()));
1011 }
1012
1013 let prefer_hardware = env::var("KRONOS_PREFER_HARDWARE").map(|v| v != "0").unwrap_or(true);
1015 if prefer_hardware {
1016 let any_hw = loaded_icds.iter().any(|(_, is_sw, _)| !*is_sw);
1017 if any_hw {
1018 loaded_icds.retain(|(_, is_sw, _)| !*is_sw);
1019 info!("Hardware ICDs available; software ICDs will be ignored (set KRONOS_PREFER_HARDWARE=0 to disable)");
1020 }
1021 }
1022
1023 loaded_icds.sort_by_key(|(_, is_software, is_env_priority)| {
1025 (!is_env_priority, *is_software)
1026 });
1027
1028 info!("Available ICDs: {} hardware, {} software",
1030 loaded_icds.iter().filter(|(_, is_sw, _)| !is_sw).count(),
1031 loaded_icds.iter().filter(|(_, is_sw, _)| *is_sw).count());
1032
1033 let mut all_icds_vec = Vec::new();
1035 for (icd, _, _) in &loaded_icds {
1036 all_icds_vec.push(Arc::new(icd.clone()));
1037 }
1038 *ALL_ICDS.lock()? = all_icds_vec;
1039
1040 let preferred = PREFERRED_ICD.lock().ok().and_then(|p| p.clone());
1042 let (best_icd, is_software, is_env_priority) = if let Some(pref) = preferred {
1043 match pref {
1044 IcdPreference::Path(want) => {
1045 if let Some((idx, _)) = loaded_icds.iter().enumerate().find(|(_, (icd, _, _))| icd.library_path == want) {
1046 loaded_icds.into_iter().nth(idx).unwrap()
1047 } else {
1048 warn!("Preferred ICD path not found: {} — falling back to default selection", want.display());
1049 loaded_icds.into_iter().next().unwrap()
1050 }
1051 }
1052 IcdPreference::Index(i) => {
1053 if i < loaded_icds.len() {
1054 loaded_icds.into_iter().nth(i).unwrap()
1055 } else {
1056 warn!("Preferred ICD index {} out of range ({}); falling back to default", i, loaded_icds.len());
1057 loaded_icds.into_iter().next().unwrap()
1058 }
1059 }
1060 }
1061 } else {
1062 loaded_icds.into_iter().next().unwrap()
1064 };
1065
1066 if is_env_priority {
1067 info!("Using ICD specified by VK_ICD_FILENAMES: {}", best_icd.library_path.display());
1068 } else if is_software {
1069 warn!("Using software renderer - no hardware Vulkan drivers found");
1070 info!("To use hardware drivers, ensure they are installed and ICD files are in /usr/share/vulkan/icd.d/");
1071 } else {
1072 info!("Selected hardware Vulkan driver: {}", best_icd.library_path.display());
1073 }
1074
1075 if aggregated_mode_enabled() {
1077 let icd_count = ALL_ICDS.lock()?.len();
1078 info!("Aggregated mode enabled: {} ICDs available for multi-GPU support", icd_count);
1079 info!("Using {} as fallback ICD", best_icd.library_path.display());
1080 }
1081
1082 *ICD_LOADER.lock()? = Some(Arc::new(best_icd));
1083 Ok(())
1084}
1085
1086pub fn get_icd() -> Option<Arc<LoadedICD>> {
1088 ICD_LOADER.lock().ok()?.as_ref().cloned()
1089}
1090
1091fn replace_icd<F>(mutator: F) -> Result<(), IcdError>
1093where
1094 F: FnOnce(&mut LoadedICD) -> Result<(), IcdError>,
1095{
1096 let mut guard = ICD_LOADER.lock()?;
1097 let current = guard.as_ref().ok_or(IcdError::NoIcdLoaded)?;
1098 let mut updated = (**current).clone();
1099 mutator(&mut updated)?;
1100 *guard = Some(Arc::new(updated));
1101 Ok(())
1102}
1103
1104pub unsafe fn update_instance_functions(instance: VkInstance) -> Result<(), IcdError> {
1106 replace_icd(|icd| load_instance_functions_inner(icd, instance))
1107}
1108
1109pub unsafe fn update_device_functions(device: VkDevice) -> Result<(), IcdError> {
1111 replace_icd(|icd| load_device_functions_inner(icd, device))
1112}
1113
1114pub unsafe fn load_instance_functions_for_icd(icd: &mut LoadedICD, instance: VkInstance) -> Result<(), IcdError> {
1116 load_instance_functions_inner(icd, instance)
1117}