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