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 mut out = Vec::new();
261 let icd_files = discover_icds();
262 if icd_files.is_empty() { return out; }
263
264 let prefer_hardware = env::var("KRONOS_PREFER_HARDWARE").map(|v| v != "0").unwrap_or(true);
265
266 for icd_file in &icd_files {
267 if let Some(manifest) = parse_icd_manifest(icd_file) {
268 let mut candidates: Vec<PathBuf> = Vec::new();
269 if Path::new(&manifest.library_path).is_absolute() {
270 candidates.push(PathBuf::from(&manifest.library_path));
271 } else {
272 candidates.push(PathBuf::from(&manifest.library_path));
273 if let Some(parent) = icd_file.parent() { candidates.push(parent.join(&manifest.library_path)); }
274 }
275 for cand in &candidates {
276 let can = fs::canonicalize(cand).unwrap_or(cand.clone());
277 if let Ok(icd) = load_icd(&can) {
278 let arc = Arc::new(icd);
279 out.push(arc);
280 break;
281 }
282 }
283 }
284 }
285
286 if prefer_hardware {
287 let any_hw = out.iter().any(|icd| {
288 let s = icd.library_path.to_string_lossy();
289 !(s.contains("lvp") || s.contains("swrast") || s.contains("llvmpipe"))
290 });
291 if any_hw {
292 out.retain(|icd| {
293 let s = icd.library_path.to_string_lossy();
294 !(s.contains("lvp") || s.contains("swrast") || s.contains("llvmpipe"))
295 });
296 }
297 }
298 out
299}
300
301pub fn discover_icds() -> Vec<PathBuf> {
303 let mut icd_files = Vec::new();
304 let mut env_icds = Vec::new();
305
306 if let Ok(icd_filenames) = env::var("VK_ICD_FILENAMES") {
308 let separator = if cfg!(windows) { ';' } else { ':' };
309 for path in icd_filenames.split(separator) {
310 let raw = PathBuf::from(path);
311 let can = fs::canonicalize(&raw).unwrap_or(raw);
312 if can.exists() {
313 env_icds.push(can.clone());
314 icd_files.push(can);
315 } else {
316 warn!("VK_ICD_FILENAMES contains non-existent path: {}", path);
317 }
318 }
319 if !env_icds.is_empty() {
320 info!("Found {} ICD files from VK_ICD_FILENAMES (will be prioritized)", env_icds.len());
321 }
322 }
323
324 let search_paths = get_icd_search_paths();
326 for search_path in &search_paths {
327 if let Ok(entries) = fs::read_dir(search_path) {
328 let mut path_count = 0;
329 for entry in entries.flatten() {
330 let path = entry.path();
331 if path.extension().and_then(|s| s.to_str()) == Some("json") {
332 if !env_icds.contains(&path) {
334 let can = fs::canonicalize(&path).unwrap_or(path);
335 log::debug!("Discovered ICD candidate: {}", can.display());
336 icd_files.push(can);
337 path_count += 1;
338 }
339 }
340 }
341 if path_count > 0 {
342 info!("Found {} additional ICD manifest files in {}", path_count, search_path.display());
343 }
344 }
345 }
346
347 if icd_files.is_empty() {
348 warn!("No ICD manifest files found in any search paths: {:#?}", search_paths);
349 }
350
351 icd_files
352}
353
354fn parse_icd_manifest(path: &Path) -> Option<ICDManifest> {
356 let content = fs::read_to_string(path).ok()?;
357
358 match serde_json::from_str::<ICDManifestRoot>(&content) {
360 Ok(manifest_root) => {
361 if manifest_root.icd.library_path.is_empty() {
362 warn!("ICD manifest has empty library_path: {}", path.display());
363 return None;
364 }
365 debug!("Successfully parsed ICD manifest: {} -> {}", path.display(), manifest_root.icd.library_path);
366 Some(manifest_root.icd)
367 }
368 Err(e) => {
369 warn!("Failed to parse ICD manifest {}: {}", path.display(), e);
370 None
371 }
372 }
373}
374
375fn parse_api_version(version: &str) -> Option<u32> {
377 let mut parts = version.split('.');
378 let major = parts.next()?.parse::<u32>().ok()?;
379 let minor = parts.next().and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
380 let patch = parts.next().and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
381 Some(VK_MAKE_VERSION(major, minor, patch))
382}
383
384#[cfg(test)]
385mod tests {
386 use super::*;
387
388 #[test]
389 fn test_parse_api_version() {
390 assert_eq!(parse_api_version("1.2.176"), Some(VK_MAKE_VERSION(1,2,176)));
391 assert_eq!(parse_api_version("1.3"), Some(VK_MAKE_VERSION(1,3,0)));
392 assert_eq!(parse_api_version("2"), Some(VK_MAKE_VERSION(2,0,0)));
393 assert_eq!(parse_api_version(""), None);
394 assert_eq!(parse_api_version("a.b.c"), None);
395 }
396
397 #[test]
398 fn test_aggregated_mode_default_off() {
399 std::env::remove_var("KRONOS_AGGREGATE_ICD");
401 assert!(!aggregated_mode_enabled());
402 }
403}
404
405pub fn available_icds() -> Vec<IcdInfo> {
407 let mut out = Vec::new();
408 let icd_files = discover_icds();
409
410 for icd_file in &icd_files {
411 if let Some(manifest) = parse_icd_manifest(icd_file) {
412 let mut candidates: Vec<PathBuf> = Vec::new();
414 if Path::new(&manifest.library_path).is_absolute() {
415 candidates.push(PathBuf::from(&manifest.library_path));
416 } else {
417 candidates.push(PathBuf::from(&manifest.library_path));
418 if let Some(parent) = icd_file.parent() {
419 candidates.push(parent.join(&manifest.library_path));
420 }
421 }
422
423 for cand in &candidates {
425 if let Ok(icd) = load_icd(cand) {
426 let path_str = icd.library_path.to_string_lossy();
427 let is_software = path_str.contains("lvp") || path_str.contains("swrast") || path_str.contains("llvmpipe");
428 let api_version = manifest
429 .api_version
430 .as_deref()
431 .and_then(parse_api_version)
432 .unwrap_or(icd.api_version);
433
434 out.push(IcdInfo {
435 library_path: icd.library_path,
436 manifest_path: Some(icd_file.clone()),
437 api_version,
438 is_software,
439 });
440 break; }
442 }
443 }
444 }
445
446 out
447}
448
449#[derive(Debug, Clone)]
451enum IcdPreference {
452 Path(PathBuf),
453 Index(usize),
454}
455
456lazy_static::lazy_static! {
457 static ref PREFERRED_ICD: Mutex<Option<IcdPreference>> = Mutex::new(None);
458}
459
460pub fn set_preferred_icd_path<P: Into<PathBuf>>(path: P) {
461 if let Ok(mut pref) = PREFERRED_ICD.lock() {
462 *pref = Some(IcdPreference::Path(path.into()));
463 }
464}
465
466pub fn set_preferred_icd_index(index: usize) {
467 if let Ok(mut pref) = PREFERRED_ICD.lock() {
468 *pref = Some(IcdPreference::Index(index));
469 }
470}
471
472pub fn clear_preferred_icd() {
473 if let Ok(mut pref) = PREFERRED_ICD.lock() {
474 *pref = None;
475 }
476}
477
478pub fn selected_icd_info() -> Option<IcdInfo> {
480 let icd = get_icd()?;
481 let path = icd.library_path.clone();
482 let path_str = path.to_string_lossy();
483 let is_software = path_str.contains("lvp") || path_str.contains("swrast") || path_str.contains("llvmpipe");
484 Some(IcdInfo {
485 library_path: path,
486 manifest_path: None,
487 api_version: icd.api_version,
488 is_software,
489 })
490}
491
492fn upgrade_icd(w: &Weak<LoadedICD>) -> Option<Arc<LoadedICD>> { w.upgrade() }
495
496pub fn register_instance_icd(instance: VkInstance, icd: &Arc<LoadedICD>) {
497 let _ = REG_INSTANCES.lock().map(|mut m| { m.insert(instance.as_raw(), Arc::downgrade(icd)); });
498}
499pub fn register_physical_device_icd(phys: VkPhysicalDevice, icd: &Arc<LoadedICD>) {
500 let _ = REG_PHYS_DEVS.lock().map(|mut m| { m.insert(phys.as_raw(), Arc::downgrade(icd)); });
501}
502pub fn register_device_icd(device: VkDevice, icd: &Arc<LoadedICD>) {
503 let _ = REG_DEVICES.lock().map(|mut m| { m.insert(device.as_raw(), Arc::downgrade(icd)); });
504}
505pub fn register_queue_icd(queue: VkQueue, icd: &Arc<LoadedICD>) {
506 let _ = REG_QUEUES.lock().map(|mut m| { m.insert(queue.as_raw(), Arc::downgrade(icd)); });
507}
508pub fn register_command_pool_icd(pool: VkCommandPool, icd: &Arc<LoadedICD>) {
509 let _ = REG_CMD_POOLS.lock().map(|mut m| { m.insert(pool.as_raw(), Arc::downgrade(icd)); });
510}
511pub fn register_command_buffer_icd(cb: VkCommandBuffer, icd: &Arc<LoadedICD>) {
512 let _ = REG_CMD_BUFFERS.lock().map(|mut m| { m.insert(cb.as_raw(), Arc::downgrade(icd)); });
513}
514
515pub fn unregister_instance(instance: VkInstance) { let _ = REG_INSTANCES.lock().map(|mut m| { m.remove(&instance.as_raw()); }); }
516pub fn unregister_physical_device(phys: VkPhysicalDevice) { let _ = REG_PHYS_DEVS.lock().map(|mut m| { m.remove(&phys.as_raw()); }); }
517pub fn unregister_device(device: VkDevice) { let _ = REG_DEVICES.lock().map(|mut m| { m.remove(&device.as_raw()); }); }
518pub fn unregister_queue(queue: VkQueue) { let _ = REG_QUEUES.lock().map(|mut m| { m.remove(&queue.as_raw()); }); }
519pub fn unregister_command_pool(pool: VkCommandPool) { let _ = REG_CMD_POOLS.lock().map(|mut m| { m.remove(&pool.as_raw()); }); }
520pub fn unregister_command_buffer(cb: VkCommandBuffer) { let _ = REG_CMD_BUFFERS.lock().map(|mut m| { m.remove(&cb.as_raw()); }); }
521
522pub fn icd_for_instance(instance: VkInstance) -> Option<Arc<LoadedICD>> {
523 REG_INSTANCES.lock().ok()?.get(&instance.as_raw()).and_then(upgrade_icd)
524 .or_else(|| get_icd())
525}
526pub fn icd_for_physical_device(phys: VkPhysicalDevice) -> Option<Arc<LoadedICD>> {
527 REG_PHYS_DEVS.lock().ok()?.get(&phys.as_raw()).and_then(upgrade_icd)
528 .or_else(|| icd_for_instance(VkInstance::NULL))
529}
530pub fn icd_for_device(device: VkDevice) -> Option<Arc<LoadedICD>> {
531 REG_DEVICES.lock().ok()?.get(&device.as_raw()).and_then(upgrade_icd)
532 .or_else(|| get_icd())
533}
534pub fn icd_for_queue(queue: VkQueue) -> Option<Arc<LoadedICD>> {
535 REG_QUEUES.lock().ok()?.get(&queue.as_raw()).and_then(upgrade_icd)
536 .or_else(|| get_icd())
537}
538pub fn icd_for_command_pool(pool: VkCommandPool) -> Option<Arc<LoadedICD>> {
539 REG_CMD_POOLS.lock().ok()?.get(&pool.as_raw()).and_then(upgrade_icd)
540 .or_else(|| get_icd())
541}
542pub fn icd_for_command_buffer(cb: VkCommandBuffer) -> Option<Arc<LoadedICD>> {
543 REG_CMD_BUFFERS.lock().ok()?.get(&cb.as_raw()).and_then(upgrade_icd)
544 .or_else(|| get_icd())
545}
546
547fn is_trusted_library(path: &Path) -> bool {
549 if env::var("KRONOS_ALLOW_UNTRUSTED_LIBS").map(|v| v == "1").unwrap_or(false) {
550 return true;
551 }
552 #[cfg(target_os = "linux")]
553 {
554 const TRUSTED_PREFIXES: &[&str] = &[
555 "/usr/lib",
556 "/usr/lib64",
557 "/usr/local/lib",
558 "/lib",
559 "/lib64",
560 "/usr/lib/x86_64-linux-gnu",
561 ];
562 let p = path.to_string_lossy();
563 return TRUSTED_PREFIXES.iter().any(|prefix| p.starts_with(prefix));
564 }
565 #[cfg(target_os = "windows")]
566 {
567 let p = path.to_string_lossy().to_lowercase();
569 let sysroot = env::var("SYSTEMROOT").unwrap_or_else(|_| String::from("C:\\Windows"));
570 let sys32 = format!("{}\\System32", sysroot);
571 let sys32_l = sys32.to_lowercase();
572 let pf = env::var("PROGRAMFILES").unwrap_or_default().to_lowercase();
574 let pf86 = env::var("PROGRAMFILES(X86)").unwrap_or_default().to_lowercase();
575 let trusted = p.starts_with(&sys32_l)
576 || (p.starts_with(&pf) && p.contains("vulkan"))
577 || (!pf86.is_empty() && p.starts_with(&pf86) && p.contains("vulkan"));
578 return trusted;
579 }
580 #[cfg(not(any(target_os = "linux", target_os = "windows")))]
581 {
582 true
584 }
585}
586
587pub fn load_icd(library_path: &Path) -> Result<LoadedICD, IcdError> {
588 unsafe {
593 let canon = fs::canonicalize(library_path).unwrap_or_else(|_| library_path.to_path_buf());
595 let meta = fs::metadata(&canon)
596 .map_err(|_| IcdError::LibraryLoadFailed(format!("{} (metadata not found)", canon.display())))?;
597 if !meta.is_file() {
598 return Err(IcdError::LibraryLoadFailed(format!("{} is not a regular file", canon.display())));
599 }
600 if !is_trusted_library(&canon) {
601 return Err(IcdError::LibraryLoadFailed(format!(
602 "{} rejected by trust policy (set KRONOS_ALLOW_UNTRUSTED_LIBS=1 to override)",
603 canon.display()
604 )));
605 }
606
607 #[cfg(windows)]
609 let lib = {
610 libloading::Library::new(&canon)
611 .map_err(|e| IcdError::LibraryLoadFailed(format!("{}: {}", canon.display(), e)))?
612 };
613 #[cfg(not(windows))]
614 let (handle, lib) = {
615 let lib_cstr = CString::new(canon.as_os_str().as_bytes())?;
616 let handle = libc::dlopen(lib_cstr.as_ptr(), libc::RTLD_NOW | libc::RTLD_LOCAL);
617 if handle.is_null() {
618 let error = unsafe { CStr::from_ptr(libc::dlerror()) }.to_string_lossy().into_owned();
619 return Err(IcdError::LibraryLoadFailed(format!("{}: {}", library_path.display(), error)));
620 }
621 (handle, ())
622 };
623
624 #[cfg(windows)]
626 let get_proc = unsafe {
627 let sym: libloading::Symbol<unsafe extern "C" fn(VkInstance, *const c_char) -> PFN_vkVoidFunction> =
628 lib.get(b"vk_icdGetInstanceProcAddr\0")
629 .map_err(|e| IcdError::LibraryLoadFailed(format!("{}: missing vk_icdGetInstanceProcAddr ({})", canon.display(), e)))?;
630 Some(*sym)
631 };
632 #[cfg(not(windows))]
633 let get_proc = {
634 let get_instance_proc_addr_name = CString::new("vk_icdGetInstanceProcAddr")?;
635 let ptr = unsafe { libc::dlsym(handle, get_instance_proc_addr_name.as_ptr()) };
636 if ptr.is_null() {
637 unsafe { libc::dlclose(handle); }
638 return Err(IcdError::MissingFunction("vk_icdGetInstanceProcAddr"));
639 }
640 let f: PFN_vkGetInstanceProcAddr = unsafe { std::mem::transmute(ptr) };
641 f
642 };
643 let vk_get_instance_proc_addr: PFN_vkGetInstanceProcAddr = get_proc;
644
645 let mut icd = LoadedICD {
647 library_path: canon,
648 handle: {
650 #[cfg(windows)]
651 {
652 let boxed = Box::new(lib);
653 Box::into_raw(boxed) as *mut c_void
654 }
655 #[cfg(not(windows))]
656 { handle as *mut c_void }
657 },
658 api_version: VK_API_VERSION_1_0,
659 vk_get_instance_proc_addr,
660 create_instance: None,
661 destroy_instance: None,
662 enumerate_physical_devices: None,
663 get_physical_device_properties: None,
664 get_physical_device_queue_family_properties: None,
665 get_physical_device_memory_properties: None,
666 create_device: None,
667 destroy_device: None,
668 get_device_proc_addr: None,
669 get_device_queue: None,
670 queue_submit: None,
671 queue_wait_idle: None,
672 device_wait_idle: None,
673 allocate_memory: None,
674 free_memory: None,
675 map_memory: None,
676 unmap_memory: None,
677 create_buffer: None,
678 destroy_buffer: None,
679 get_buffer_memory_requirements: None,
680 bind_buffer_memory: None,
681 create_descriptor_set_layout: None,
682 destroy_descriptor_set_layout: None,
683 create_descriptor_pool: None,
684 destroy_descriptor_pool: None,
685 reset_descriptor_pool: None,
686 allocate_descriptor_sets: None,
687 free_descriptor_sets: None,
688 update_descriptor_sets: None,
689 create_pipeline_layout: None,
690 destroy_pipeline_layout: None,
691 create_compute_pipelines: None,
692 destroy_pipeline: None,
693 create_shader_module: None,
694 destroy_shader_module: None,
695 create_command_pool: None,
696 destroy_command_pool: None,
697 allocate_command_buffers: None,
698 free_command_buffers: None,
699 begin_command_buffer: None,
700 end_command_buffer: None,
701 cmd_bind_pipeline: None,
702 cmd_bind_descriptor_sets: None,
703 cmd_dispatch: None,
704 cmd_dispatch_indirect: None,
705 cmd_pipeline_barrier: None,
706 cmd_copy_buffer: None,
707 cmd_push_constants: None,
708 create_fence: None,
709 destroy_fence: None,
710 reset_fences: None,
711 get_fence_status: None,
712 wait_for_fences: None,
713 create_semaphore: None,
714 destroy_semaphore: None,
715 create_event: None,
716 destroy_event: None,
717 get_event_status: None,
718 set_event: None,
719 reset_event: None,
720 cmd_set_event: None,
721 cmd_reset_event: None,
722 cmd_wait_events: None,
723 wait_semaphores: None,
724 };
725
726 load_global_functions_inner(&mut icd)?;
728 Ok(icd)
729 }
730}
731
732unsafe fn load_global_functions_inner(icd: &mut LoadedICD) -> Result<(), IcdError> {
744 let get_proc_addr = icd.vk_get_instance_proc_addr
745 .ok_or(IcdError::MissingFunction("vkGetInstanceProcAddr not loaded"))?;
746
747 macro_rules! load_fn {
749 ($name:ident, $fn_name:expr) => {
750 let name = CString::new($fn_name)
752 .expect(concat!("Invalid function name: ", $fn_name));
753 if let Some(addr) = get_proc_addr(VkInstance::NULL, name.as_ptr()) {
754 icd.$name = std::mem::transmute(addr);
755 }
756 };
757 }
758
759 load_fn!(create_instance, "vkCreateInstance");
761
762 debug!("Loaded global functions - create_instance: {:?}",
763 icd.create_instance.is_some());
764
765 Ok(())
766}
767
768pub unsafe fn load_instance_functions_inner(icd: &mut LoadedICD, instance: VkInstance) -> Result<(), IcdError> {
781 let get_proc_addr = icd.vk_get_instance_proc_addr
782 .ok_or(IcdError::MissingFunction("vkGetInstanceProcAddr not loaded"))?;
783
784 macro_rules! load_fn {
785 ($name:ident, $fn_name:expr) => {
786 let name = CString::new($fn_name)
787 .expect(concat!("Invalid function name: ", $fn_name));
788 if let Some(addr) = get_proc_addr(instance, name.as_ptr()) {
789 icd.$name = std::mem::transmute(addr);
790 }
791 };
792 }
793
794 load_fn!(destroy_instance, "vkDestroyInstance");
796 load_fn!(enumerate_physical_devices, "vkEnumeratePhysicalDevices");
797 load_fn!(get_physical_device_properties, "vkGetPhysicalDeviceProperties");
798 load_fn!(get_physical_device_queue_family_properties, "vkGetPhysicalDeviceQueueFamilyProperties");
799 load_fn!(get_physical_device_memory_properties, "vkGetPhysicalDeviceMemoryProperties");
800 load_fn!(create_device, "vkCreateDevice");
801 load_fn!(get_device_proc_addr, "vkGetDeviceProcAddr");
802
803 debug!("Loaded instance functions - enumerate_physical_devices: {:?}",
804 icd.enumerate_physical_devices.is_some());
805
806 Ok(())
807}
808
809pub unsafe fn load_device_functions_inner(icd: &mut LoadedICD, device: VkDevice) -> Result<(), IcdError> {
823 let get_device_proc_fn = icd
825 .get_device_proc_addr
826 .ok_or(IcdError::MissingFunction("vkGetDeviceProcAddr not loaded"))?;
827
828 let get_proc_addr_helper = |name: *const c_char| -> PFN_vkVoidFunction {
830 get_device_proc_fn(device, name)
831 };
832
833 macro_rules! load_fn {
834 ($name:ident, $fn_name:expr) => {
835 let name = CString::new($fn_name)
836 .expect(concat!("Invalid function name: ", $fn_name));
837 if let Some(addr) = get_proc_addr_helper(name.as_ptr()) {
838 icd.$name = std::mem::transmute(addr);
839 }
840 };
841 }
842
843 load_fn!(destroy_device, "vkDestroyDevice");
845 load_fn!(get_device_queue, "vkGetDeviceQueue");
846 load_fn!(device_wait_idle, "vkDeviceWaitIdle");
847
848 load_fn!(queue_submit, "vkQueueSubmit");
850 load_fn!(queue_wait_idle, "vkQueueWaitIdle");
851
852 load_fn!(allocate_memory, "vkAllocateMemory");
854 load_fn!(free_memory, "vkFreeMemory");
855 load_fn!(map_memory, "vkMapMemory");
856 load_fn!(unmap_memory, "vkUnmapMemory");
857
858 load_fn!(create_buffer, "vkCreateBuffer");
860 load_fn!(destroy_buffer, "vkDestroyBuffer");
861 load_fn!(get_buffer_memory_requirements, "vkGetBufferMemoryRequirements");
862 load_fn!(bind_buffer_memory, "vkBindBufferMemory");
863
864 load_fn!(create_descriptor_set_layout, "vkCreateDescriptorSetLayout");
866 load_fn!(destroy_descriptor_set_layout, "vkDestroyDescriptorSetLayout");
867 load_fn!(create_descriptor_pool, "vkCreateDescriptorPool");
868 load_fn!(destroy_descriptor_pool, "vkDestroyDescriptorPool");
869 load_fn!(reset_descriptor_pool, "vkResetDescriptorPool");
870 load_fn!(allocate_descriptor_sets, "vkAllocateDescriptorSets");
871 load_fn!(free_descriptor_sets, "vkFreeDescriptorSets");
872 load_fn!(update_descriptor_sets, "vkUpdateDescriptorSets");
873
874 load_fn!(create_pipeline_layout, "vkCreatePipelineLayout");
875 load_fn!(destroy_pipeline_layout, "vkDestroyPipelineLayout");
876 load_fn!(create_compute_pipelines, "vkCreateComputePipelines");
877 load_fn!(destroy_pipeline, "vkDestroyPipeline");
878
879 load_fn!(create_shader_module, "vkCreateShaderModule");
880 load_fn!(destroy_shader_module, "vkDestroyShaderModule");
881
882 load_fn!(create_command_pool, "vkCreateCommandPool");
883 load_fn!(destroy_command_pool, "vkDestroyCommandPool");
884 load_fn!(allocate_command_buffers, "vkAllocateCommandBuffers");
885 load_fn!(free_command_buffers, "vkFreeCommandBuffers");
886 load_fn!(begin_command_buffer, "vkBeginCommandBuffer");
887 load_fn!(end_command_buffer, "vkEndCommandBuffer");
888
889 load_fn!(cmd_bind_pipeline, "vkCmdBindPipeline");
890 load_fn!(cmd_bind_descriptor_sets, "vkCmdBindDescriptorSets");
891 load_fn!(cmd_dispatch, "vkCmdDispatch");
892 load_fn!(cmd_dispatch_indirect, "vkCmdDispatchIndirect");
893 load_fn!(cmd_pipeline_barrier, "vkCmdPipelineBarrier");
894 load_fn!(cmd_copy_buffer, "vkCmdCopyBuffer");
895 load_fn!(cmd_push_constants, "vkCmdPushConstants");
896
897 load_fn!(create_fence, "vkCreateFence");
899 load_fn!(destroy_fence, "vkDestroyFence");
900 load_fn!(reset_fences, "vkResetFences");
901 load_fn!(get_fence_status, "vkGetFenceStatus");
902 load_fn!(wait_for_fences, "vkWaitForFences");
903
904 load_fn!(create_semaphore, "vkCreateSemaphore");
905 load_fn!(destroy_semaphore, "vkDestroySemaphore");
906
907 load_fn!(create_event, "vkCreateEvent");
908 load_fn!(destroy_event, "vkDestroyEvent");
909 load_fn!(get_event_status, "vkGetEventStatus");
910 load_fn!(set_event, "vkSetEvent");
911 load_fn!(reset_event, "vkResetEvent");
912
913 load_fn!(cmd_set_event, "vkCmdSetEvent");
914 load_fn!(cmd_reset_event, "vkCmdResetEvent");
915 load_fn!(cmd_wait_events, "vkCmdWaitEvents");
916
917 load_fn!(wait_semaphores, "vkWaitSemaphores");
919
920 Ok(())
921}
922
923pub fn initialize_icd_loader() -> Result<(), IcdError> {
925 info!("Initializing ICD loader...");
926 let icd_files = discover_icds();
927
928 if icd_files.is_empty() {
929 warn!("No ICD manifest files found");
930 return Err(IcdError::NoManifestsFound);
931 }
932
933 info!("Found {} ICD manifest files", icd_files.len());
934
935 let env_icd_count = if let Ok(icd_filenames) = env::var("VK_ICD_FILENAMES") {
937 let separator = if cfg!(windows) { ';' } else { ':' };
938 icd_filenames.split(separator).filter(|s| !s.is_empty()).count()
939 } else {
940 0
941 };
942
943 let mut loaded_icds = Vec::new();
945
946 for (idx, icd_file) in icd_files.iter().enumerate() {
948 if let Some(manifest) = parse_icd_manifest(&icd_file) {
949 let mut candidates: Vec<PathBuf> = Vec::new();
953 if Path::new(&manifest.library_path).is_absolute() {
954 candidates.push(PathBuf::from(&manifest.library_path));
955 } else {
956 candidates.push(PathBuf::from(&manifest.library_path));
958 if let Some(parent) = icd_file.parent() {
960 candidates.push(parent.join(&manifest.library_path));
961 }
962 }
963
964 let mut loaded_ok: Option<LoadedICD> = None;
965 for cand in &candidates {
966 let can = fs::canonicalize(cand).unwrap_or(cand.clone());
968 log::info!("Attempting to load ICD library: {} (from {})", can.display(), icd_file.display());
969 match load_icd(&can) {
970 Ok(icd) => {
971 loaded_ok = Some(icd);
972 break;
973 }
974 Err(e) => {
975 warn!("Failed to load candidate {}: {}", can.display(), e);
976 }
977 }
978 }
979
980 if let Some(icd) = loaded_ok {
981 let path_str = icd.library_path.to_string_lossy();
983 let is_software = path_str.contains("lvp") ||
984 path_str.contains("swrast") ||
985 path_str.contains("llvmpipe");
986
987 let is_env_priority = idx < env_icd_count;
989
990 let icd_type = if is_software { "software" } else { "hardware" };
991 let priority_str = if is_env_priority { " (VK_ICD_FILENAMES priority)" } else { "" };
992 info!("Successfully loaded {} Vulkan ICD: {}{}", icd_type, icd.library_path.display(), priority_str);
993
994 loaded_icds.push((icd, is_software, is_env_priority));
995 } else {
996 warn!("Failed to load ICD from any candidate for manifest {}", icd_file.display());
997 }
998 }
999 }
1000
1001 if loaded_icds.is_empty() {
1002 return Err(IcdError::InvalidManifest("Failed to load any Vulkan ICD".to_string()));
1003 }
1004
1005 let prefer_hardware = env::var("KRONOS_PREFER_HARDWARE").map(|v| v != "0").unwrap_or(true);
1007 if prefer_hardware {
1008 let any_hw = loaded_icds.iter().any(|(_, is_sw, _)| !*is_sw);
1009 if any_hw {
1010 loaded_icds.retain(|(_, is_sw, _)| !*is_sw);
1011 info!("Hardware ICDs available; software ICDs will be ignored (set KRONOS_PREFER_HARDWARE=0 to disable)");
1012 }
1013 }
1014
1015 loaded_icds.sort_by_key(|(_, is_software, is_env_priority)| {
1017 (!is_env_priority, *is_software)
1018 });
1019
1020 info!("Available ICDs: {} hardware, {} software",
1022 loaded_icds.iter().filter(|(_, is_sw, _)| !is_sw).count(),
1023 loaded_icds.iter().filter(|(_, is_sw, _)| *is_sw).count());
1024
1025 let preferred = PREFERRED_ICD.lock().ok().and_then(|p| p.clone());
1027 let (best_icd, is_software, is_env_priority) = if let Some(pref) = preferred {
1028 match pref {
1029 IcdPreference::Path(want) => {
1030 if let Some((idx, _)) = loaded_icds.iter().enumerate().find(|(_, (icd, _, _))| icd.library_path == want) {
1031 loaded_icds.into_iter().nth(idx).unwrap()
1032 } else {
1033 warn!("Preferred ICD path not found: {} — falling back to default selection", want.display());
1034 loaded_icds.into_iter().next().unwrap()
1035 }
1036 }
1037 IcdPreference::Index(i) => {
1038 if i < loaded_icds.len() {
1039 loaded_icds.into_iter().nth(i).unwrap()
1040 } else {
1041 warn!("Preferred ICD index {} out of range ({}); falling back to default", i, loaded_icds.len());
1042 loaded_icds.into_iter().next().unwrap()
1043 }
1044 }
1045 }
1046 } else {
1047 loaded_icds.into_iter().next().unwrap()
1049 };
1050
1051 if is_env_priority {
1052 info!("Using ICD specified by VK_ICD_FILENAMES: {}", best_icd.library_path.display());
1053 } else if is_software {
1054 warn!("Using software renderer - no hardware Vulkan drivers found");
1055 info!("To use hardware drivers, ensure they are installed and ICD files are in /usr/share/vulkan/icd.d/");
1056 } else {
1057 info!("Selected hardware Vulkan driver: {}", best_icd.library_path.display());
1058 }
1059
1060 let mut all_vec = Vec::new();
1062 all_vec.push(Arc::new(best_icd.clone()));
1063 *ALL_ICDS.lock()? = all_vec;
1066
1067 *ICD_LOADER.lock()? = Some(Arc::new(best_icd));
1068 Ok(())
1069}
1070
1071pub fn get_icd() -> Option<Arc<LoadedICD>> {
1073 ICD_LOADER.lock().ok()?.as_ref().cloned()
1074}
1075
1076fn replace_icd<F>(mutator: F) -> Result<(), IcdError>
1078where
1079 F: FnOnce(&mut LoadedICD) -> Result<(), IcdError>,
1080{
1081 let mut guard = ICD_LOADER.lock()?;
1082 let current = guard.as_ref().ok_or(IcdError::NoIcdLoaded)?;
1083 let mut updated = (**current).clone();
1084 mutator(&mut updated)?;
1085 *guard = Some(Arc::new(updated));
1086 Ok(())
1087}
1088
1089pub unsafe fn update_instance_functions(instance: VkInstance) -> Result<(), IcdError> {
1091 replace_icd(|icd| load_instance_functions_inner(icd, instance))
1092}
1093
1094pub unsafe fn update_device_functions(device: VkDevice) -> Result<(), IcdError> {
1096 replace_icd(|icd| load_device_functions_inner(icd, device))
1097}