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 only used for dlclose in Drop
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/// Public info about a loadable ICD
189#[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/// ICD manifest root structure
198#[derive(Debug, Deserialize, Serialize)]
199struct ICDManifestRoot {
200    file_format_version: String,
201    #[serde(rename = "ICD")]
202    icd: ICDManifest,
203}
204
205/// ICD manifest structure
206#[derive(Debug, Deserialize, Serialize)]
207struct ICDManifest {
208    library_path: String,
209    api_version: Option<String>,
210}
211
212lazy_static::lazy_static! {
213    // Global ICD loader state (Arc allows safe sharing; we replace on updates)
214    pub static ref ICD_LOADER: Mutex<Option<Arc<LoadedICD>>> = Mutex::new(None);
215    // Handle provenance registries (Phase 4.1 scaffolding)
216    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    // Aggregated mode: all loaded ICDs and meta-instance registry
223    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
258/// Discover and load all ICDs (used in aggregated mode)
259pub fn discover_and_load_all_icds() -> Vec<Arc<LoadedICD>> {
260    // First try to get already loaded ICDs
261    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    // If none loaded, do the discovery (fallback for direct calls)
268    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
309/// Find and load Vulkan ICDs
310pub fn discover_icds() -> Vec<PathBuf> {
311    let mut icd_files = Vec::new();
312    let mut env_icds = Vec::new();
313    
314    // Check environment variable - these will be prioritized but not exclusive
315    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    // Always search platform-specific paths for all available ICDs
333    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                    // Skip if already added from environment variable
341                    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
362/// Parse ICD manifest JSON
363fn parse_icd_manifest(path: &Path) -> Option<ICDManifest> {
364    let content = fs::read_to_string(path).ok()?;
365    
366    // Parse JSON using serde_json
367    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
383/// Parse API version from manifest string like "1.3.268" into VK_MAKE_VERSION
384fn 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        // By default, aggregated mode should be disabled unless env var is set
408        std::env::remove_var("KRONOS_AGGREGATE_ICD");
409        assert!(!aggregated_mode_enabled());
410    }
411}
412
413/// Return all loadable ICDs with metadata (does not mutate global state)
414pub 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            // Build candidate library paths; prefer absolute, else try as-provided and manifest-relative
421            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            // Attempt to load first working candidate for this manifest
432            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; // one entry per manifest
449                }
450            }
451        }
452    }
453
454    out
455}
456
457// Preferred ICD selection (process-wide for now)
458#[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
486/// Get info for the currently selected/loaded ICD (if any)
487pub 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
500// ===== Phase 4.1: Handle provenance registry (public helpers) =====
501
502fn 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
555/// Load an ICD library
556fn 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        // Trust common system locations by default
576        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        // Program Files (both x64/x86)
581        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        // Conservative default on other platforms
591        true
592    }
593}
594
595pub fn load_icd(library_path: &Path) -> Result<LoadedICD, IcdError> {
596    // SAFETY: This function uses unsafe operations for:
597    // 1. dlopen/dlsym - We ensure the library path is valid and null-terminated
598    // 2. Function pointer transmutation - We trust the Vulkan ICD to provide correct function signatures
599    // 3. The loaded library handle is kept alive for the lifetime of LoadedICD
600    unsafe {
601        // Resolve and validate the library path
602        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        // Load the library (cross-platform)
616        #[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        // Get vk_icdGetInstanceProcAddr (ICD entry point)
633        #[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        // Get global functions
654        let mut icd = LoadedICD {
655            library_path: canon,
656            // Keep library alive for process lifetime. On Windows, store as opaque pointer.
657            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 and propagate failure instead of silently ignoring it
735        load_global_functions_inner(&mut icd)?;
736        Ok(icd)
737    }
738}
739
740/// Load global function pointers
741///
742/// # Safety
743///
744/// This function is unsafe because:
745/// - It calls vkGetInstanceProcAddr through a function pointer
746/// - It transmutes void pointers to function pointers without type checking
747/// - The caller must ensure icd contains a valid vkGetInstanceProcAddr function pointer
748/// - The ICD library must be loaded and remain valid for the lifetime of icd
749/// - Function signatures must match the Vulkan specification exactly
750/// - Incorrect function pointers will cause undefined behavior when called
751unsafe 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    // Helper macro to load functions
756    macro_rules! load_fn {
757        ($name:ident, $fn_name:expr) => {
758            // These are static strings, so they won't have null bytes
759            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 instance creation functions
768    load_fn!(create_instance, "vkCreateInstance");
769    
770    debug!("Loaded global functions - create_instance: {:?}",
771           icd.create_instance.is_some());
772    
773    Ok(())
774}
775
776/// Load instance-level functions
777///
778/// # Safety
779///
780/// This function is unsafe because:
781/// - It calls vkGetInstanceProcAddr with the provided instance handle
782/// - It transmutes void pointers to function pointers without type checking
783/// - The instance must be a valid VkInstance created by this ICD
784/// - The icd must contain valid function pointers from the same ICD that created the instance
785/// - The instance must remain valid for at least as long as these functions are used
786/// - Using an invalid instance handle will cause undefined behavior
787/// - Function signatures must match the Vulkan specification exactly
788pub 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 instance functions
803    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
817/// Load device-level functions
818///
819/// # Safety
820///
821/// This function is unsafe because:
822/// - It calls vkGetDeviceProcAddr or vkGetInstanceProcAddr with device/instance handles
823/// - It transmutes void pointers to function pointers without type checking
824/// - The device must be a valid VkDevice created by this ICD
825/// - The icd must contain valid function pointers from the same ICD that created the device
826/// - The device must remain valid for at least as long as these functions are used
827/// - Using an invalid device handle will cause undefined behavior
828/// - Function signatures must match the Vulkan specification exactly
829/// - The fallback to instance proc addr requires a valid instance context
830pub unsafe fn load_device_functions_inner(icd: &mut LoadedICD, device: VkDevice) -> Result<(), IcdError> {
831    // Prefer device-level loader; do not fall back to NULL instance, which is invalid.
832    let get_device_proc_fn = icd
833        .get_device_proc_addr
834        .ok_or(IcdError::MissingFunction("vkGetDeviceProcAddr not loaded"))?;
835    
836    // Helper function to get proc address strictly via device
837    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    // Device functions
852    load_fn!(destroy_device, "vkDestroyDevice");
853    load_fn!(get_device_queue, "vkGetDeviceQueue");
854    load_fn!(device_wait_idle, "vkDeviceWaitIdle");
855    
856    // Queue functions
857    load_fn!(queue_submit, "vkQueueSubmit");
858    load_fn!(queue_wait_idle, "vkQueueWaitIdle");
859    
860    // Memory functions
861    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    // Buffer functions
867    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    // Compute-specific functions
873    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    // Sync functions
906    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    // Timeline semaphore functions (optional)
926    load_fn!(wait_semaphores, "vkWaitSemaphores");
927    
928    Ok(())
929}
930
931/// Initialize the ICD loader
932pub 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    // Check if we have environment variable override
944    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    // Collect all successfully loaded ICDs with priority flag
952    let mut loaded_icds = Vec::new();
953    
954    // Try to load each ICD
955    for (idx, icd_file) in icd_files.iter().enumerate() {
956        if let Some(manifest) = parse_icd_manifest(&icd_file) {
957            // Build candidate library paths. Prefer the path as provided to allow
958            // dynamic linker search to resolve common locations (e.g. /usr/lib). As a
959            // fallback, try relative to the manifest directory.
960            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                // As provided (lets dlopen search LD_LIBRARY_PATH etc.)
965                candidates.push(PathBuf::from(&manifest.library_path));
966                // Fallback relative to manifest directory
967                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                // Canonicalize candidate for validation
975                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                    // Check if this is a software renderer
990                    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                    // Environment variable ICDs are prioritized (first N entries from discover_icds)
996                    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    // Optional policy: if any hardware ICDs are present, prefer them over software by filtering
1014    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    // Sort ICDs: env priority first, then hardware (already filtered if policy), then software renderers
1024    loaded_icds.sort_by_key(|(_, is_software, is_env_priority)| {
1025        (!is_env_priority, *is_software)
1026    });
1027    
1028    // Log all available ICDs
1029    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    // Store all ICDs for aggregated mode BEFORE selecting the best one
1034    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    // Check for explicit preference
1041    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        // Use the best ICD (first in sorted list)
1063        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    // In aggregated mode, log that we're using multiple ICDs
1076    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
1086/// Get the loaded ICD (shared clone)
1087pub fn get_icd() -> Option<Arc<LoadedICD>> {
1088    ICD_LOADER.lock().ok()?.as_ref().cloned()
1089}
1090
1091/// Apply a mutation to the current ICD by replacing it with an updated copy
1092fn 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
1104/// Update instance-level function pointers for the current ICD
1105pub unsafe fn update_instance_functions(instance: VkInstance) -> Result<(), IcdError> {
1106    replace_icd(|icd| load_instance_functions_inner(icd, instance))
1107}
1108
1109/// Update device-level function pointers for the current ICD
1110pub unsafe fn update_device_functions(device: VkDevice) -> Result<(), IcdError> {
1111    replace_icd(|icd| load_device_functions_inner(icd, device))
1112}
1113
1114/// Load instance functions for a specific ICD (used in aggregated mode)
1115pub unsafe fn load_instance_functions_for_icd(icd: &mut LoadedICD, instance: VkInstance) -> Result<(), IcdError> {
1116    load_instance_functions_inner(icd, instance)
1117}