keyhog_scanner/compiled_scanner/
types.rs1use crate::hw_probe::ScanBackend;
4use std::sync::{Arc, OnceLock};
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum GpuInitPolicy {
8 FromRuntimePolicy,
10 ForceEnabled,
13 ForceDisabled,
17}
18
19pub(crate) struct GpuBackendPeers {
20 cuda: OnceLock<Result<AcquiredGpuPeer, String>>,
21 wgpu: OnceLock<Result<AcquiredGpuPeer, String>>,
22 pub(crate) cuda_available: bool,
23 pub(crate) wgpu_available: bool,
24 pub(crate) cuda_device_identity: Option<String>,
25 pub(crate) cuda_runtime_identity: Option<String>,
26 pub(crate) wgpu_device_identity: Option<String>,
27 pub(crate) wgpu_runtime_identity: Option<String>,
28 pub(crate) wgpu_is_software: bool,
29}
30
31pub(crate) struct AcquiredGpuPeer {
32 pub(crate) backend: Arc<dyn vyre::VyreBackend>,
33 pub(crate) device_identity: Option<String>,
34 pub(crate) is_software: bool,
35}
36
37impl Default for GpuBackendPeers {
38 fn default() -> Self {
39 Self {
40 cuda: OnceLock::new(),
41 wgpu: OnceLock::new(),
42 cuda_available: false,
43 wgpu_available: false,
44 cuda_device_identity: None,
45 cuda_runtime_identity: None,
46 wgpu_device_identity: None,
47 wgpu_runtime_identity: None,
48 wgpu_is_software: false,
49 }
50 }
51}
52
53impl GpuBackendPeers {
54 pub(crate) fn get(&self, backend: ScanBackend) -> Option<&Arc<dyn vyre::VyreBackend>> {
55 match backend {
56 ScanBackend::GpuCuda if self.cuda_available => self
57 .cuda
58 .get_or_init(acquire_cuda_peer)
59 .as_ref()
60 .ok()
62 .map(|peer| &peer.backend),
63 ScanBackend::GpuWgpu if self.wgpu_available => self
64 .wgpu
65 .get_or_init(acquire_wgpu_peer)
66 .as_ref()
67 .ok()
69 .map(|peer| &peer.backend),
70 _ => None,
71 }
72 }
73
74 pub(crate) fn initialized(&self, backend: ScanBackend) -> Option<&AcquiredGpuPeer> {
75 match backend {
76 ScanBackend::GpuCuda => self.cuda.get().and_then(|result| result.as_ref().ok()),
78 ScanBackend::GpuWgpu => self.wgpu.get().and_then(|result| result.as_ref().ok()),
80 _ => None,
81 }
82 }
83
84 pub(crate) fn initialization_error(&self, backend: ScanBackend) -> Option<&str> {
85 match backend {
86 ScanBackend::GpuCuda => self.cuda.get(),
87 ScanBackend::GpuWgpu => self.wgpu.get(),
88 _ => None,
89 }
90 .and_then(|result| result.as_ref().err().map(String::as_str))
91 }
92
93 pub(crate) fn availability(&self) -> GpuBackendAvailability {
94 GpuBackendAvailability {
95 cuda: self.cuda_available,
96 wgpu: self.wgpu_available,
97 }
98 }
99}
100
101#[cfg(all(feature = "gpu", target_os = "linux"))]
102fn acquire_cuda_peer() -> Result<AcquiredGpuPeer, String> {
103 let backend = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
104 let cuda = vyre_driver_cuda::backend::CudaBackend::acquire()?;
105 let boxed: Box<dyn vyre::VyreBackend> =
106 Box::new(vyre_driver_cuda::CudaBackendRegistration::new(cuda));
107 Ok::<Arc<dyn vyre::VyreBackend>, String>(Arc::from(boxed))
108 }))
109 .map_err(|panic| {
110 format!(
111 "CUDA backend acquisition panicked: {}. Fix: repair the CUDA driver/runtime or select another calibrated backend",
112 crate::error::panic_payload_detail(panic)
113 )
114 })??;
115 tracing::info!(target: "keyhog::routing", "selected CUDA peer backend acquired");
116 Ok(AcquiredGpuPeer {
117 backend,
118 device_identity: None,
119 is_software: false,
120 })
121}
122
123#[cfg(not(all(feature = "gpu", target_os = "linux")))]
124fn acquire_cuda_peer() -> Result<AcquiredGpuPeer, String> {
125 Err("CUDA peer is not compiled for this platform".to_string())
126}
127
128#[cfg(feature = "gpu")]
129fn acquire_wgpu_peer() -> Result<AcquiredGpuPeer, String> {
130 let backend = std::panic::catch_unwind(std::panic::AssertUnwindSafe(
131 vyre_driver_wgpu::WgpuBackend::shared,
132 ))
133 .map_err(|panic| {
134 format!(
135 "WGPU backend acquisition panicked: {}. Fix: repair the graphics driver/runtime or select another calibrated backend",
136 crate::error::panic_payload_detail(panic)
137 )
138 })?
139 .map_err(|error| error.to_string())?;
140 let info = backend.adapter_info();
141 let device_identity =
142 crate::gpu::gpu_adapter_device_identity(info, backend.device_limits().max_buffer_size);
143 let is_software = crate::gpu::is_software_adapter(info);
144 tracing::info!(
145 target: "keyhog::routing",
146 device_identity,
147 "selected WGPU peer backend acquired"
148 );
149 let backend: Arc<dyn vyre::VyreBackend> = backend;
150 Ok(AcquiredGpuPeer {
151 backend,
152 device_identity: Some(device_identity),
153 is_software,
154 })
155}
156
157#[cfg(all(feature = "gpu", target_os = "linux"))]
158pub(crate) fn probe_cuda_peer() -> Result<vyre_driver_cuda::device::CudaDeviceCaps, String> {
159 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
160 vyre_driver_cuda::device::CudaDeviceCaps::probe(0)
161 }))
162 .map_err(|panic| {
163 format!(
164 "CUDA device probe panicked: {}. Fix: repair the CUDA driver/runtime before enabling this backend",
165 crate::error::panic_payload_detail(panic)
166 )
167 })?
168 .map_err(|error| error.to_string())
169}
170
171#[cfg(not(feature = "gpu"))]
172fn acquire_wgpu_peer() -> Result<AcquiredGpuPeer, String> {
173 Err("WGPU peer is not compiled in this build".to_string())
174}
175
176#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
177pub struct GpuBackendAvailability {
178 pub cuda: bool,
179 pub wgpu: bool,
180}
181
182impl GpuBackendAvailability {
183 #[must_use]
184 pub const fn any(self) -> bool {
185 self.cuda || self.wgpu
186 }
187}
188
189#[derive(Clone, Debug, Eq, PartialEq)]
190pub(crate) struct GpuBackendAcquisitionFailure {
191 pub backend: &'static str,
192 pub diagnostic: String,
193}
194
195#[derive(Clone, Debug, Eq, PartialEq)]
196pub struct GpuBackendCandidateStatus {
197 pub backend: ScanBackend,
198 pub available: bool,
201 pub acquired: bool,
203 pub driver_id: Option<&'static str>,
204 pub driver_version: Option<&'static str>,
205 pub device_identity: Option<String>,
206 pub runtime_identity: Option<String>,
207 pub is_software: bool,
208 pub acquisition_error: Option<String>,
209}
210
211impl GpuBackendCandidateStatus {
212 #[must_use]
213 pub fn has_complete_identity(&self) -> bool {
214 self.driver_id.is_some_and(|value| !value.trim().is_empty())
215 && self
216 .driver_version
217 .is_some_and(|value| !value.trim().is_empty())
218 && self
219 .device_identity
220 .as_deref()
221 .is_some_and(|value| !value.trim().is_empty())
222 && self
223 .runtime_identity
224 .as_deref()
225 .is_some_and(|value| !value.trim().is_empty())
226 }
227
228 #[must_use]
232 pub fn is_eligible(&self) -> bool {
233 self.available && !self.is_software && self.has_complete_identity()
234 }
235
236 #[must_use]
238 pub fn is_acquired_eligible(&self) -> bool {
239 self.acquired && self.is_eligible()
240 }
241}
242
243#[derive(Clone, Copy, Debug, Eq, PartialEq)]
244pub struct CompiledScannerRuntime {
245 pub detector_count: usize,
246 pub pattern_count: usize,
247 pub detector_digest: u64,
250 pub preferred_backend: &'static str,
253 pub gpu_backends: GpuBackendAvailability,
254 pub gpu_degrade_count: u64,
255}