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    let mut out = Vec::new();
261    let icd_files = discover_icds();
262    if icd_files.is_empty() { return out; }
263
264    let prefer_hardware = env::var("KRONOS_PREFER_HARDWARE").map(|v| v != "0").unwrap_or(true);
265
266    for icd_file in &icd_files {
267        if let Some(manifest) = parse_icd_manifest(icd_file) {
268            let mut candidates: Vec<PathBuf> = Vec::new();
269            if Path::new(&manifest.library_path).is_absolute() {
270                candidates.push(PathBuf::from(&manifest.library_path));
271            } else {
272                candidates.push(PathBuf::from(&manifest.library_path));
273                if let Some(parent) = icd_file.parent() { candidates.push(parent.join(&manifest.library_path)); }
274            }
275            for cand in &candidates {
276                let can = fs::canonicalize(cand).unwrap_or(cand.clone());
277                if let Ok(icd) = load_icd(&can) {
278                    let arc = Arc::new(icd);
279                    out.push(arc);
280                    break;
281                }
282            }
283        }
284    }
285
286    if prefer_hardware {
287        let any_hw = out.iter().any(|icd| {
288            let s = icd.library_path.to_string_lossy();
289            !(s.contains("lvp") || s.contains("swrast") || s.contains("llvmpipe"))
290        });
291        if any_hw {
292            out.retain(|icd| {
293                let s = icd.library_path.to_string_lossy();
294                !(s.contains("lvp") || s.contains("swrast") || s.contains("llvmpipe"))
295            });
296        }
297    }
298    out
299}
300
301/// Find and load Vulkan ICDs
302pub fn discover_icds() -> Vec<PathBuf> {
303    let mut icd_files = Vec::new();
304    let mut env_icds = Vec::new();
305    
306    // Check environment variable - these will be prioritized but not exclusive
307    if let Ok(icd_filenames) = env::var("VK_ICD_FILENAMES") {
308        let separator = if cfg!(windows) { ';' } else { ':' };
309        for path in icd_filenames.split(separator) {
310            let raw = PathBuf::from(path);
311            let can = fs::canonicalize(&raw).unwrap_or(raw);
312            if can.exists() {
313                env_icds.push(can.clone());
314                icd_files.push(can);
315            } else {
316                warn!("VK_ICD_FILENAMES contains non-existent path: {}", path);
317            }
318        }
319        if !env_icds.is_empty() {
320            info!("Found {} ICD files from VK_ICD_FILENAMES (will be prioritized)", env_icds.len());
321        }
322    }
323    
324    // Always search platform-specific paths for all available ICDs
325    let search_paths = get_icd_search_paths();
326    for search_path in &search_paths {
327        if let Ok(entries) = fs::read_dir(search_path) {
328            let mut path_count = 0;
329            for entry in entries.flatten() {
330                let path = entry.path();
331                if path.extension().and_then(|s| s.to_str()) == Some("json") {
332                    // Skip if already added from environment variable
333                    if !env_icds.contains(&path) {
334                        let can = fs::canonicalize(&path).unwrap_or(path);
335                        log::debug!("Discovered ICD candidate: {}", can.display());
336                        icd_files.push(can);
337                        path_count += 1;
338                    }
339                }
340            }
341            if path_count > 0 {
342                info!("Found {} additional ICD manifest files in {}", path_count, search_path.display());
343            }
344        }
345    }
346    
347    if icd_files.is_empty() {
348        warn!("No ICD manifest files found in any search paths: {:#?}", search_paths);
349    }
350    
351    icd_files
352}
353
354/// Parse ICD manifest JSON
355fn parse_icd_manifest(path: &Path) -> Option<ICDManifest> {
356    let content = fs::read_to_string(path).ok()?;
357    
358    // Parse JSON using serde_json
359    match serde_json::from_str::<ICDManifestRoot>(&content) {
360        Ok(manifest_root) => {
361            if manifest_root.icd.library_path.is_empty() {
362                warn!("ICD manifest has empty library_path: {}", path.display());
363                return None;
364            }
365            debug!("Successfully parsed ICD manifest: {} -> {}", path.display(), manifest_root.icd.library_path);
366            Some(manifest_root.icd)
367        }
368        Err(e) => {
369            warn!("Failed to parse ICD manifest {}: {}", path.display(), e);
370            None
371        }
372    }
373}
374
375/// Parse API version from manifest string like "1.3.268" into VK_MAKE_VERSION
376fn parse_api_version(version: &str) -> Option<u32> {
377    let mut parts = version.split('.');
378    let major = parts.next()?.parse::<u32>().ok()?;
379    let minor = parts.next().and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
380    let patch = parts.next().and_then(|s| s.parse::<u32>().ok()).unwrap_or(0);
381    Some(VK_MAKE_VERSION(major, minor, patch))
382}
383
384#[cfg(test)]
385mod tests {
386    use super::*;
387
388    #[test]
389    fn test_parse_api_version() {
390        assert_eq!(parse_api_version("1.2.176"), Some(VK_MAKE_VERSION(1,2,176)));
391        assert_eq!(parse_api_version("1.3"), Some(VK_MAKE_VERSION(1,3,0)));
392        assert_eq!(parse_api_version("2"), Some(VK_MAKE_VERSION(2,0,0)));
393        assert_eq!(parse_api_version(""), None);
394        assert_eq!(parse_api_version("a.b.c"), None);
395    }
396
397    #[test]
398    fn test_aggregated_mode_default_off() {
399        // By default, aggregated mode should be disabled unless env var is set
400        std::env::remove_var("KRONOS_AGGREGATE_ICD");
401        assert!(!aggregated_mode_enabled());
402    }
403}
404
405/// Return all loadable ICDs with metadata (does not mutate global state)
406pub fn available_icds() -> Vec<IcdInfo> {
407    let mut out = Vec::new();
408    let icd_files = discover_icds();
409
410    for icd_file in &icd_files {
411        if let Some(manifest) = parse_icd_manifest(icd_file) {
412            // Build candidate library paths; prefer absolute, else try as-provided and manifest-relative
413            let mut candidates: Vec<PathBuf> = Vec::new();
414            if Path::new(&manifest.library_path).is_absolute() {
415                candidates.push(PathBuf::from(&manifest.library_path));
416            } else {
417                candidates.push(PathBuf::from(&manifest.library_path));
418                if let Some(parent) = icd_file.parent() {
419                    candidates.push(parent.join(&manifest.library_path));
420                }
421            }
422
423            // Attempt to load first working candidate for this manifest
424            for cand in &candidates {
425                if let Ok(icd) = load_icd(cand) {
426                    let path_str = icd.library_path.to_string_lossy();
427                    let is_software = path_str.contains("lvp") || path_str.contains("swrast") || path_str.contains("llvmpipe");
428                    let api_version = manifest
429                        .api_version
430                        .as_deref()
431                        .and_then(parse_api_version)
432                        .unwrap_or(icd.api_version);
433
434                    out.push(IcdInfo {
435                        library_path: icd.library_path,
436                        manifest_path: Some(icd_file.clone()),
437                        api_version,
438                        is_software,
439                    });
440                    break; // one entry per manifest
441                }
442            }
443        }
444    }
445
446    out
447}
448
449// Preferred ICD selection (process-wide for now)
450#[derive(Debug, Clone)]
451enum IcdPreference {
452    Path(PathBuf),
453    Index(usize),
454}
455
456lazy_static::lazy_static! {
457    static ref PREFERRED_ICD: Mutex<Option<IcdPreference>> = Mutex::new(None);
458}
459
460pub fn set_preferred_icd_path<P: Into<PathBuf>>(path: P) {
461    if let Ok(mut pref) = PREFERRED_ICD.lock() {
462        *pref = Some(IcdPreference::Path(path.into()));
463    }
464}
465
466pub fn set_preferred_icd_index(index: usize) {
467    if let Ok(mut pref) = PREFERRED_ICD.lock() {
468        *pref = Some(IcdPreference::Index(index));
469    }
470}
471
472pub fn clear_preferred_icd() {
473    if let Ok(mut pref) = PREFERRED_ICD.lock() {
474        *pref = None;
475    }
476}
477
478/// Get info for the currently selected/loaded ICD (if any)
479pub fn selected_icd_info() -> Option<IcdInfo> {
480    let icd = get_icd()?;
481    let path = icd.library_path.clone();
482    let path_str = path.to_string_lossy();
483    let is_software = path_str.contains("lvp") || path_str.contains("swrast") || path_str.contains("llvmpipe");
484    Some(IcdInfo {
485        library_path: path,
486        manifest_path: None,
487        api_version: icd.api_version,
488        is_software,
489    })
490}
491
492// ===== Phase 4.1: Handle provenance registry (public helpers) =====
493
494fn upgrade_icd(w: &Weak<LoadedICD>) -> Option<Arc<LoadedICD>> { w.upgrade() }
495
496pub fn register_instance_icd(instance: VkInstance, icd: &Arc<LoadedICD>) {
497    let _ = REG_INSTANCES.lock().map(|mut m| { m.insert(instance.as_raw(), Arc::downgrade(icd)); });
498}
499pub fn register_physical_device_icd(phys: VkPhysicalDevice, icd: &Arc<LoadedICD>) {
500    let _ = REG_PHYS_DEVS.lock().map(|mut m| { m.insert(phys.as_raw(), Arc::downgrade(icd)); });
501}
502pub fn register_device_icd(device: VkDevice, icd: &Arc<LoadedICD>) {
503    let _ = REG_DEVICES.lock().map(|mut m| { m.insert(device.as_raw(), Arc::downgrade(icd)); });
504}
505pub fn register_queue_icd(queue: VkQueue, icd: &Arc<LoadedICD>) {
506    let _ = REG_QUEUES.lock().map(|mut m| { m.insert(queue.as_raw(), Arc::downgrade(icd)); });
507}
508pub fn register_command_pool_icd(pool: VkCommandPool, icd: &Arc<LoadedICD>) {
509    let _ = REG_CMD_POOLS.lock().map(|mut m| { m.insert(pool.as_raw(), Arc::downgrade(icd)); });
510}
511pub fn register_command_buffer_icd(cb: VkCommandBuffer, icd: &Arc<LoadedICD>) {
512    let _ = REG_CMD_BUFFERS.lock().map(|mut m| { m.insert(cb.as_raw(), Arc::downgrade(icd)); });
513}
514
515pub fn unregister_instance(instance: VkInstance) { let _ = REG_INSTANCES.lock().map(|mut m| { m.remove(&instance.as_raw()); }); }
516pub fn unregister_physical_device(phys: VkPhysicalDevice) { let _ = REG_PHYS_DEVS.lock().map(|mut m| { m.remove(&phys.as_raw()); }); }
517pub fn unregister_device(device: VkDevice) { let _ = REG_DEVICES.lock().map(|mut m| { m.remove(&device.as_raw()); }); }
518pub fn unregister_queue(queue: VkQueue) { let _ = REG_QUEUES.lock().map(|mut m| { m.remove(&queue.as_raw()); }); }
519pub fn unregister_command_pool(pool: VkCommandPool) { let _ = REG_CMD_POOLS.lock().map(|mut m| { m.remove(&pool.as_raw()); }); }
520pub fn unregister_command_buffer(cb: VkCommandBuffer) { let _ = REG_CMD_BUFFERS.lock().map(|mut m| { m.remove(&cb.as_raw()); }); }
521
522pub fn icd_for_instance(instance: VkInstance) -> Option<Arc<LoadedICD>> {
523    REG_INSTANCES.lock().ok()?.get(&instance.as_raw()).and_then(upgrade_icd)
524        .or_else(|| get_icd())
525}
526pub fn icd_for_physical_device(phys: VkPhysicalDevice) -> Option<Arc<LoadedICD>> {
527    REG_PHYS_DEVS.lock().ok()?.get(&phys.as_raw()).and_then(upgrade_icd)
528        .or_else(|| icd_for_instance(VkInstance::NULL))
529}
530pub fn icd_for_device(device: VkDevice) -> Option<Arc<LoadedICD>> {
531    REG_DEVICES.lock().ok()?.get(&device.as_raw()).and_then(upgrade_icd)
532        .or_else(|| get_icd())
533}
534pub fn icd_for_queue(queue: VkQueue) -> Option<Arc<LoadedICD>> {
535    REG_QUEUES.lock().ok()?.get(&queue.as_raw()).and_then(upgrade_icd)
536        .or_else(|| get_icd())
537}
538pub fn icd_for_command_pool(pool: VkCommandPool) -> Option<Arc<LoadedICD>> {
539    REG_CMD_POOLS.lock().ok()?.get(&pool.as_raw()).and_then(upgrade_icd)
540        .or_else(|| get_icd())
541}
542pub fn icd_for_command_buffer(cb: VkCommandBuffer) -> Option<Arc<LoadedICD>> {
543    REG_CMD_BUFFERS.lock().ok()?.get(&cb.as_raw()).and_then(upgrade_icd)
544        .or_else(|| get_icd())
545}
546
547/// Load an ICD library
548fn is_trusted_library(path: &Path) -> bool {
549    if env::var("KRONOS_ALLOW_UNTRUSTED_LIBS").map(|v| v == "1").unwrap_or(false) {
550        return true;
551    }
552    #[cfg(target_os = "linux")]
553    {
554        const TRUSTED_PREFIXES: &[&str] = &[
555            "/usr/lib",
556            "/usr/lib64",
557            "/usr/local/lib",
558            "/lib",
559            "/lib64",
560            "/usr/lib/x86_64-linux-gnu",
561        ];
562        let p = path.to_string_lossy();
563        return TRUSTED_PREFIXES.iter().any(|prefix| p.starts_with(prefix));
564    }
565    #[cfg(target_os = "windows")]
566    {
567        // Trust common system locations by default
568        let p = path.to_string_lossy().to_lowercase();
569        let sysroot = env::var("SYSTEMROOT").unwrap_or_else(|_| String::from("C:\\Windows"));
570        let sys32 = format!("{}\\System32", sysroot);
571        let sys32_l = sys32.to_lowercase();
572        // Program Files (both x64/x86)
573        let pf = env::var("PROGRAMFILES").unwrap_or_default().to_lowercase();
574        let pf86 = env::var("PROGRAMFILES(X86)").unwrap_or_default().to_lowercase();
575        let trusted = p.starts_with(&sys32_l)
576            || (p.starts_with(&pf) && p.contains("vulkan"))
577            || (!pf86.is_empty() && p.starts_with(&pf86) && p.contains("vulkan"));
578        return trusted;
579    }
580    #[cfg(not(any(target_os = "linux", target_os = "windows")))]
581    {
582        // Conservative default on other platforms
583        true
584    }
585}
586
587pub fn load_icd(library_path: &Path) -> Result<LoadedICD, IcdError> {
588    // SAFETY: This function uses unsafe operations for:
589    // 1. dlopen/dlsym - We ensure the library path is valid and null-terminated
590    // 2. Function pointer transmutation - We trust the Vulkan ICD to provide correct function signatures
591    // 3. The loaded library handle is kept alive for the lifetime of LoadedICD
592    unsafe {
593        // Resolve and validate the library path
594        let canon = fs::canonicalize(library_path).unwrap_or_else(|_| library_path.to_path_buf());
595        let meta = fs::metadata(&canon)
596            .map_err(|_| IcdError::LibraryLoadFailed(format!("{} (metadata not found)", canon.display())))?;
597        if !meta.is_file() {
598            return Err(IcdError::LibraryLoadFailed(format!("{} is not a regular file", canon.display())));
599        }
600        if !is_trusted_library(&canon) {
601            return Err(IcdError::LibraryLoadFailed(format!(
602                "{} rejected by trust policy (set KRONOS_ALLOW_UNTRUSTED_LIBS=1 to override)",
603                canon.display()
604            )));
605        }
606
607        // Load the library (cross-platform)
608        #[cfg(windows)]
609        let lib = {
610            libloading::Library::new(&canon)
611                .map_err(|e| IcdError::LibraryLoadFailed(format!("{}: {}", canon.display(), e)))?
612        };
613        #[cfg(not(windows))]
614        let (handle, lib) = {
615            let lib_cstr = CString::new(canon.as_os_str().as_bytes())?;
616            let handle = libc::dlopen(lib_cstr.as_ptr(), libc::RTLD_NOW | libc::RTLD_LOCAL);
617            if handle.is_null() {
618                let error = unsafe { CStr::from_ptr(libc::dlerror()) }.to_string_lossy().into_owned();
619                return Err(IcdError::LibraryLoadFailed(format!("{}: {}", library_path.display(), error)));
620            }
621            (handle, ())
622        };
623
624        // Get vk_icdGetInstanceProcAddr (ICD entry point)
625        #[cfg(windows)]
626        let get_proc = unsafe {
627            let sym: libloading::Symbol<unsafe extern "C" fn(VkInstance, *const c_char) -> PFN_vkVoidFunction> =
628                lib.get(b"vk_icdGetInstanceProcAddr\0")
629                    .map_err(|e| IcdError::LibraryLoadFailed(format!("{}: missing vk_icdGetInstanceProcAddr ({})", canon.display(), e)))?;
630            Some(*sym)
631        };
632        #[cfg(not(windows))]
633        let get_proc = {
634            let get_instance_proc_addr_name = CString::new("vk_icdGetInstanceProcAddr")?;
635            let ptr = unsafe { libc::dlsym(handle, get_instance_proc_addr_name.as_ptr()) };
636            if ptr.is_null() {
637                unsafe { libc::dlclose(handle); }
638                return Err(IcdError::MissingFunction("vk_icdGetInstanceProcAddr"));
639            }
640            let f: PFN_vkGetInstanceProcAddr = unsafe { std::mem::transmute(ptr) };
641            f
642        };
643        let vk_get_instance_proc_addr: PFN_vkGetInstanceProcAddr = get_proc;
644        
645        // Get global functions
646        let mut icd = LoadedICD {
647            library_path: canon,
648            // Keep library alive for process lifetime. On Windows, store as opaque pointer.
649            handle: {
650                #[cfg(windows)]
651                {
652                    let boxed = Box::new(lib);
653                    Box::into_raw(boxed) as *mut c_void
654                }
655                #[cfg(not(windows))]
656                { handle as *mut c_void }
657            },
658            api_version: VK_API_VERSION_1_0,
659            vk_get_instance_proc_addr,
660            create_instance: None,
661            destroy_instance: None,
662            enumerate_physical_devices: None,
663            get_physical_device_properties: None,
664            get_physical_device_queue_family_properties: None,
665            get_physical_device_memory_properties: None,
666            create_device: None,
667            destroy_device: None,
668            get_device_proc_addr: None,
669            get_device_queue: None,
670            queue_submit: None,
671            queue_wait_idle: None,
672            device_wait_idle: None,
673            allocate_memory: None,
674            free_memory: None,
675            map_memory: None,
676            unmap_memory: None,
677            create_buffer: None,
678            destroy_buffer: None,
679            get_buffer_memory_requirements: None,
680            bind_buffer_memory: None,
681            create_descriptor_set_layout: None,
682            destroy_descriptor_set_layout: None,
683            create_descriptor_pool: None,
684            destroy_descriptor_pool: None,
685            reset_descriptor_pool: None,
686            allocate_descriptor_sets: None,
687            free_descriptor_sets: None,
688            update_descriptor_sets: None,
689            create_pipeline_layout: None,
690            destroy_pipeline_layout: None,
691            create_compute_pipelines: None,
692            destroy_pipeline: None,
693            create_shader_module: None,
694            destroy_shader_module: None,
695            create_command_pool: None,
696            destroy_command_pool: None,
697            allocate_command_buffers: None,
698            free_command_buffers: None,
699            begin_command_buffer: None,
700            end_command_buffer: None,
701            cmd_bind_pipeline: None,
702            cmd_bind_descriptor_sets: None,
703            cmd_dispatch: None,
704            cmd_dispatch_indirect: None,
705            cmd_pipeline_barrier: None,
706            cmd_copy_buffer: None,
707            cmd_push_constants: None,
708            create_fence: None,
709            destroy_fence: None,
710            reset_fences: None,
711            get_fence_status: None,
712            wait_for_fences: None,
713            create_semaphore: None,
714            destroy_semaphore: None,
715            create_event: None,
716            destroy_event: None,
717            get_event_status: None,
718            set_event: None,
719            reset_event: None,
720            cmd_set_event: None,
721            cmd_reset_event: None,
722            cmd_wait_events: None,
723            wait_semaphores: None,
724        };
725        
726        // Load global functions and propagate failure instead of silently ignoring it
727        load_global_functions_inner(&mut icd)?;
728        Ok(icd)
729    }
730}
731
732/// Load global function pointers
733///
734/// # Safety
735///
736/// This function is unsafe because:
737/// - It calls vkGetInstanceProcAddr through a function pointer
738/// - It transmutes void pointers to function pointers without type checking
739/// - The caller must ensure icd contains a valid vkGetInstanceProcAddr function pointer
740/// - The ICD library must be loaded and remain valid for the lifetime of icd
741/// - Function signatures must match the Vulkan specification exactly
742/// - Incorrect function pointers will cause undefined behavior when called
743unsafe fn load_global_functions_inner(icd: &mut LoadedICD) -> Result<(), IcdError> {
744    let get_proc_addr = icd.vk_get_instance_proc_addr
745        .ok_or(IcdError::MissingFunction("vkGetInstanceProcAddr not loaded"))?;
746    
747    // Helper macro to load functions
748    macro_rules! load_fn {
749        ($name:ident, $fn_name:expr) => {
750            // These are static strings, so they won't have null bytes
751            let name = CString::new($fn_name)
752                .expect(concat!("Invalid function name: ", $fn_name));
753            if let Some(addr) = get_proc_addr(VkInstance::NULL, name.as_ptr()) {
754                icd.$name = std::mem::transmute(addr);
755            }
756        };
757    }
758    
759    // Load instance creation functions
760    load_fn!(create_instance, "vkCreateInstance");
761    
762    debug!("Loaded global functions - create_instance: {:?}",
763           icd.create_instance.is_some());
764    
765    Ok(())
766}
767
768/// Load instance-level functions
769///
770/// # Safety
771///
772/// This function is unsafe because:
773/// - It calls vkGetInstanceProcAddr with the provided instance handle
774/// - It transmutes void pointers to function pointers without type checking
775/// - The instance must be a valid VkInstance created by this ICD
776/// - The icd must contain valid function pointers from the same ICD that created the instance
777/// - The instance must remain valid for at least as long as these functions are used
778/// - Using an invalid instance handle will cause undefined behavior
779/// - Function signatures must match the Vulkan specification exactly
780pub unsafe fn load_instance_functions_inner(icd: &mut LoadedICD, instance: VkInstance) -> Result<(), IcdError> {
781    let get_proc_addr = icd.vk_get_instance_proc_addr
782        .ok_or(IcdError::MissingFunction("vkGetInstanceProcAddr not loaded"))?;
783    
784    macro_rules! load_fn {
785        ($name:ident, $fn_name:expr) => {
786            let name = CString::new($fn_name)
787                .expect(concat!("Invalid function name: ", $fn_name));
788            if let Some(addr) = get_proc_addr(instance, name.as_ptr()) {
789                icd.$name = std::mem::transmute(addr);
790            }
791        };
792    }
793    
794    // Load instance functions
795    load_fn!(destroy_instance, "vkDestroyInstance");
796    load_fn!(enumerate_physical_devices, "vkEnumeratePhysicalDevices");
797    load_fn!(get_physical_device_properties, "vkGetPhysicalDeviceProperties");
798    load_fn!(get_physical_device_queue_family_properties, "vkGetPhysicalDeviceQueueFamilyProperties");
799    load_fn!(get_physical_device_memory_properties, "vkGetPhysicalDeviceMemoryProperties");
800    load_fn!(create_device, "vkCreateDevice");
801    load_fn!(get_device_proc_addr, "vkGetDeviceProcAddr");
802    
803    debug!("Loaded instance functions - enumerate_physical_devices: {:?}",
804           icd.enumerate_physical_devices.is_some());
805    
806    Ok(())
807}
808
809/// Load device-level functions
810///
811/// # Safety
812///
813/// This function is unsafe because:
814/// - It calls vkGetDeviceProcAddr or vkGetInstanceProcAddr with device/instance handles
815/// - It transmutes void pointers to function pointers without type checking
816/// - The device must be a valid VkDevice created by this ICD
817/// - The icd must contain valid function pointers from the same ICD that created the device
818/// - The device must remain valid for at least as long as these functions are used
819/// - Using an invalid device handle will cause undefined behavior
820/// - Function signatures must match the Vulkan specification exactly
821/// - The fallback to instance proc addr requires a valid instance context
822pub unsafe fn load_device_functions_inner(icd: &mut LoadedICD, device: VkDevice) -> Result<(), IcdError> {
823    // Prefer device-level loader; do not fall back to NULL instance, which is invalid.
824    let get_device_proc_fn = icd
825        .get_device_proc_addr
826        .ok_or(IcdError::MissingFunction("vkGetDeviceProcAddr not loaded"))?;
827    
828    // Helper function to get proc address strictly via device
829    let get_proc_addr_helper = |name: *const c_char| -> PFN_vkVoidFunction {
830        get_device_proc_fn(device, name)
831    };
832    
833    macro_rules! load_fn {
834        ($name:ident, $fn_name:expr) => {
835            let name = CString::new($fn_name)
836                .expect(concat!("Invalid function name: ", $fn_name));
837            if let Some(addr) = get_proc_addr_helper(name.as_ptr()) {
838                icd.$name = std::mem::transmute(addr);
839            }
840        };
841    }
842    
843    // Device functions
844    load_fn!(destroy_device, "vkDestroyDevice");
845    load_fn!(get_device_queue, "vkGetDeviceQueue");
846    load_fn!(device_wait_idle, "vkDeviceWaitIdle");
847    
848    // Queue functions
849    load_fn!(queue_submit, "vkQueueSubmit");
850    load_fn!(queue_wait_idle, "vkQueueWaitIdle");
851    
852    // Memory functions
853    load_fn!(allocate_memory, "vkAllocateMemory");
854    load_fn!(free_memory, "vkFreeMemory");
855    load_fn!(map_memory, "vkMapMemory");
856    load_fn!(unmap_memory, "vkUnmapMemory");
857    
858    // Buffer functions
859    load_fn!(create_buffer, "vkCreateBuffer");
860    load_fn!(destroy_buffer, "vkDestroyBuffer");
861    load_fn!(get_buffer_memory_requirements, "vkGetBufferMemoryRequirements");
862    load_fn!(bind_buffer_memory, "vkBindBufferMemory");
863    
864    // Compute-specific functions
865    load_fn!(create_descriptor_set_layout, "vkCreateDescriptorSetLayout");
866    load_fn!(destroy_descriptor_set_layout, "vkDestroyDescriptorSetLayout");
867    load_fn!(create_descriptor_pool, "vkCreateDescriptorPool");
868    load_fn!(destroy_descriptor_pool, "vkDestroyDescriptorPool");
869    load_fn!(reset_descriptor_pool, "vkResetDescriptorPool");
870    load_fn!(allocate_descriptor_sets, "vkAllocateDescriptorSets");
871    load_fn!(free_descriptor_sets, "vkFreeDescriptorSets");
872    load_fn!(update_descriptor_sets, "vkUpdateDescriptorSets");
873    
874    load_fn!(create_pipeline_layout, "vkCreatePipelineLayout");
875    load_fn!(destroy_pipeline_layout, "vkDestroyPipelineLayout");
876    load_fn!(create_compute_pipelines, "vkCreateComputePipelines");
877    load_fn!(destroy_pipeline, "vkDestroyPipeline");
878    
879    load_fn!(create_shader_module, "vkCreateShaderModule");
880    load_fn!(destroy_shader_module, "vkDestroyShaderModule");
881    
882    load_fn!(create_command_pool, "vkCreateCommandPool");
883    load_fn!(destroy_command_pool, "vkDestroyCommandPool");
884    load_fn!(allocate_command_buffers, "vkAllocateCommandBuffers");
885    load_fn!(free_command_buffers, "vkFreeCommandBuffers");
886    load_fn!(begin_command_buffer, "vkBeginCommandBuffer");
887    load_fn!(end_command_buffer, "vkEndCommandBuffer");
888    
889    load_fn!(cmd_bind_pipeline, "vkCmdBindPipeline");
890    load_fn!(cmd_bind_descriptor_sets, "vkCmdBindDescriptorSets");
891    load_fn!(cmd_dispatch, "vkCmdDispatch");
892    load_fn!(cmd_dispatch_indirect, "vkCmdDispatchIndirect");
893    load_fn!(cmd_pipeline_barrier, "vkCmdPipelineBarrier");
894    load_fn!(cmd_copy_buffer, "vkCmdCopyBuffer");
895    load_fn!(cmd_push_constants, "vkCmdPushConstants");
896    
897    // Sync functions
898    load_fn!(create_fence, "vkCreateFence");
899    load_fn!(destroy_fence, "vkDestroyFence");
900    load_fn!(reset_fences, "vkResetFences");
901    load_fn!(get_fence_status, "vkGetFenceStatus");
902    load_fn!(wait_for_fences, "vkWaitForFences");
903    
904    load_fn!(create_semaphore, "vkCreateSemaphore");
905    load_fn!(destroy_semaphore, "vkDestroySemaphore");
906    
907    load_fn!(create_event, "vkCreateEvent");
908    load_fn!(destroy_event, "vkDestroyEvent");
909    load_fn!(get_event_status, "vkGetEventStatus");
910    load_fn!(set_event, "vkSetEvent");
911    load_fn!(reset_event, "vkResetEvent");
912    
913    load_fn!(cmd_set_event, "vkCmdSetEvent");
914    load_fn!(cmd_reset_event, "vkCmdResetEvent");
915    load_fn!(cmd_wait_events, "vkCmdWaitEvents");
916    
917    // Timeline semaphore functions (optional)
918    load_fn!(wait_semaphores, "vkWaitSemaphores");
919    
920    Ok(())
921}
922
923/// Initialize the ICD loader
924pub fn initialize_icd_loader() -> Result<(), IcdError> {
925    info!("Initializing ICD loader...");
926    let icd_files = discover_icds();
927    
928    if icd_files.is_empty() {
929        warn!("No ICD manifest files found");
930        return Err(IcdError::NoManifestsFound);
931    }
932    
933    info!("Found {} ICD manifest files", icd_files.len());
934    
935    // Check if we have environment variable override
936    let env_icd_count = if let Ok(icd_filenames) = env::var("VK_ICD_FILENAMES") {
937        let separator = if cfg!(windows) { ';' } else { ':' };
938        icd_filenames.split(separator).filter(|s| !s.is_empty()).count()
939    } else {
940        0
941    };
942    
943    // Collect all successfully loaded ICDs with priority flag
944    let mut loaded_icds = Vec::new();
945    
946    // Try to load each ICD
947    for (idx, icd_file) in icd_files.iter().enumerate() {
948        if let Some(manifest) = parse_icd_manifest(&icd_file) {
949            // Build candidate library paths. Prefer the path as provided to allow
950            // dynamic linker search to resolve common locations (e.g. /usr/lib). As a
951            // fallback, try relative to the manifest directory.
952            let mut candidates: Vec<PathBuf> = Vec::new();
953            if Path::new(&manifest.library_path).is_absolute() {
954                candidates.push(PathBuf::from(&manifest.library_path));
955            } else {
956                // As provided (lets dlopen search LD_LIBRARY_PATH etc.)
957                candidates.push(PathBuf::from(&manifest.library_path));
958                // Fallback relative to manifest directory
959                if let Some(parent) = icd_file.parent() {
960                    candidates.push(parent.join(&manifest.library_path));
961                }
962            }
963
964            let mut loaded_ok: Option<LoadedICD> = None;
965            for cand in &candidates {
966                // Canonicalize candidate for validation
967                let can = fs::canonicalize(cand).unwrap_or(cand.clone());
968                log::info!("Attempting to load ICD library: {} (from {})", can.display(), icd_file.display());
969                match load_icd(&can) {
970                    Ok(icd) => {
971                        loaded_ok = Some(icd);
972                        break;
973                    }
974                    Err(e) => {
975                        warn!("Failed to load candidate {}: {}", can.display(), e);
976                    }
977                }
978            }
979
980            if let Some(icd) = loaded_ok {
981                    // Check if this is a software renderer
982                    let path_str = icd.library_path.to_string_lossy();
983                    let is_software = path_str.contains("lvp") ||
984                                     path_str.contains("swrast") ||
985                                     path_str.contains("llvmpipe");
986                    
987                    // Environment variable ICDs are prioritized (first N entries from discover_icds)
988                    let is_env_priority = idx < env_icd_count;
989                    
990                    let icd_type = if is_software { "software" } else { "hardware" };
991                    let priority_str = if is_env_priority { " (VK_ICD_FILENAMES priority)" } else { "" };
992                    info!("Successfully loaded {} Vulkan ICD: {}{}", icd_type, icd.library_path.display(), priority_str);
993                    
994                    loaded_icds.push((icd, is_software, is_env_priority));
995            } else {
996                warn!("Failed to load ICD from any candidate for manifest {}", icd_file.display());
997            }
998        }
999    }
1000    
1001    if loaded_icds.is_empty() {
1002        return Err(IcdError::InvalidManifest("Failed to load any Vulkan ICD".to_string()));
1003    }
1004
1005    // Optional policy: if any hardware ICDs are present, prefer them over software by filtering
1006    let prefer_hardware = env::var("KRONOS_PREFER_HARDWARE").map(|v| v != "0").unwrap_or(true);
1007    if prefer_hardware {
1008        let any_hw = loaded_icds.iter().any(|(_, is_sw, _)| !*is_sw);
1009        if any_hw {
1010            loaded_icds.retain(|(_, is_sw, _)| !*is_sw);
1011            info!("Hardware ICDs available; software ICDs will be ignored (set KRONOS_PREFER_HARDWARE=0 to disable)");
1012        }
1013    }
1014
1015    // Sort ICDs: env priority first, then hardware (already filtered if policy), then software renderers
1016    loaded_icds.sort_by_key(|(_, is_software, is_env_priority)| {
1017        (!is_env_priority, *is_software)
1018    });
1019    
1020    // Log all available ICDs
1021    info!("Available ICDs: {} hardware, {} software", 
1022          loaded_icds.iter().filter(|(_, is_sw, _)| !is_sw).count(),
1023          loaded_icds.iter().filter(|(_, is_sw, _)| *is_sw).count());
1024    
1025    // Check for explicit preference
1026    let preferred = PREFERRED_ICD.lock().ok().and_then(|p| p.clone());
1027    let (best_icd, is_software, is_env_priority) = if let Some(pref) = preferred {
1028        match pref {
1029            IcdPreference::Path(want) => {
1030                if let Some((idx, _)) = loaded_icds.iter().enumerate().find(|(_, (icd, _, _))| icd.library_path == want) {
1031                    loaded_icds.into_iter().nth(idx).unwrap()
1032                } else {
1033                    warn!("Preferred ICD path not found: {} — falling back to default selection", want.display());
1034                    loaded_icds.into_iter().next().unwrap()
1035                }
1036            }
1037            IcdPreference::Index(i) => {
1038                if i < loaded_icds.len() {
1039                    loaded_icds.into_iter().nth(i).unwrap()
1040                } else {
1041                    warn!("Preferred ICD index {} out of range ({}); falling back to default", i, loaded_icds.len());
1042                    loaded_icds.into_iter().next().unwrap()
1043                }
1044            }
1045        }
1046    } else {
1047        // Use the best ICD (first in sorted list)
1048        loaded_icds.into_iter().next().unwrap()
1049    };
1050    
1051    if is_env_priority {
1052        info!("Using ICD specified by VK_ICD_FILENAMES: {}", best_icd.library_path.display());
1053    } else if is_software {
1054        warn!("Using software renderer - no hardware Vulkan drivers found");
1055        info!("To use hardware drivers, ensure they are installed and ICD files are in /usr/share/vulkan/icd.d/");
1056    } else {
1057        info!("Selected hardware Vulkan driver: {}", best_icd.library_path.display());
1058    }
1059    
1060    // Store all ICDs (after policy filtering) for aggregated mode
1061    let mut all_vec = Vec::new();
1062    all_vec.push(Arc::new(best_icd.clone()));
1063    // NOTE: Remaining ICDs from loaded_icds iterator were consumed above. Re-discover for completeness if needed.
1064    // For now, push only the selected best ICD; aggregated mode will re-run discovery if more are needed.
1065    *ALL_ICDS.lock()? = all_vec;
1066    
1067    *ICD_LOADER.lock()? = Some(Arc::new(best_icd));
1068    Ok(())
1069}
1070
1071/// Get the loaded ICD (shared clone)
1072pub fn get_icd() -> Option<Arc<LoadedICD>> {
1073    ICD_LOADER.lock().ok()?.as_ref().cloned()
1074}
1075
1076/// Apply a mutation to the current ICD by replacing it with an updated copy
1077fn replace_icd<F>(mutator: F) -> Result<(), IcdError>
1078where
1079    F: FnOnce(&mut LoadedICD) -> Result<(), IcdError>,
1080{
1081    let mut guard = ICD_LOADER.lock()?;
1082    let current = guard.as_ref().ok_or(IcdError::NoIcdLoaded)?;
1083    let mut updated = (**current).clone();
1084    mutator(&mut updated)?;
1085    *guard = Some(Arc::new(updated));
1086    Ok(())
1087}
1088
1089/// Update instance-level function pointers for the current ICD
1090pub unsafe fn update_instance_functions(instance: VkInstance) -> Result<(), IcdError> {
1091    replace_icd(|icd| load_instance_functions_inner(icd, instance))
1092}
1093
1094/// Update device-level function pointers for the current ICD
1095pub unsafe fn update_device_functions(device: VkDevice) -> Result<(), IcdError> {
1096    replace_icd(|icd| load_device_functions_inner(icd, device))
1097}