Skip to main content

sim_lib_compute_wgpu/
probe.rs

1//! Portable GPU adapter discovery and raw probe evidence.
2
3use sim_lib_compute_auto::{ComputeDeviceIdentity, ComputeEvidenceKind, ComputePhysicalEvidence};
4use wgpu::{Backends, Features, Limits};
5
6/// Requested limits and optional features passed to `wgpu`.
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct RequestedWgpuProfile {
9    /// Requested device limits.
10    pub limits: WgpuLimitEvidence,
11    /// Whether timestamp queries were requested.
12    pub timestamp_query: bool,
13    /// Whether shader f16 was requested.
14    pub shader_f16: bool,
15}
16
17impl RequestedWgpuProfile {
18    /// Captures the profile requested by a platform capsule.
19    pub fn from_parts(limits: Limits, features: Features) -> Self {
20        Self {
21            limits: WgpuLimitEvidence::from_limits(&limits),
22            timestamp_query: features.contains(Features::TIMESTAMP_QUERY),
23            shader_f16: features.contains(Features::SHADER_F16),
24        }
25    }
26}
27
28/// Limits recorded from either the requested or granted device contract.
29#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct WgpuLimitEvidence {
31    /// Maximum buffer size.
32    pub max_buffer_size: u64,
33    /// Maximum storage buffer binding size.
34    pub max_storage_buffer_binding_size: u64,
35    /// Maximum uniform buffer binding size.
36    pub max_uniform_buffer_binding_size: u64,
37    /// Minimum storage buffer offset alignment.
38    pub min_storage_buffer_offset_alignment: u32,
39    /// Minimum uniform buffer offset alignment.
40    pub min_uniform_buffer_offset_alignment: u32,
41    /// Maximum compute workgroups per dimension.
42    pub max_compute_workgroups_per_dimension: u32,
43    /// Maximum compute invocations per workgroup.
44    pub max_compute_invocations_per_workgroup: u32,
45    /// Maximum compute workgroup size x.
46    pub max_compute_workgroup_size_x: u32,
47    /// Maximum compute workgroup size y.
48    pub max_compute_workgroup_size_y: u32,
49    /// Maximum compute workgroup size z.
50    pub max_compute_workgroup_size_z: u32,
51}
52
53impl WgpuLimitEvidence {
54    /// Captures stable limit evidence from a capsule-observed device.
55    pub fn from_limits(limits: &Limits) -> Self {
56        Self {
57            max_buffer_size: limits.max_buffer_size,
58            max_storage_buffer_binding_size: limits.max_storage_buffer_binding_size,
59            max_uniform_buffer_binding_size: limits.max_uniform_buffer_binding_size,
60            min_storage_buffer_offset_alignment: limits.min_storage_buffer_offset_alignment,
61            min_uniform_buffer_offset_alignment: limits.min_uniform_buffer_offset_alignment,
62            max_compute_workgroups_per_dimension: limits.max_compute_workgroups_per_dimension,
63            max_compute_invocations_per_workgroup: limits.max_compute_invocations_per_workgroup,
64            max_compute_workgroup_size_x: limits.max_compute_workgroup_size_x,
65            max_compute_workgroup_size_y: limits.max_compute_workgroup_size_y,
66            max_compute_workgroup_size_z: limits.max_compute_workgroup_size_z,
67        }
68    }
69}
70
71/// Feature evidence recorded from a granted device.
72#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct WgpuCapabilityEvidence {
74    /// Whether timestamp queries are granted.
75    pub timestamp_query: bool,
76    /// Whether shader f16 is granted.
77    pub shader_f16: bool,
78    /// Whether primary buffers may be mapped.
79    pub mappable_primary_buffers: bool,
80}
81
82impl WgpuCapabilityEvidence {
83    /// Captures stable feature evidence from a capsule-observed device.
84    pub fn from_features(features: Features) -> Self {
85        Self {
86            timestamp_query: features.contains(Features::TIMESTAMP_QUERY),
87            shader_f16: features.contains(Features::SHADER_F16),
88            mappable_primary_buffers: features.contains(Features::MAPPABLE_PRIMARY_BUFFERS),
89        }
90    }
91}
92
93/// Adapter identity and requested/granted capability evidence.
94#[derive(Clone, Debug, PartialEq, Eq)]
95pub struct WgpuAdapterEvidence {
96    /// Deterministic ordinal assigned after sorting adapters.
97    pub ordinal: usize,
98    /// Diagnostic adapter name from `wgpu`.
99    pub name: String,
100    /// Diagnostic backend label from `wgpu`.
101    pub backend: String,
102    /// Diagnostic adapter type from `wgpu`.
103    pub adapter_type: String,
104    /// Diagnostic vendor id.
105    pub vendor: u32,
106    /// Diagnostic device id.
107    pub device: u32,
108    /// Requested profile.
109    pub requested: RequestedWgpuProfile,
110    /// Granted device limits.
111    pub granted_limits: WgpuLimitEvidence,
112    /// Granted features.
113    pub granted_features: WgpuCapabilityEvidence,
114}
115
116impl WgpuAdapterEvidence {
117    /// Sort key that keeps enumeration deterministic without treating identity
118    /// as product logic.
119    pub fn sort_key(&self) -> (&str, &str, &str, u32, u32) {
120        (
121            self.backend.as_str(),
122            self.adapter_type.as_str(),
123            self.name.as_str(),
124            self.vendor,
125            self.device,
126        )
127    }
128}
129
130/// Transfer and mapping probe evidence.
131#[derive(Clone, Debug, PartialEq, Eq)]
132pub struct TransferEvidence {
133    /// Bytes written and read back.
134    pub bytes: u64,
135    /// Whether queue write plus copy completed.
136    pub transfer_ok: bool,
137    /// Whether map-read completed and matched the payload.
138    pub mapping_ok: bool,
139}
140
141/// One bounded allocation attempt.
142#[derive(Clone, Debug, PartialEq, Eq)]
143pub struct AllocationAttempt {
144    /// Attempted byte size.
145    pub bytes: u64,
146    /// Whether creating the buffer succeeded.
147    pub success: bool,
148}
149
150/// Probe evidence required before a site is exported.
151#[derive(Clone, Debug, PartialEq, Eq)]
152pub struct ProbeEvidence {
153    /// Transfer and mapping evidence.
154    pub transfer: TransferEvidence,
155    /// Bounded allocation attempts.
156    pub allocation_attempts: Vec<AllocationAttempt>,
157}
158
159impl ProbeEvidence {
160    /// Returns true when all required probes succeeded.
161    pub fn successful(&self) -> bool {
162        self.transfer.transfer_ok
163            && self.transfer.mapping_ok
164            && self
165                .allocation_attempts
166                .iter()
167                .any(|attempt| attempt.success && attempt.bytes > 0)
168    }
169}
170
171/// One successful adapter probe.
172#[derive(Clone, Debug, PartialEq, Eq)]
173pub struct WgpuAdapterProbe {
174    /// Whether this evidence came from a real retained device or a synthetic fixture.
175    pub evidence_kind: ComputeEvidenceKind,
176    /// Claimed adapter identity for physical evidence verification.
177    pub claimed_identity: Option<ComputeDeviceIdentity>,
178    /// Observed adapter identity captured by the producer.
179    pub observed_identity: Option<ComputeDeviceIdentity>,
180    /// Adapter and capability evidence.
181    pub adapter: WgpuAdapterEvidence,
182    /// Raw probe evidence.
183    pub probe: ProbeEvidence,
184}
185
186/// A successful adapter probe with the retained device context that produced it.
187pub struct WgpuAdapterRuntime {
188    pub(crate) probe: WgpuAdapterProbe,
189    pub(crate) device: wgpu::Device,
190    pub(crate) queue: wgpu::Queue,
191}
192
193impl WgpuAdapterRuntime {
194    /// Joins capsule-observed evidence to the retained device and queue that
195    /// produced it. The provider never enumerates the host itself.
196    pub fn new(probe: WgpuAdapterProbe, device: wgpu::Device, queue: wgpu::Queue) -> Self {
197        Self {
198            probe,
199            device,
200            queue,
201        }
202    }
203
204    /// Returns the capsule-supplied probe evidence.
205    pub fn probe(&self) -> &WgpuAdapterProbe {
206        &self.probe
207    }
208}
209
210/// Smallest host membrane consumed by the wgpu provider.
211pub trait WgpuProbePort {
212    /// Returns already-probed adapters with their retained execution handles.
213    fn probe_wgpu(
214        &self,
215        policy: &ProbePolicy,
216    ) -> Result<Vec<WgpuAdapterRuntime>, WgpuDiscoveryError>;
217}
218
219impl ComputePhysicalEvidence for WgpuAdapterProbe {
220    fn evidence_kind(&self) -> ComputeEvidenceKind {
221        self.evidence_kind
222    }
223
224    fn claimed_identity(&self) -> Option<&ComputeDeviceIdentity> {
225        self.claimed_identity.as_ref()
226    }
227
228    fn observed_identity(&self) -> Option<&ComputeDeviceIdentity> {
229        self.observed_identity.as_ref()
230    }
231}
232
233/// Complete discovery result.
234#[derive(Clone, Debug, Default, PartialEq, Eq)]
235pub struct WgpuDiscovery {
236    /// Successful adapter probes, in deterministic order.
237    pub adapters: Vec<WgpuAdapterProbe>,
238    /// Diagnostic errors from adapters that did not become sites.
239    pub diagnostics: Vec<String>,
240}
241
242impl WgpuDiscovery {
243    /// Builds a discovery result and drops unsuccessful adapters from the site
244    /// list while preserving their diagnostics.
245    pub fn from_probes(probes: Vec<WgpuAdapterProbe>, mut diagnostics: Vec<String>) -> Self {
246        let mut adapters = Vec::new();
247        for probe in probes {
248            if probe.probe.successful() {
249                adapters.push(probe);
250            } else {
251                diagnostics.push(format!(
252                    "wgpu adapter {} did not pass required probes",
253                    probe.adapter.name
254                ));
255            }
256        }
257        adapters.sort_by(|left, right| left.adapter.sort_key().cmp(&right.adapter.sort_key()));
258        for (ordinal, probe) in adapters.iter_mut().enumerate() {
259            probe.adapter.ordinal = ordinal;
260        }
261        Self {
262            adapters,
263            diagnostics,
264        }
265    }
266}
267
268/// Bounded probe policy.
269#[derive(Clone, Debug, PartialEq, Eq)]
270pub struct ProbePolicy {
271    /// Backends to enumerate.
272    pub backends: Backends,
273    /// Bytes used by the transfer and map probe.
274    pub transfer_bytes: u64,
275    /// Largest allocation attempt, capped again by granted limits.
276    pub max_allocation_probe_bytes: u64,
277}
278
279impl Default for ProbePolicy {
280    fn default() -> Self {
281        Self {
282            backends: Backends::all(),
283            transfer_bytes: 16,
284            max_allocation_probe_bytes: 16 * 1024 * 1024,
285        }
286    }
287}
288
289/// Discovery failure for infrastructure-level probe setup.
290#[derive(Clone, Debug, PartialEq, Eq)]
291pub struct WgpuDiscoveryError {
292    message: String,
293}
294
295impl WgpuDiscoveryError {
296    /// Builds a bounded platform-probe diagnostic.
297    pub fn new(message: impl Into<String>) -> Self {
298        Self {
299            message: message.into(),
300        }
301    }
302}
303
304impl std::fmt::Display for WgpuDiscoveryError {
305    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306        formatter.write_str(&self.message)
307    }
308}
309
310impl std::error::Error for WgpuDiscoveryError {}