1use std::sync::OnceLock;
22#[cfg(not(target_arch = "wasm32"))]
23use std::sync::atomic::{AtomicU8, Ordering};
24
25#[cfg(not(target_arch = "wasm32"))]
26const PREF_DEFAULT: u8 = 0;
27#[cfg(not(target_arch = "wasm32"))]
28const PREF_VULKAN: u8 = 1;
29
30#[cfg(not(target_arch = "wasm32"))]
31static BACKEND_PREF: AtomicU8 = AtomicU8::new(PREF_DEFAULT);
32
33pub struct WgpuDevice {
37 pub instance: wgpu::Instance,
38 pub adapter: wgpu::Adapter,
39 pub device: wgpu::Device,
40 pub queue: wgpu::Queue,
41 pub name: String,
42 pub backend: wgpu::Backend,
43}
44
45fn required_features_for(adapter: &wgpu::Adapter) -> wgpu::Features {
49 let adapter_feats = adapter.features();
50 let mut required_features = wgpu::Features::empty();
51 if adapter_feats.contains(wgpu::Features::SHADER_F16) {
52 required_features |= wgpu::Features::SHADER_F16;
53 }
54 if adapter_feats.contains(wgpu::Features::EXPERIMENTAL_COOPERATIVE_MATRIX) {
55 required_features |= wgpu::Features::EXPERIMENTAL_COOPERATIVE_MATRIX;
56 }
57 if adapter_feats.contains(wgpu::Features::SUBGROUP) {
58 required_features |= wgpu::Features::SUBGROUP;
59 }
60 required_features
61}
62
63fn make_instance(backends: wgpu::Backends) -> wgpu::Instance {
64 wgpu::Instance::new(wgpu::InstanceDescriptor {
65 backends,
66 flags: wgpu::InstanceFlags::default(),
67 backend_options: wgpu::BackendOptions::default(),
68 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
69 display: None,
70 })
71}
72
73const ADAPTER_OPTIONS: wgpu::RequestAdapterOptions = wgpu::RequestAdapterOptions {
74 power_preference: wgpu::PowerPreference::HighPerformance,
75 compatible_surface: None,
76 force_fallback_adapter: false,
77 apply_limit_buckets: false,
78};
79
80fn device_descriptor(
81 required_features: wgpu::Features,
82 limits: wgpu::Limits,
83) -> wgpu::DeviceDescriptor<'static> {
84 wgpu::DeviceDescriptor {
85 label: Some("rlx-wgpu device"),
86 required_features,
87 required_limits: limits,
88 memory_hints: wgpu::MemoryHints::Performance,
89 experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() },
90 trace: wgpu::Trace::Off,
91 }
92}
93
94impl WgpuDevice {
95 #[cfg(not(target_arch = "wasm32"))]
97 fn new_with_backends(backends: wgpu::Backends) -> Option<Self> {
98 let instance = make_instance(backends);
99 let adapter = pollster::block_on(instance.request_adapter(&ADAPTER_OPTIONS)).ok()?;
100
101 let info = adapter.get_info();
102 let limits = adapter.limits();
103 let required_features = required_features_for(&adapter);
104
105 let (device, queue) = match pollster::block_on(
106 adapter.request_device(&device_descriptor(required_features, limits)),
107 ) {
108 Ok(p) => p,
109 Err(e) => {
110 eprintln!("rlx-wgpu request_device failed: {e}");
111 return None;
112 }
113 };
114
115 Some(Self {
116 instance,
117 adapter,
118 device,
119 queue,
120 name: info.name,
121 backend: info.backend,
122 })
123 }
124
125 #[cfg(target_arch = "wasm32")]
129 async fn new_with_backends_async(backends: wgpu::Backends) -> Option<Self> {
130 let instance = make_instance(backends);
131 let adapter = instance.request_adapter(&ADAPTER_OPTIONS).await.ok()?;
132
133 let info = adapter.get_info();
134 let limits = adapter.limits();
135 let required_features = required_features_for(&adapter);
136
137 let (device, queue) = adapter
138 .request_device(&device_descriptor(required_features, limits))
139 .await
140 .ok()?;
141
142 Some(Self {
143 instance,
144 adapter,
145 device,
146 queue,
147 name: info.name,
148 backend: info.backend,
149 })
150 }
151
152 #[cfg(not(target_arch = "wasm32"))]
153 fn new_default() -> Option<Self> {
154 Self::new_with_backends(default_backends())
155 }
156}
157
158#[cfg(not(target_arch = "wasm32"))]
159fn default_backends() -> wgpu::Backends {
160 if let Some(b) = wgpu::Backends::from_env() {
161 return b;
162 }
163 #[cfg(target_os = "windows")]
164 {
165 wgpu::Backends::DX12 | wgpu::Backends::VULKAN
167 }
168 #[cfg(target_os = "linux")]
169 {
170 wgpu::Backends::VULKAN
172 }
173 #[cfg(target_vendor = "apple")]
174 {
175 wgpu::Backends::METAL | wgpu::Backends::VULKAN
178 }
179 #[cfg(not(any(target_os = "windows", target_os = "linux", target_vendor = "apple")))]
180 {
181 wgpu::Backends::all()
182 }
183}
184
185unsafe impl Send for WgpuDevice {}
187unsafe impl Sync for WgpuDevice {}
188
189#[cfg(not(target_arch = "wasm32"))]
190fn default_device() -> Option<&'static WgpuDevice> {
191 static DEVICE: OnceLock<Option<WgpuDevice>> = OnceLock::new();
192 DEVICE.get_or_init(WgpuDevice::new_default).as_ref()
193}
194
195#[cfg(not(target_arch = "wasm32"))]
196fn vulkan_device() -> Option<&'static WgpuDevice> {
197 static DEVICE: OnceLock<Option<WgpuDevice>> = OnceLock::new();
198 DEVICE
199 .get_or_init(|| WgpuDevice::new_with_backends(wgpu::Backends::VULKAN))
200 .as_ref()
201}
202
203#[cfg(not(target_arch = "wasm32"))]
207pub fn select_vulkan_backend() {
208 BACKEND_PREF.store(PREF_VULKAN, Ordering::SeqCst);
209}
210
211#[cfg(target_arch = "wasm32")]
213pub fn select_vulkan_backend() {}
214
215#[cfg(not(target_arch = "wasm32"))]
217pub fn is_vulkan_available() -> bool {
218 vulkan_device().is_some()
219}
220
221#[cfg(target_arch = "wasm32")]
223pub fn is_vulkan_available() -> bool {
224 false
225}
226
227#[cfg(not(target_arch = "wasm32"))]
230pub fn wgpu_device() -> Option<&'static WgpuDevice> {
231 if BACKEND_PREF.load(Ordering::SeqCst) == PREF_VULKAN {
232 vulkan_device()
233 } else {
234 default_device()
235 }
236}
237
238#[cfg(target_arch = "wasm32")]
246static WASM_DEVICE: OnceLock<Option<WgpuDevice>> = OnceLock::new();
247
248#[cfg(target_arch = "wasm32")]
251pub async fn init_wgpu_device() -> bool {
252 if let Some(slot) = WASM_DEVICE.get() {
253 return slot.is_some();
254 }
255 let dev = WgpuDevice::new_with_backends_async(wgpu::Backends::BROWSER_WEBGPU).await;
256 let available = dev.is_some();
257 let _ = WASM_DEVICE.set(dev);
258 available
259}
260
261#[cfg(target_arch = "wasm32")]
264pub fn wgpu_device() -> Option<&'static WgpuDevice> {
265 WASM_DEVICE.get().and_then(|d| d.as_ref())
266}
267
268pub fn adapter_name() -> Option<String> {
270 wgpu_device().map(|d| d.name.clone())
271}
272
273pub fn coop_discrete_backend() -> bool {
275 wgpu_device()
276 .map(|d| matches!(d.backend, wgpu::Backend::Vulkan | wgpu::Backend::Dx12))
277 .unwrap_or(false)
278}
279
280pub fn coop_f32_8x8_supported() -> bool {
283 let dev = match wgpu_device() {
284 Some(d) => d,
285 None => return false,
286 };
287 dev.adapter.cooperative_matrix_properties().iter().any(|p| {
288 p.m_size == 8
289 && p.n_size == 8
290 && p.k_size == 8
291 && p.ab_type == wgpu::CooperativeScalarType::F32
292 && p.cr_type == wgpu::CooperativeScalarType::F32
293 })
294}
295
296pub fn coop_f16_16x16_supported() -> bool {
299 let dev = match wgpu_device() {
300 Some(d) => d,
301 None => return false,
302 };
303 dev.adapter.cooperative_matrix_properties().iter().any(|p| {
304 p.m_size == 16
305 && p.n_size == 16
306 && p.k_size == 16
307 && p.ab_type == wgpu::CooperativeScalarType::F16
308 && (p.cr_type == wgpu::CooperativeScalarType::F16
309 || p.cr_type == wgpu::CooperativeScalarType::F32)
310 })
311}
312
313pub fn coop_f16_16x16_f32_acc_supported() -> bool {
315 let dev = match wgpu_device() {
316 Some(d) => d,
317 None => return false,
318 };
319 dev.adapter.cooperative_matrix_properties().iter().any(|p| {
320 p.m_size == 16
321 && p.n_size == 16
322 && p.k_size == 16
323 && p.ab_type == wgpu::CooperativeScalarType::F16
324 && p.cr_type == wgpu::CooperativeScalarType::F32
325 })
326}