kronos_compute/implementation/
icd_loader.rs

1//! Vulkan ICD (Installable Client Driver) loader
2//! 
3//! Loads real Vulkan drivers and forwards compute calls
4
5use 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
20/// Get platform-specific ICD search paths
21fn get_icd_search_paths() -> Vec<PathBuf> {
22    let mut paths = Vec::new();
23    
24    // Check environment variable first (cross-platform override)
25    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    // Platform-specific default paths
33    #[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        // Windows registry paths - simplified for now
46        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        // Program Files paths
52        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        // User-specific paths
66        if let Ok(home) = env::var("HOME") {
67            paths.push(PathBuf::from(home).join(".local/share/vulkan/icd.d"));
68        }
69    }
70    
71    // Canonicalize where possible to mitigate traversal issues
72    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), // keep original if canonicalize fails
77        }
78    }
79    log::info!("ICD search paths: {:#?}", canon);
80    canon
81}
82
83/// Loaded ICD information
84#[derive(Clone)]
85pub struct LoadedICD {
86    pub library_path: PathBuf,
87    pub handle: *mut c_void,
88    pub api_version: u32,
89    
90    // Core function pointers
91    pub vk_get_instance_proc_addr: PFN_vkGetInstanceProcAddr,
92    
93    // Instance functions
94    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    // Device functions
102    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    // Queue functions
108    pub queue_submit: PFN_vkQueueSubmit,
109    pub queue_wait_idle: PFN_vkQueueWaitIdle,
110    pub device_wait_idle: PFN_vkDeviceWaitIdle,
111    
112    // Memory functions
113    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    // Buffer functions
119    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    // Descriptor functions
125    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    // Pipeline functions
135    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    // Shader functions
141    pub create_shader_module: PFN_vkCreateShaderModule,
142    pub destroy_shader_module: PFN_vkDestroyShaderModule,
143    
144    // Command buffer functions
145    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    // Sync functions
160    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    // Timeline semaphore functions
177    pub wait_semaphores: Option<unsafe extern "C" fn(VkDevice, *const VkSemaphoreWaitInfo, u64) -> VkResult>,
178}
179
180// SAFETY: LoadedICD is safe to send between threads because:
181// 1. The library handle is intentionally leaked (never closed)
182// 2. Function pointers are immutable once loaded
183// 3. PathBuf is already Send+Sync
184// 4. No mutable state is shared between threads
185unsafe impl Send for LoadedICD {}
186unsafe impl Sync for LoadedICD {}
187
188// NOTE: No Drop implementation - we intentionally leak the library handles
189// because function pointers from the library may still be in use elsewhere.
190// This is standard practice for dynamically loaded Vulkan ICDs.
191
192/// Public info about a loadable ICD
193#[derive(Debug, Clone)]
194pub struct IcdInfo {
195    pub library_path: PathBuf,
196    pub manifest_path: Option<PathBuf>,
197    pub api_version: u32,
198    pub is_software: bool,
199}
200
201/// ICD manifest root structure
202#[derive(Debug, Deserialize, Serialize)]
203struct ICDManifestRoot {
204    file_format_version: String,
205    #[serde(rename = "ICD")]
206    icd: ICDManifest,
207}
208
209/// ICD manifest structure
210#[derive(Debug, Deserialize, Serialize)]
211struct ICDManifest {
212    library_path: String,
213    api_version: Option<String>,
214}
215
216lazy_static::lazy_static! {
217    // Global ICD loader state (Arc allows safe sharing; we replace on updates)
218    pub static ref ICD_LOADER: Mutex<Option<Arc<LoadedICD>>> = Mutex::new(None);
219    // Handle provenance registries (Phase 4.1 scaffolding)
220    static ref REG_INSTANCES: Mutex<HashMap<u64, Weak<LoadedICD>>> = Mutex::new(HashMap::new());
221    static ref REG_PHYS_DEVS: Mutex<HashMap<u64, Weak<LoadedICD>>> = Mutex::new(HashMap::new());
222    static ref REG_DEVICES: Mutex<HashMap<u64, Weak<LoadedICD>>> = Mutex::new(HashMap::new());
223    static ref REG_QUEUES: Mutex<HashMap<u64, Weak<LoadedICD>>> = Mutex::new(HashMap::new());
224    static ref REG_CMD_POOLS: Mutex<HashMap<u64, Weak<LoadedICD>>> = Mutex::new(HashMap::new());
225    static ref REG_CMD_BUFFERS: Mutex<HashMap<u64, Weak<LoadedICD>>> = Mutex::new(HashMap::new());
226    // Aggregated mode: all loaded ICDs and meta-instance registry
227    static ref ALL_ICDS: Mutex<Vec<Arc<LoadedICD>>> = Mutex::new(Vec::new());
228    static ref META_INSTANCES: Mutex<HashMap<u64, Vec<(Arc<LoadedICD>, VkInstance)>>> = Mutex::new(HashMap::new());
229    static ref NEXT_META_INSTANCE: Mutex<u64> = Mutex::new(0xBEEF_0000_0000_0000);
230    // Device-specific ICDs (keeps them alive)
231    static ref DEVICE_ICDS: Mutex<HashMap<u64, Arc<LoadedICD>>> = Mutex::new(HashMap::new());
232}
233
234pub fn aggregated_mode_enabled() -> bool {
235    std::env::var("KRONOS_AGGREGATE_ICD").map(|v| v == "1").unwrap_or(false)
236}
237
238pub fn new_meta_instance_id() -> u64 {
239    let mut g = NEXT_META_INSTANCE.lock().unwrap();
240    *g += 1;
241    *g
242}
243
244pub fn set_meta_instance(meta_id: u64, inners: Vec<(Arc<LoadedICD>, VkInstance)>) {
245    let _ = META_INSTANCES.lock().map(|mut m| { m.insert(meta_id, inners); });
246}
247
248pub fn take_meta_instance(meta_id: u64) -> Option<Vec<(Arc<LoadedICD>, VkInstance)>> {
249    META_INSTANCES.lock().ok()?.remove(&meta_id)
250}
251
252pub fn meta_instance_for(meta_id: u64) -> Option<Vec<(Arc<LoadedICD>, VkInstance)>> {
253    META_INSTANCES.lock().ok()?.get(&meta_id).cloned()
254}
255
256pub fn get_all_icds() -> Vec<Arc<LoadedICD>> {
257    if let Ok(guard) = ALL_ICDS.lock() {
258        guard.clone()
259    } else {
260        Vec::new()
261    }
262}
263
264/// Discover and load all ICDs (used in aggregated mode)
265pub fn discover_and_load_all_icds() -> Vec<Arc<LoadedICD>> {
266    // First try to get already loaded ICDs
267    let existing = get_all_icds();
268    if !existing.is_empty() {
269        info!("Using {} already loaded ICDs for aggregated mode", existing.len());
270        return existing;
271    }
272    
273    // If none loaded, do the discovery (fallback for direct calls)
274    let mut out = Vec::new();
275    let icd_files = discover_icds();
276    if icd_files.is_empty() { return out; }
277
278    let prefer_hardware = env::var("KRONOS_PREFER_HARDWARE").map(|v| v != "0").unwrap_or(true);
279
280    for icd_file in &icd_files {
281        if let Some(manifest) = parse_icd_manifest(icd_file) {
282            let mut candidates: Vec<PathBuf> = Vec::new();
283            if Path::new(&manifest.library_path).is_absolute() {
284                candidates.push(PathBuf::from(&manifest.library_path));
285            } else {
286                candidates.push(PathBuf::from(&manifest.library_path));
287                if let Some(parent) = icd_file.parent() { candidates.push(parent.join(&manifest.library_path)); }
288            }
289            for cand in &candidates {
290                let can = fs::canonicalize(cand).unwrap_or(cand.clone());
291                if let Ok(icd) = load_icd(&can) {
292                    let arc = Arc::new(icd);
293                    out.push(arc);
294                    break;
295                }
296            }
297        }
298    }
299
300    if prefer_hardware {
301        let any_hw = out.iter().any(|icd| {
302            let s = icd.library_path.to_string_lossy();
303            !(s.contains("lvp") || s.contains("swrast") || s.contains("llvmpipe"))
304        });
305        if any_hw {
306            out.retain(|icd| {
307                let s = icd.library_path.to_string_lossy();
308                !(s.contains("lvp") || s.contains("swrast") || s.contains("llvmpipe"))
309            });
310        }
311    }
312    out
313}
314
315/// Find and load Vulkan ICDs
316pub fn discover_icds() -> Vec<PathBuf> {
317    let mut icd_files = Vec::new();
318    let mut env_icds = Vec::new();
319    
320    // Check environment variable - these will be prioritized but not exclusive
321    if let Ok(icd_filenames) = env::var("VK_ICD_FILENAMES") {
322        let separator = if cfg!(windows) { ';' } else { ':' };
323        for path in icd_filenames.split(separator) {
324            let raw = PathBuf::from(path);
325            let can = fs::canonicalize(&raw).unwrap_or(raw);
326            if can.exists() {
327                env_icds.push(can.clone());
328                icd_files.push(can);
329            } else {
330                warn!("VK_ICD_FILENAMES contains non-existent path: {}", path);
331            }
332        }
333        if !env_icds.is_empty() {
334            info!("Found {} ICD files from VK_ICD_FILENAMES (will be prioritized)", env_icds.len());
335        }
336    }
337    
338    // Always search platform-specific paths for all available ICDs
339    let search_paths = get_icd_search_paths();
340    for search_path in &search_paths {
341        if let Ok(entries) = fs::read_dir(search_path) {
342            let mut path_count = 0;
343            for entry in entries.flatten() {
344                let path = entry.path();
345                if path.extension().and_then(|s| s.to_str()) == Some("json") {
346                    // Skip if already added from environment variable
347                    if !env_icds.contains(&path) {
348                        let can = fs::canonicalize(&path).unwrap_or(path);
349                        log::debug!("Discovered ICD candidate: {}", can.display());
350                        icd_files.push(can);
351                        path_count += 1;
352                    }
353                }
354            }
355            if path_count > 0 {
356                info!("Found {} additional ICD manifest files in {}", path_count, search_path.display());
357            }
358        }
359    }
360    
361    if icd_files.is_empty() {
362        warn!("No ICD manifest files found in any search paths: {:#?}", search_paths);
363    }
364    
365    icd_files
366}
367
368/// Parse ICD manifest JSON
369fn parse_icd_manifest(path: &Path) -> Option<ICDManifest> {
370    let content = fs::read_to_string(path).ok()?;
371    
372    // Parse JSON using serde_json
373    match serde_json::from_str::<ICDManifestRoot>(&content) {
374        Ok(manifest_root) => {
375            if manifest_root.icd.library_path.is_empty() {
376                warn!("ICD manifest has empty library_path: {}", path.display());
377                return None;
378            }
379            debug!("Successfully parsed ICD manifest: {} -> {}", path.display(), manifest_root.icd.library_path);
380            Some(manifest_root.icd)
381        }
382        Err(e) => {
383            warn!("Failed to parse ICD manifest {}: {}", path.display(), e);
384            None
385        }
386    }
387}
388
389/// Parse API version from manifest string like "1.3.268" into VK_MAKE_VERSION
390fn parse_api_version(version: &str) -> Option<u32> {
391    let mut parts = version.split('.');
392    let major = parts.next()?.parse::<u32>().ok()?;
393    let minor = parts.next().and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
394    let patch = parts.next().and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
395    Some(VK_MAKE_VERSION(major, minor, patch))
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401
402    #[test]
403    fn test_parse_api_version() {
404        assert_eq!(parse_api_version("1.2.176"), Some(VK_MAKE_VERSION(1,2,176)));
405        assert_eq!(parse_api_version("1.3"), Some(VK_MAKE_VERSION(1,3,0)));
406        assert_eq!(parse_api_version("2"), Some(VK_MAKE_VERSION(2,0,0)));
407        assert_eq!(parse_api_version(""), None);
408        assert_eq!(parse_api_version("a.b.c"), None);
409    }
410
411    #[test]
412    fn test_aggregated_mode_default_off() {
413        // By default, aggregated mode should be disabled unless env var is set
414        std::env::remove_var("KRONOS_AGGREGATE_ICD");
415        assert!(!aggregated_mode_enabled());
416    }
417}
418
419/// Return all loadable ICDs with metadata (does not mutate global state)
420pub fn available_icds() -> Vec<IcdInfo> {
421    let mut out = Vec::new();
422    let icd_files = discover_icds();
423
424    for icd_file in &icd_files {
425        if let Some(manifest) = parse_icd_manifest(icd_file) {
426            // Build candidate library paths; prefer absolute, else try as-provided and manifest-relative
427            let mut candidates: Vec<PathBuf> = Vec::new();
428            if Path::new(&manifest.library_path).is_absolute() {
429                candidates.push(PathBuf::from(&manifest.library_path));
430            } else {
431                candidates.push(PathBuf::from(&manifest.library_path));
432                if let Some(parent) = icd_file.parent() {
433                    candidates.push(parent.join(&manifest.library_path));
434                }
435            }
436
437            // Attempt to load first working candidate for this manifest
438            for cand in &candidates {
439                if let Ok(icd) = load_icd(cand) {
440                    let path_str = icd.library_path.to_string_lossy();
441                    let is_software = path_str.contains("lvp") || path_str.contains("swrast") || path_str.contains("llvmpipe");
442                    let api_version = manifest
443                        .api_version
444                        .as_deref()
445                        .and_then(parse_api_version)
446                        .unwrap_or(icd.api_version);
447
448                    out.push(IcdInfo {
449                        library_path: icd.library_path,
450                        manifest_path: Some(icd_file.clone()),
451                        api_version,
452                        is_software,
453                    });
454                    break; // one entry per manifest
455                }
456            }
457        }
458    }
459
460    out
461}
462
463// Preferred ICD selection (process-wide for now)
464#[derive(Debug, Clone)]
465enum IcdPreference {
466    Path(PathBuf),
467    Index(usize),
468}
469
470lazy_static::lazy_static! {
471    static ref PREFERRED_ICD: Mutex<Option<IcdPreference>> = Mutex::new(None);
472}
473
474pub fn set_preferred_icd_path<P: Into<PathBuf>>(path: P) {
475    let path_buf = path.into();
476    log::info!("Setting preferred ICD path: {:?}", path_buf);
477    if let Ok(mut pref) = PREFERRED_ICD.lock() {
478        *pref = Some(IcdPreference::Path(path_buf));
479    }
480}
481
482pub fn set_preferred_icd_index(index: usize) {
483    log::info!("Setting preferred ICD index: {}", index);
484    if let Ok(mut pref) = PREFERRED_ICD.lock() {
485        *pref = Some(IcdPreference::Index(index));
486    }
487}
488
489pub fn clear_preferred_icd() {
490    if let Ok(mut pref) = PREFERRED_ICD.lock() {
491        *pref = None;
492    }
493}
494
495/// Get info for the currently selected/loaded ICD (if any)
496pub fn selected_icd_info() -> Option<IcdInfo> {
497    let icd = get_icd()?;
498    let path = icd.library_path.clone();
499    let path_str = path.to_string_lossy();
500    let is_software = path_str.contains("lvp") || path_str.contains("swrast") || path_str.contains("llvmpipe");
501    Some(IcdInfo {
502        library_path: path,
503        manifest_path: None,
504        api_version: icd.api_version,
505        is_software,
506    })
507}
508
509// ===== Phase 4.1: Handle provenance registry (public helpers) =====
510
511fn upgrade_icd(w: &Weak<LoadedICD>) -> Option<Arc<LoadedICD>> { w.upgrade() }
512
513pub fn register_instance_icd(instance: VkInstance, icd: &Arc<LoadedICD>) {
514    let _ = REG_INSTANCES.lock().map(|mut m| { m.insert(instance.as_raw(), Arc::downgrade(icd)); });
515}
516pub fn register_physical_device_icd(phys: VkPhysicalDevice, icd: &Arc<LoadedICD>) {
517    let _ = REG_PHYS_DEVS.lock().map(|mut m| { m.insert(phys.as_raw(), Arc::downgrade(icd)); });
518}
519pub fn register_device_icd(device: VkDevice, icd: &Arc<LoadedICD>) {
520    let device_raw = device.as_raw();
521    log::debug!("Registering device {} with ICD", device_raw);
522    
523    // Store the Arc to keep the ICD alive
524    if let Ok(mut device_icds) = DEVICE_ICDS.lock() {
525        device_icds.insert(device_raw, icd.clone());
526        log::debug!("Stored device ICD Arc for device {}", device_raw);
527    }
528    
529    // Register the weak reference for lookups
530    match REG_DEVICES.lock() {
531        Ok(mut m) => {
532            m.insert(device_raw, Arc::downgrade(icd));
533            log::debug!("Device {} registered successfully", device_raw);
534        }
535        Err(e) => {
536            log::error!("Failed to lock REG_DEVICES: {:?}", e);
537        }
538    }
539}
540pub fn register_queue_icd(queue: VkQueue, icd: &Arc<LoadedICD>) {
541    let _ = REG_QUEUES.lock().map(|mut m| { m.insert(queue.as_raw(), Arc::downgrade(icd)); });
542}
543pub fn register_command_pool_icd(pool: VkCommandPool, icd: &Arc<LoadedICD>) {
544    let _ = REG_CMD_POOLS.lock().map(|mut m| { m.insert(pool.as_raw(), Arc::downgrade(icd)); });
545}
546pub fn register_command_buffer_icd(cb: VkCommandBuffer, icd: &Arc<LoadedICD>) {
547    let _ = REG_CMD_BUFFERS.lock().map(|mut m| { m.insert(cb.as_raw(), Arc::downgrade(icd)); });
548}
549
550pub fn unregister_instance(instance: VkInstance) { let _ = REG_INSTANCES.lock().map(|mut m| { m.remove(&instance.as_raw()); }); }
551pub fn unregister_physical_device(phys: VkPhysicalDevice) { let _ = REG_PHYS_DEVS.lock().map(|mut m| { m.remove(&phys.as_raw()); }); }
552pub fn unregister_device(device: VkDevice) { 
553    let device_raw = device.as_raw();
554    let _ = REG_DEVICES.lock().map(|mut m| { m.remove(&device_raw); }); 
555    let _ = DEVICE_ICDS.lock().map(|mut m| { m.remove(&device_raw); });
556}
557pub fn unregister_queue(queue: VkQueue) { let _ = REG_QUEUES.lock().map(|mut m| { m.remove(&queue.as_raw()); }); }
558pub fn unregister_command_pool(pool: VkCommandPool) { let _ = REG_CMD_POOLS.lock().map(|mut m| { m.remove(&pool.as_raw()); }); }
559pub fn unregister_command_buffer(cb: VkCommandBuffer) { let _ = REG_CMD_BUFFERS.lock().map(|mut m| { m.remove(&cb.as_raw()); }); }
560
561pub fn icd_for_instance(instance: VkInstance) -> Option<Arc<LoadedICD>> {
562    REG_INSTANCES.lock().ok()?.get(&instance.as_raw()).and_then(upgrade_icd)
563        .or_else(|| get_icd())
564}
565pub fn icd_for_physical_device(phys: VkPhysicalDevice) -> Option<Arc<LoadedICD>> {
566    REG_PHYS_DEVS.lock().ok()?.get(&phys.as_raw()).and_then(upgrade_icd)
567        .or_else(|| icd_for_instance(VkInstance::NULL))
568}
569pub fn icd_for_device(device: VkDevice) -> Option<Arc<LoadedICD>> {
570    let device_raw = device.as_raw();
571    log::trace!("Looking up ICD for device {:?} (raw: {})", device, device_raw);
572    
573    if let Ok(guard) = REG_DEVICES.lock() {
574        log::trace!("REG_DEVICES has {} entries", guard.len());
575        if let Some(weak_icd) = guard.get(&device_raw) {
576            log::trace!("Found weak reference for device {}", device_raw);
577            if let Some(arc_icd) = upgrade_icd(weak_icd) {
578                log::trace!("Successfully upgraded weak reference to Arc");
579                return Some(arc_icd);
580            } else {
581                log::warn!("Weak reference for device {} could not be upgraded (ICD dropped?)", device_raw);
582            }
583        } else {
584            log::trace!("Device {} not found in registry", device_raw);
585        }
586    } else {
587        log::error!("Failed to lock REG_DEVICES");
588    }
589    
590    log::trace!("Device not found in registry, using fallback");
591    get_icd()
592}
593pub fn icd_for_queue(queue: VkQueue) -> Option<Arc<LoadedICD>> {
594    REG_QUEUES.lock().ok()?.get(&queue.as_raw()).and_then(upgrade_icd)
595        .or_else(|| get_icd())
596}
597pub fn icd_for_command_pool(pool: VkCommandPool) -> Option<Arc<LoadedICD>> {
598    REG_CMD_POOLS.lock().ok()?.get(&pool.as_raw()).and_then(upgrade_icd)
599        .or_else(|| get_icd())
600}
601pub fn icd_for_command_buffer(cb: VkCommandBuffer) -> Option<Arc<LoadedICD>> {
602    REG_CMD_BUFFERS.lock().ok()?.get(&cb.as_raw()).and_then(upgrade_icd)
603        .or_else(|| get_icd())
604}
605
606/// Load an ICD library
607fn is_trusted_library(path: &Path) -> bool {
608    if env::var("KRONOS_ALLOW_UNTRUSTED_LIBS").map(|v| v == "1").unwrap_or(false) {
609        return true;
610    }
611    #[cfg(target_os = "linux")]
612    {
613        const TRUSTED_PREFIXES: &[&str] = &[
614            "/usr/lib",
615            "/usr/lib64",
616            "/usr/local/lib",
617            "/lib",
618            "/lib64",
619            "/usr/lib/x86_64-linux-gnu",
620        ];
621        let p = path.to_string_lossy();
622        return TRUSTED_PREFIXES.iter().any(|prefix| p.starts_with(prefix));
623    }
624    #[cfg(target_os = "windows")]
625    {
626        // Trust common system locations by default
627        let p = path.to_string_lossy().to_lowercase();
628        let sysroot = env::var("SYSTEMROOT").unwrap_or_else(|_| String::from("C:\\Windows"));
629        let sys32 = format!("{}\\System32", sysroot);
630        let sys32_l = sys32.to_lowercase();
631        // Program Files (both x64/x86)
632        let pf = env::var("PROGRAMFILES").unwrap_or_default().to_lowercase();
633        let pf86 = env::var("PROGRAMFILES(X86)").unwrap_or_default().to_lowercase();
634        let trusted = p.starts_with(&sys32_l)
635            || (p.starts_with(&pf) && p.contains("vulkan"))
636            || (!pf86.is_empty() && p.starts_with(&pf86) && p.contains("vulkan"));
637        return trusted;
638    }
639    #[cfg(not(any(target_os = "linux", target_os = "windows")))]
640    {
641        // Conservative default on other platforms
642        true
643    }
644}
645
646pub fn load_icd(library_path: &Path) -> Result<LoadedICD, IcdError> {
647    // SAFETY: This function uses unsafe operations for:
648    // 1. dlopen/dlsym - We ensure the library path is valid and null-terminated
649    // 2. Function pointer transmutation - We trust the Vulkan ICD to provide correct function signatures
650    // 3. The loaded library handle is kept alive for the lifetime of LoadedICD
651    unsafe {
652        // Resolve and validate the library path
653        let canon = fs::canonicalize(library_path).unwrap_or_else(|_| library_path.to_path_buf());
654        let meta = fs::metadata(&canon)
655            .map_err(|_| IcdError::LibraryLoadFailed(format!("{} (metadata not found)", canon.display())))?;
656        if !meta.is_file() {
657            return Err(IcdError::LibraryLoadFailed(format!("{} is not a regular file", canon.display())));
658        }
659        if !is_trusted_library(&canon) {
660            return Err(IcdError::LibraryLoadFailed(format!(
661                "{} rejected by trust policy (set KRONOS_ALLOW_UNTRUSTED_LIBS=1 to override)",
662                canon.display()
663            )));
664        }
665
666        // Load the library (cross-platform)
667        #[cfg(windows)]
668        let lib = {
669            libloading::Library::new(&canon)
670                .map_err(|e| IcdError::LibraryLoadFailed(format!("{}: {}", canon.display(), e)))?
671        };
672        #[cfg(not(windows))]
673        let (handle, lib) = {
674            let lib_cstr = CString::new(canon.as_os_str().as_bytes())?;
675            let handle = libc::dlopen(lib_cstr.as_ptr(), libc::RTLD_NOW | libc::RTLD_LOCAL);
676            if handle.is_null() {
677                let error = unsafe { CStr::from_ptr(libc::dlerror()) }.to_string_lossy().into_owned();
678                return Err(IcdError::LibraryLoadFailed(format!("{}: {}", library_path.display(), error)));
679            }
680            (handle, ())
681        };
682
683        // Get vk_icdGetInstanceProcAddr (ICD entry point)
684        #[cfg(windows)]
685        let get_proc = unsafe {
686            let sym: libloading::Symbol<unsafe extern "C" fn(VkInstance, *const c_char) -> PFN_vkVoidFunction> =
687                lib.get(b"vk_icdGetInstanceProcAddr\0")
688                    .map_err(|e| IcdError::LibraryLoadFailed(format!("{}: missing vk_icdGetInstanceProcAddr ({})", canon.display(), e)))?;
689            Some(*sym)
690        };
691        #[cfg(not(windows))]
692        let get_proc = {
693            let get_instance_proc_addr_name = CString::new("vk_icdGetInstanceProcAddr")?;
694            let ptr = unsafe { libc::dlsym(handle, get_instance_proc_addr_name.as_ptr()) };
695            if ptr.is_null() {
696                unsafe { libc::dlclose(handle); }
697                return Err(IcdError::MissingFunction("vk_icdGetInstanceProcAddr"));
698            }
699            let f: PFN_vkGetInstanceProcAddr = unsafe { std::mem::transmute(ptr) };
700            f
701        };
702        let vk_get_instance_proc_addr: PFN_vkGetInstanceProcAddr = get_proc;
703        
704        // Get global functions
705        let mut icd = LoadedICD {
706            library_path: canon,
707            // Keep library alive for process lifetime. On Windows, store as opaque pointer.
708            handle: {
709                #[cfg(windows)]
710                {
711                    let boxed = Box::new(lib);
712                    Box::into_raw(boxed) as *mut c_void
713                }
714                #[cfg(not(windows))]
715                { handle as *mut c_void }
716            },
717            api_version: VK_API_VERSION_1_0,
718            vk_get_instance_proc_addr,
719            create_instance: None,
720            destroy_instance: None,
721            enumerate_physical_devices: None,
722            get_physical_device_properties: None,
723            get_physical_device_queue_family_properties: None,
724            get_physical_device_memory_properties: None,
725            create_device: None,
726            destroy_device: None,
727            get_device_proc_addr: None,
728            get_device_queue: None,
729            queue_submit: None,
730            queue_wait_idle: None,
731            device_wait_idle: None,
732            allocate_memory: None,
733            free_memory: None,
734            map_memory: None,
735            unmap_memory: None,
736            create_buffer: None,
737            destroy_buffer: None,
738            get_buffer_memory_requirements: None,
739            bind_buffer_memory: None,
740            create_descriptor_set_layout: None,
741            destroy_descriptor_set_layout: None,
742            create_descriptor_pool: None,
743            destroy_descriptor_pool: None,
744            reset_descriptor_pool: None,
745            allocate_descriptor_sets: None,
746            free_descriptor_sets: None,
747            update_descriptor_sets: None,
748            create_pipeline_layout: None,
749            destroy_pipeline_layout: None,
750            create_compute_pipelines: None,
751            destroy_pipeline: None,
752            create_shader_module: None,
753            destroy_shader_module: None,
754            create_command_pool: None,
755            destroy_command_pool: None,
756            allocate_command_buffers: None,
757            free_command_buffers: None,
758            begin_command_buffer: None,
759            end_command_buffer: None,
760            cmd_bind_pipeline: None,
761            cmd_bind_descriptor_sets: None,
762            cmd_dispatch: None,
763            cmd_dispatch_indirect: None,
764            cmd_pipeline_barrier: None,
765            cmd_copy_buffer: None,
766            cmd_push_constants: None,
767            create_fence: None,
768            destroy_fence: None,
769            reset_fences: None,
770            get_fence_status: None,
771            wait_for_fences: None,
772            create_semaphore: None,
773            destroy_semaphore: None,
774            create_event: None,
775            destroy_event: None,
776            get_event_status: None,
777            set_event: None,
778            reset_event: None,
779            cmd_set_event: None,
780            cmd_reset_event: None,
781            cmd_wait_events: None,
782            wait_semaphores: None,
783        };
784        
785        // Load global functions and propagate failure instead of silently ignoring it
786        load_global_functions_inner(&mut icd)?;
787        Ok(icd)
788    }
789}
790
791/// Load global function pointers
792///
793/// # Safety
794///
795/// This function is unsafe because:
796/// - It calls vkGetInstanceProcAddr through a function pointer
797/// - It transmutes void pointers to function pointers without type checking
798/// - The caller must ensure icd contains a valid vkGetInstanceProcAddr function pointer
799/// - The ICD library must be loaded and remain valid for the lifetime of icd
800/// - Function signatures must match the Vulkan specification exactly
801/// - Incorrect function pointers will cause undefined behavior when called
802unsafe fn load_global_functions_inner(icd: &mut LoadedICD) -> Result<(), IcdError> {
803    let get_proc_addr = icd.vk_get_instance_proc_addr
804        .ok_or(IcdError::MissingFunction("vkGetInstanceProcAddr not loaded"))?;
805    
806    // Helper macro to load functions
807    macro_rules! load_fn {
808        ($name:ident, $fn_name:expr) => {
809            // These are static strings, so they won't have null bytes
810            let name = CString::new($fn_name)
811                .expect(concat!("Invalid function name: ", $fn_name));
812            if let Some(addr) = get_proc_addr(VkInstance::NULL, name.as_ptr()) {
813                icd.$name = std::mem::transmute(addr);
814            }
815        };
816    }
817    
818    // Load instance creation functions
819    load_fn!(create_instance, "vkCreateInstance");
820    
821    debug!("Loaded global functions - create_instance: {:?}",
822           icd.create_instance.is_some());
823    
824    Ok(())
825}
826
827/// Load instance-level functions
828///
829/// # Safety
830///
831/// This function is unsafe because:
832/// - It calls vkGetInstanceProcAddr with the provided instance handle
833/// - It transmutes void pointers to function pointers without type checking
834/// - The instance must be a valid VkInstance created by this ICD
835/// - The icd must contain valid function pointers from the same ICD that created the instance
836/// - The instance must remain valid for at least as long as these functions are used
837/// - Using an invalid instance handle will cause undefined behavior
838/// - Function signatures must match the Vulkan specification exactly
839pub unsafe fn load_instance_functions_inner(icd: &mut LoadedICD, instance: VkInstance) -> Result<(), IcdError> {
840    let get_proc_addr = icd.vk_get_instance_proc_addr
841        .ok_or(IcdError::MissingFunction("vkGetInstanceProcAddr not loaded"))?;
842    
843    macro_rules! load_fn {
844        ($name:ident, $fn_name:expr) => {
845            let name = CString::new($fn_name)
846                .expect(concat!("Invalid function name: ", $fn_name));
847            if let Some(addr) = get_proc_addr(instance, name.as_ptr()) {
848                icd.$name = std::mem::transmute(addr);
849            }
850        };
851    }
852    
853    // Load instance functions
854    load_fn!(destroy_instance, "vkDestroyInstance");
855    load_fn!(enumerate_physical_devices, "vkEnumeratePhysicalDevices");
856    load_fn!(get_physical_device_properties, "vkGetPhysicalDeviceProperties");
857    load_fn!(get_physical_device_queue_family_properties, "vkGetPhysicalDeviceQueueFamilyProperties");
858    load_fn!(get_physical_device_memory_properties, "vkGetPhysicalDeviceMemoryProperties");
859    load_fn!(create_device, "vkCreateDevice");
860    load_fn!(get_device_proc_addr, "vkGetDeviceProcAddr");
861    
862    debug!("Loaded instance functions - enumerate_physical_devices: {:?}",
863           icd.enumerate_physical_devices.is_some());
864    
865    Ok(())
866}
867
868/// Load device-level functions
869///
870/// # Safety
871///
872/// This function is unsafe because:
873/// - It calls vkGetDeviceProcAddr or vkGetInstanceProcAddr with device/instance handles
874/// - It transmutes void pointers to function pointers without type checking
875/// - The device must be a valid VkDevice created by this ICD
876/// - The icd must contain valid function pointers from the same ICD that created the device
877/// - The device must remain valid for at least as long as these functions are used
878/// - Using an invalid device handle will cause undefined behavior
879/// - Function signatures must match the Vulkan specification exactly
880/// - The fallback to instance proc addr requires a valid instance context
881pub unsafe fn load_device_functions_inner(icd: &mut LoadedICD, device: VkDevice) -> Result<(), IcdError> {
882    // Prefer device-level loader; do not fall back to NULL instance, which is invalid.
883    let get_device_proc_fn = icd
884        .get_device_proc_addr
885        .ok_or(IcdError::MissingFunction("vkGetDeviceProcAddr not loaded"))?;
886    
887    // Helper function to get proc address strictly via device
888    let get_proc_addr_helper = |name: *const c_char| -> PFN_vkVoidFunction {
889        get_device_proc_fn(device, name)
890    };
891    
892    macro_rules! load_fn {
893        ($name:ident, $fn_name:expr) => {
894            let name = CString::new($fn_name)
895                .expect(concat!("Invalid function name: ", $fn_name));
896            if let Some(addr) = get_proc_addr_helper(name.as_ptr()) {
897                icd.$name = std::mem::transmute(addr);
898            }
899        };
900    }
901    
902    // Device functions
903    load_fn!(destroy_device, "vkDestroyDevice");
904    load_fn!(get_device_queue, "vkGetDeviceQueue");
905    load_fn!(device_wait_idle, "vkDeviceWaitIdle");
906    
907    // Queue functions
908    load_fn!(queue_submit, "vkQueueSubmit");
909    load_fn!(queue_wait_idle, "vkQueueWaitIdle");
910    
911    // Memory functions
912    load_fn!(allocate_memory, "vkAllocateMemory");
913    load_fn!(free_memory, "vkFreeMemory");
914    load_fn!(map_memory, "vkMapMemory");
915    load_fn!(unmap_memory, "vkUnmapMemory");
916    
917    // Buffer functions
918    load_fn!(create_buffer, "vkCreateBuffer");
919    load_fn!(destroy_buffer, "vkDestroyBuffer");
920    load_fn!(get_buffer_memory_requirements, "vkGetBufferMemoryRequirements");
921    load_fn!(bind_buffer_memory, "vkBindBufferMemory");
922    
923    // Compute-specific functions
924    load_fn!(create_descriptor_set_layout, "vkCreateDescriptorSetLayout");
925    load_fn!(destroy_descriptor_set_layout, "vkDestroyDescriptorSetLayout");
926    load_fn!(create_descriptor_pool, "vkCreateDescriptorPool");
927    load_fn!(destroy_descriptor_pool, "vkDestroyDescriptorPool");
928    load_fn!(reset_descriptor_pool, "vkResetDescriptorPool");
929    load_fn!(allocate_descriptor_sets, "vkAllocateDescriptorSets");
930    load_fn!(free_descriptor_sets, "vkFreeDescriptorSets");
931    load_fn!(update_descriptor_sets, "vkUpdateDescriptorSets");
932    
933    load_fn!(create_pipeline_layout, "vkCreatePipelineLayout");
934    load_fn!(destroy_pipeline_layout, "vkDestroyPipelineLayout");
935    load_fn!(create_compute_pipelines, "vkCreateComputePipelines");
936    load_fn!(destroy_pipeline, "vkDestroyPipeline");
937    
938    load_fn!(create_shader_module, "vkCreateShaderModule");
939    load_fn!(destroy_shader_module, "vkDestroyShaderModule");
940    
941    load_fn!(create_command_pool, "vkCreateCommandPool");
942    load_fn!(destroy_command_pool, "vkDestroyCommandPool");
943    load_fn!(allocate_command_buffers, "vkAllocateCommandBuffers");
944    load_fn!(free_command_buffers, "vkFreeCommandBuffers");
945    load_fn!(begin_command_buffer, "vkBeginCommandBuffer");
946    load_fn!(end_command_buffer, "vkEndCommandBuffer");
947    
948    load_fn!(cmd_bind_pipeline, "vkCmdBindPipeline");
949    load_fn!(cmd_bind_descriptor_sets, "vkCmdBindDescriptorSets");
950    load_fn!(cmd_dispatch, "vkCmdDispatch");
951    load_fn!(cmd_dispatch_indirect, "vkCmdDispatchIndirect");
952    load_fn!(cmd_pipeline_barrier, "vkCmdPipelineBarrier");
953    load_fn!(cmd_copy_buffer, "vkCmdCopyBuffer");
954    load_fn!(cmd_push_constants, "vkCmdPushConstants");
955    
956    // Sync functions
957    load_fn!(create_fence, "vkCreateFence");
958    load_fn!(destroy_fence, "vkDestroyFence");
959    load_fn!(reset_fences, "vkResetFences");
960    load_fn!(get_fence_status, "vkGetFenceStatus");
961    load_fn!(wait_for_fences, "vkWaitForFences");
962    
963    load_fn!(create_semaphore, "vkCreateSemaphore");
964    load_fn!(destroy_semaphore, "vkDestroySemaphore");
965    
966    load_fn!(create_event, "vkCreateEvent");
967    load_fn!(destroy_event, "vkDestroyEvent");
968    load_fn!(get_event_status, "vkGetEventStatus");
969    load_fn!(set_event, "vkSetEvent");
970    load_fn!(reset_event, "vkResetEvent");
971    
972    load_fn!(cmd_set_event, "vkCmdSetEvent");
973    load_fn!(cmd_reset_event, "vkCmdResetEvent");
974    load_fn!(cmd_wait_events, "vkCmdWaitEvents");
975    
976    // Timeline semaphore functions (optional)
977    load_fn!(wait_semaphores, "vkWaitSemaphores");
978    
979    log::debug!("Device functions loaded - create_buffer: {}, create_command_pool: {}",
980        icd.create_buffer.is_some(),
981        icd.create_command_pool.is_some());
982    
983    Ok(())
984}
985
986/// Initialize the ICD loader
987pub fn initialize_icd_loader() -> Result<(), IcdError> {
988    info!("Initializing ICD loader...");
989    let icd_files = discover_icds();
990    
991    if icd_files.is_empty() {
992        warn!("No ICD manifest files found");
993        return Err(IcdError::NoManifestsFound);
994    }
995    
996    info!("Found {} ICD manifest files", icd_files.len());
997    
998    // Check if we have environment variable override
999    let env_icd_count = if let Ok(icd_filenames) = env::var("VK_ICD_FILENAMES") {
1000        let separator = if cfg!(windows) { ';' } else { ':' };
1001        icd_filenames.split(separator).filter(|s| !s.is_empty()).count()
1002    } else {
1003        0
1004    };
1005    
1006    // Collect all successfully loaded ICDs with priority flag
1007    let mut loaded_icds = Vec::new();
1008    
1009    // Try to load each ICD
1010    for (idx, icd_file) in icd_files.iter().enumerate() {
1011        if let Some(manifest) = parse_icd_manifest(&icd_file) {
1012            // Build candidate library paths. Prefer the path as provided to allow
1013            // dynamic linker search to resolve common locations (e.g. /usr/lib). As a
1014            // fallback, try relative to the manifest directory.
1015            let mut candidates: Vec<PathBuf> = Vec::new();
1016            if Path::new(&manifest.library_path).is_absolute() {
1017                candidates.push(PathBuf::from(&manifest.library_path));
1018            } else {
1019                // As provided (lets dlopen search LD_LIBRARY_PATH etc.)
1020                candidates.push(PathBuf::from(&manifest.library_path));
1021                // Fallback relative to manifest directory
1022                if let Some(parent) = icd_file.parent() {
1023                    candidates.push(parent.join(&manifest.library_path));
1024                }
1025            }
1026
1027            let mut loaded_ok: Option<LoadedICD> = None;
1028            for cand in &candidates {
1029                // Canonicalize candidate for validation
1030                let can = fs::canonicalize(cand).unwrap_or(cand.clone());
1031                log::info!("Attempting to load ICD library: {} (from {})", can.display(), icd_file.display());
1032                match load_icd(&can) {
1033                    Ok(icd) => {
1034                        loaded_ok = Some(icd);
1035                        break;
1036                    }
1037                    Err(e) => {
1038                        warn!("Failed to load candidate {}: {}", can.display(), e);
1039                    }
1040                }
1041            }
1042
1043            if let Some(icd) = loaded_ok {
1044                    // Check if this is a software renderer
1045                    let path_str = icd.library_path.to_string_lossy();
1046                    let is_software = path_str.contains("lvp") ||
1047                                     path_str.contains("swrast") ||
1048                                     path_str.contains("llvmpipe");
1049                    
1050                    // Environment variable ICDs are prioritized (first N entries from discover_icds)
1051                    let is_env_priority = idx < env_icd_count;
1052                    
1053                    let icd_type = if is_software { "software" } else { "hardware" };
1054                    let priority_str = if is_env_priority { " (VK_ICD_FILENAMES priority)" } else { "" };
1055                    info!("Successfully loaded {} Vulkan ICD: {}{}", icd_type, icd.library_path.display(), priority_str);
1056                    
1057                    loaded_icds.push((icd, is_software, is_env_priority));
1058            } else {
1059                warn!("Failed to load ICD from any candidate for manifest {}", icd_file.display());
1060            }
1061        }
1062    }
1063    
1064    if loaded_icds.is_empty() {
1065        return Err(IcdError::InvalidManifest("Failed to load any Vulkan ICD".to_string()));
1066    }
1067
1068    // Optional policy: if any hardware ICDs are present, prefer them over software by filtering
1069    let prefer_hardware = env::var("KRONOS_PREFER_HARDWARE").map(|v| v != "0").unwrap_or(true);
1070    if prefer_hardware {
1071        let any_hw = loaded_icds.iter().any(|(_, is_sw, _)| !*is_sw);
1072        if any_hw {
1073            loaded_icds.retain(|(_, is_sw, _)| !*is_sw);
1074            info!("Hardware ICDs available; software ICDs will be ignored (set KRONOS_PREFER_HARDWARE=0 to disable)");
1075        }
1076    }
1077
1078    // Sort ICDs: env priority first, then hardware (already filtered if policy), then software renderers
1079    loaded_icds.sort_by_key(|(_, is_software, is_env_priority)| {
1080        (!is_env_priority, *is_software)
1081    });
1082    
1083    // Log all available ICDs
1084    info!("Available ICDs: {} hardware, {} software", 
1085          loaded_icds.iter().filter(|(_, is_sw, _)| !is_sw).count(),
1086          loaded_icds.iter().filter(|(_, is_sw, _)| *is_sw).count());
1087    
1088    // Store all ICDs for aggregated mode BEFORE selecting the best one
1089    let mut all_icds_vec = Vec::new();
1090    for (icd, _, _) in &loaded_icds {
1091        all_icds_vec.push(Arc::new(icd.clone()));
1092    }
1093    *ALL_ICDS.lock()? = all_icds_vec;
1094    
1095    // Check for explicit preference
1096    let preferred = PREFERRED_ICD.lock().ok().and_then(|p| p.clone());
1097    let (best_icd, is_software, is_env_priority) = if let Some(pref) = preferred {
1098        match pref {
1099            IcdPreference::Path(want) => {
1100                if let Some((idx, _)) = loaded_icds.iter().enumerate().find(|(_, (icd, _, _))| icd.library_path == want) {
1101                    loaded_icds.into_iter().nth(idx).unwrap()
1102                } else {
1103                    warn!("Preferred ICD path not found: {} — falling back to default selection", want.display());
1104                    loaded_icds.into_iter().next().unwrap()
1105                }
1106            }
1107            IcdPreference::Index(i) => {
1108                log::info!("Applying ICD preference: index {} (from available_icds order)", i);
1109                
1110                // The index refers to the order in available_icds(), not loaded_icds
1111                // We need to find the matching ICD by path from ALL_ICDS
1112                if let Ok(all_icds) = ALL_ICDS.lock() {
1113                    log::info!("ALL_ICDS contains {} ICDs:", all_icds.len());
1114                    for (idx, icd) in all_icds.iter().enumerate() {
1115                        log::info!("  [{}] {}", idx, icd.library_path.display());
1116                    }
1117                    
1118                    if let Some(target_icd) = all_icds.get(i) {
1119                        let target_path = &target_icd.library_path;
1120                        log::info!("Selected index {} points to: {}", i, target_path.display());
1121                        
1122                        // Find this ICD in loaded_icds
1123                        if let Some((idx, _)) = loaded_icds.iter().enumerate()
1124                            .find(|(_, (icd, _, _))| &icd.library_path == target_path) {
1125                            loaded_icds.into_iter().nth(idx).unwrap()
1126                        } else {
1127                            warn!("Preferred ICD not found in loaded set; falling back to default");
1128                            loaded_icds.into_iter().next().unwrap()
1129                        }
1130                    } else {
1131                        warn!("Preferred ICD index {} out of range ({}); falling back to default", i, all_icds.len());
1132                        loaded_icds.into_iter().next().unwrap()
1133                    }
1134                } else {
1135                    warn!("Could not access ALL_ICDS; falling back to default");
1136                    loaded_icds.into_iter().next().unwrap()
1137                }
1138            }
1139        }
1140    } else {
1141        // Use the best ICD (first in sorted list)
1142        loaded_icds.into_iter().next().unwrap()
1143    };
1144    
1145    if is_env_priority {
1146        info!("Using ICD specified by VK_ICD_FILENAMES: {}", best_icd.library_path.display());
1147    } else if is_software {
1148        warn!("Using software renderer - no hardware Vulkan drivers found");
1149        info!("To use hardware drivers, ensure they are installed and ICD files are in /usr/share/vulkan/icd.d/");
1150    } else {
1151        info!("Selected hardware Vulkan driver: {}", best_icd.library_path.display());
1152    }
1153    
1154    // In aggregated mode, log that we're using multiple ICDs
1155    if aggregated_mode_enabled() {
1156        let icd_count = ALL_ICDS.lock()?.len();
1157        info!("Aggregated mode enabled: {} ICDs available for multi-GPU support", icd_count);
1158        info!("Using {} as fallback ICD", best_icd.library_path.display());
1159    }
1160    
1161    *ICD_LOADER.lock()? = Some(Arc::new(best_icd));
1162    Ok(())
1163}
1164
1165/// Get the loaded ICD (shared clone)
1166pub fn get_icd() -> Option<Arc<LoadedICD>> {
1167    // Always use the main ICD from ICD_LOADER
1168    // This ensures we get the ICD with properly loaded instance/device functions
1169    // Preferences are already applied during initialization in initialize_icd_loader()
1170    log::trace!("Using main ICD from ICD_LOADER");
1171    ICD_LOADER.lock().ok()?.as_ref().cloned()
1172}
1173
1174/// Get a specific ICD based on preference
1175fn get_preferred_icd(pref: &IcdPreference) -> Option<Arc<LoadedICD>> {
1176    let all_icds = ALL_ICDS.lock().ok()?;
1177    log::debug!("Looking for preferred ICD among {} loaded ICDs", all_icds.len());
1178    
1179    match pref {
1180        IcdPreference::Path(want) => {
1181            log::debug!("Looking for ICD with path: {:?}", want);
1182            let result = all_icds.iter()
1183                .find(|icd| &icd.library_path == want)
1184                .cloned();
1185            if result.is_none() {
1186                log::warn!("Preferred ICD path not found: {:?}", want);
1187            }
1188            result
1189        }
1190        IcdPreference::Index(i) => {
1191            log::debug!("Looking for ICD at index: {}", i);
1192            let result = all_icds.get(*i).cloned();
1193            if result.is_none() {
1194                log::warn!("Preferred ICD index {} out of range (have {} ICDs)", i, all_icds.len());
1195            }
1196            result
1197        }
1198    }
1199}
1200
1201/// Apply a mutation to the current ICD by replacing it with an updated copy
1202fn replace_icd<F>(mutator: F) -> Result<(), IcdError>
1203where
1204    F: FnOnce(&mut LoadedICD) -> Result<(), IcdError>,
1205{
1206    let mut guard = ICD_LOADER.lock()?;
1207    let current = guard.as_ref().ok_or(IcdError::NoIcdLoaded)?;
1208    let mut updated = (**current).clone();
1209    mutator(&mut updated)?;
1210    *guard = Some(Arc::new(updated));
1211    Ok(())
1212}
1213
1214/// Update instance-level function pointers for the current ICD
1215pub unsafe fn update_instance_functions(instance: VkInstance) -> Result<(), IcdError> {
1216    replace_icd(|icd| load_instance_functions_inner(icd, instance))
1217}
1218
1219/// Update device-level function pointers for the current ICD
1220pub unsafe fn update_device_functions(device: VkDevice) -> Result<(), IcdError> {
1221    replace_icd(|icd| load_device_functions_inner(icd, device))
1222}
1223
1224/// Load instance functions for a specific ICD (used in aggregated mode)
1225pub unsafe fn load_instance_functions_for_icd(icd: &mut LoadedICD, instance: VkInstance) -> Result<(), IcdError> {
1226    load_instance_functions_inner(icd, instance)
1227}