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};
78
79fn device_descriptor(
80 required_features: wgpu::Features,
81 limits: wgpu::Limits,
82) -> wgpu::DeviceDescriptor<'static> {
83 wgpu::DeviceDescriptor {
84 label: Some("rlx-wgpu device"),
85 required_features,
86 required_limits: limits,
87 memory_hints: wgpu::MemoryHints::Performance,
88 experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() },
89 trace: wgpu::Trace::Off,
90 }
91}
92
93impl WgpuDevice {
94 #[cfg(not(target_arch = "wasm32"))]
96 fn new_with_backends(backends: wgpu::Backends) -> Option<Self> {
97 let instance = make_instance(backends);
98 let adapter = pollster::block_on(instance.request_adapter(&ADAPTER_OPTIONS)).ok()?;
99
100 let info = adapter.get_info();
101 let limits = adapter.limits();
102 let required_features = required_features_for(&adapter);
103
104 let (device, queue) = match pollster::block_on(
105 adapter.request_device(&device_descriptor(required_features, limits)),
106 ) {
107 Ok(p) => p,
108 Err(e) => {
109 eprintln!("rlx-wgpu request_device failed: {e}");
110 return None;
111 }
112 };
113
114 Some(Self {
115 instance,
116 adapter,
117 device,
118 queue,
119 name: info.name,
120 backend: info.backend,
121 })
122 }
123
124 #[cfg(target_arch = "wasm32")]
128 async fn new_with_backends_async(backends: wgpu::Backends) -> Option<Self> {
129 let instance = make_instance(backends);
130 let adapter = instance.request_adapter(&ADAPTER_OPTIONS).await.ok()?;
131
132 let info = adapter.get_info();
133 let limits = adapter.limits();
134 let required_features = required_features_for(&adapter);
135
136 let (device, queue) = adapter
137 .request_device(&device_descriptor(required_features, limits))
138 .await
139 .ok()?;
140
141 Some(Self {
142 instance,
143 adapter,
144 device,
145 queue,
146 name: info.name,
147 backend: info.backend,
148 })
149 }
150
151 #[cfg(not(target_arch = "wasm32"))]
152 fn new_default() -> Option<Self> {
153 Self::new_with_backends(default_backends())
154 }
155}
156
157#[cfg(not(target_arch = "wasm32"))]
158fn default_backends() -> wgpu::Backends {
159 if let Some(b) = wgpu::Backends::from_env() {
160 return b;
161 }
162 #[cfg(target_os = "windows")]
163 {
164 wgpu::Backends::DX12 | wgpu::Backends::VULKAN
166 }
167 #[cfg(target_os = "linux")]
168 {
169 wgpu::Backends::VULKAN
171 }
172 #[cfg(target_vendor = "apple")]
173 {
174 wgpu::Backends::METAL | wgpu::Backends::VULKAN
177 }
178 #[cfg(not(any(target_os = "windows", target_os = "linux", target_vendor = "apple")))]
179 {
180 wgpu::Backends::all()
181 }
182}
183
184unsafe impl Send for WgpuDevice {}
186unsafe impl Sync for WgpuDevice {}
187
188#[cfg(not(target_arch = "wasm32"))]
189fn default_device() -> Option<&'static WgpuDevice> {
190 static DEVICE: OnceLock<Option<WgpuDevice>> = OnceLock::new();
191 DEVICE.get_or_init(WgpuDevice::new_default).as_ref()
192}
193
194#[cfg(not(target_arch = "wasm32"))]
195fn vulkan_device() -> Option<&'static WgpuDevice> {
196 static DEVICE: OnceLock<Option<WgpuDevice>> = OnceLock::new();
197 DEVICE
198 .get_or_init(|| WgpuDevice::new_with_backends(wgpu::Backends::VULKAN))
199 .as_ref()
200}
201
202#[cfg(not(target_arch = "wasm32"))]
206pub fn select_vulkan_backend() {
207 BACKEND_PREF.store(PREF_VULKAN, Ordering::SeqCst);
208}
209
210#[cfg(target_arch = "wasm32")]
212pub fn select_vulkan_backend() {}
213
214#[cfg(not(target_arch = "wasm32"))]
216pub fn is_vulkan_available() -> bool {
217 vulkan_device().is_some()
218}
219
220#[cfg(target_arch = "wasm32")]
222pub fn is_vulkan_available() -> bool {
223 false
224}
225
226#[cfg(not(target_arch = "wasm32"))]
229pub fn wgpu_device() -> Option<&'static WgpuDevice> {
230 if BACKEND_PREF.load(Ordering::SeqCst) == PREF_VULKAN {
231 vulkan_device()
232 } else {
233 default_device()
234 }
235}
236
237#[cfg(target_arch = "wasm32")]
245static WASM_DEVICE: OnceLock<Option<WgpuDevice>> = OnceLock::new();
246
247#[cfg(target_arch = "wasm32")]
250pub async fn init_wgpu_device() -> bool {
251 if let Some(slot) = WASM_DEVICE.get() {
252 return slot.is_some();
253 }
254 let dev = WgpuDevice::new_with_backends_async(wgpu::Backends::BROWSER_WEBGPU).await;
255 let available = dev.is_some();
256 let _ = WASM_DEVICE.set(dev);
257 available
258}
259
260#[cfg(target_arch = "wasm32")]
263pub fn wgpu_device() -> Option<&'static WgpuDevice> {
264 WASM_DEVICE.get().and_then(|d| d.as_ref())
265}
266
267pub fn adapter_name() -> Option<String> {
269 wgpu_device().map(|d| d.name.clone())
270}
271
272pub fn coop_discrete_backend() -> bool {
274 wgpu_device()
275 .map(|d| matches!(d.backend, wgpu::Backend::Vulkan | wgpu::Backend::Dx12))
276 .unwrap_or(false)
277}
278
279pub fn coop_f32_8x8_supported() -> bool {
282 let dev = match wgpu_device() {
283 Some(d) => d,
284 None => return false,
285 };
286 dev.adapter.cooperative_matrix_properties().iter().any(|p| {
287 p.m_size == 8
288 && p.n_size == 8
289 && p.k_size == 8
290 && p.ab_type == wgpu::CooperativeScalarType::F32
291 && p.cr_type == wgpu::CooperativeScalarType::F32
292 })
293}
294
295pub fn coop_f16_16x16_supported() -> bool {
298 let dev = match wgpu_device() {
299 Some(d) => d,
300 None => return false,
301 };
302 dev.adapter.cooperative_matrix_properties().iter().any(|p| {
303 p.m_size == 16
304 && p.n_size == 16
305 && p.k_size == 16
306 && p.ab_type == wgpu::CooperativeScalarType::F16
307 && (p.cr_type == wgpu::CooperativeScalarType::F16
308 || p.cr_type == wgpu::CooperativeScalarType::F32)
309 })
310}
311
312pub fn coop_f16_16x16_f32_acc_supported() -> bool {
314 let dev = match wgpu_device() {
315 Some(d) => d,
316 None => return false,
317 };
318 dev.adapter.cooperative_matrix_properties().iter().any(|p| {
319 p.m_size == 16
320 && p.n_size == 16
321 && p.k_size == 16
322 && p.ab_type == wgpu::CooperativeScalarType::F16
323 && p.cr_type == wgpu::CooperativeScalarType::F32
324 })
325}