1use std::marker::PhantomData;
2
3use crate::WgpuCompiler;
4use crate::{
5 AutoCompiler, AutoGraphicsApi, GraphicsApi, WgpuBackend, WgpuDevice, WgpuDeviceKind, backend,
6 compute::WgpuServer, contiguous_strides,
7};
8use cubecl_common::device::{Device, DeviceService, ServiceId};
9use cubecl_common::profile::TimingMethod;
10use cubecl_core::device::{DeviceId, ServerUtilitiesHandle};
11use cubecl_core::ir::TargetProperties;
12use cubecl_core::server::ServerUtilities;
13use cubecl_core::zspace::{Shape, Strides};
14use cubecl_environment::future;
15use cubecl_ir::{DeviceIdentity, DeviceProperties, HardwareProperties, MemoryDeviceProperties};
16use cubecl_server::allocator::ContiguousMemoryLayoutPolicy;
17#[cfg(not(feature = "vulkan-validate"))]
18use cubecl_server::logging::ProfileLevel;
19pub use cubecl_server::memory_management::MemoryConfiguration;
20use cubecl_server::runtime::Runtime;
21use cubecl_server::{client::Client, logging::ServerLogger};
22use wgpu::{InstanceFlags, RequestAdapterOptions};
23
24#[derive(Debug)]
28pub struct WgpuRuntime<Compiler = AutoCompiler> {
29 _p: PhantomData<Compiler>,
30}
31
32impl<C> Clone for WgpuRuntime<C> {
33 fn clone(&self) -> Self {
34 Self { _p: self._p }
35 }
36}
37
38impl<C: WgpuCompiler> DeviceService for WgpuServer<C> {
39 fn init(device_id: cubecl_common::device::DeviceId) -> Self {
40 let device = WgpuDevice::from_id(device_id);
41 let setup = future::block_on(create_setup_for_device(
42 &device,
43 resolve_backend(device.backend),
44 ));
45 create_server(setup, RuntimeOptions::default(), device_id)
46 }
47
48 fn utilities(&self) -> ServerUtilitiesHandle {
49 self.utilities.clone() as ServerUtilitiesHandle
50 }
51}
52
53impl<C: WgpuCompiler> Runtime for WgpuRuntime<C> {
54 type Server = WgpuServer<C>;
55 type Device = WgpuDevice;
56
57 fn can_read_tensor(shape: &Shape, strides: &Strides) -> bool {
58 if shape.is_empty() {
59 return true;
60 }
61
62 for (&expected, &stride) in contiguous_strides(shape).iter().zip(strides.iter()) {
63 if expected != stride {
64 return false;
65 }
66 }
67
68 true
69 }
70
71 fn target_properties() -> TargetProperties {
72 TargetProperties {
73 mma: Default::default(),
75 }
76 }
77
78 fn enumerate_devices(type_id: u16) -> Vec<DeviceId> {
79 Self::enumerate_devices_like(DeviceId::new(type_id, 0))
82 }
83
84 fn is_available() -> bool {
85 let gpu = [
90 WgpuDeviceKind::DiscreteGpu(0),
91 WgpuDeviceKind::IntegratedGpu(0),
92 WgpuDeviceKind::VirtualGpu(0),
93 WgpuDeviceKind::Other(0),
94 ]
95 .map(|kind| WgpuDevice::new(kind).to_id().type_id);
96
97 Self::enumerate_all_devices()
98 .iter()
99 .any(|device| gpu.contains(&device.type_id))
100 }
101
102 fn enumerate_all_devices() -> Vec<DeviceId> {
107 adapters_on(WgpuBackend::Auto)
108 }
109
110 fn enumerate_devices_like(device_id: DeviceId) -> Vec<DeviceId> {
111 let device = WgpuDevice::from_id(device_id);
112 let reachable = adapters_on(device.backend);
113
114 match device.kind {
115 WgpuDeviceKind::DefaultDevice if !reachable.is_empty() => alloc::vec![device_id],
120 _ => reachable
121 .into_iter()
122 .filter(|id| id.type_id == device_id.type_id)
123 .collect(),
124 }
125 }
126
127 #[cfg(target_family = "wasm")]
131 fn find_device(device_id: DeviceId) -> Result<(), usize> {
132 let device = WgpuDevice::from_id(device_id);
133 let peers = Self::enumerate_devices_like(device_id);
134
135 let low_power = device.kind == WgpuDeviceKind::IntegratedGpu(0)
136 && !adapters_on(device.backend).is_empty();
137
138 match peers.contains(&device_id) || low_power {
139 true => Ok(()),
140 false => Err(peers.len()),
141 }
142 }
143}
144
145fn adapters_on(backend: WgpuBackend) -> Vec<DeviceId> {
152 #[cfg(target_family = "wasm")]
155 let ids = match backend {
156 WgpuBackend::Auto | WgpuBackend::WebGpu => vec![DeviceId::new(0, 0)],
157 _ => Vec::new(),
158 };
159
160 #[cfg(not(target_family = "wasm"))]
161 let ids = settle(backend)
162 .map(|(_, adapters)| adapter_device_ids(adapters))
163 .unwrap_or_default();
164
165 ids.into_iter()
166 .map(|id| WgpuDevice::from_id(id).on(backend).to_id())
167 .collect()
168}
169
170#[cfg(not(target_family = "wasm"))]
179fn settle(backend: WgpuBackend) -> Option<(wgpu::Backend, Vec<wgpu::Adapter>)> {
180 let mut software_only = None;
181
182 for api in backend_candidates(backend) {
183 let adapters = enumerate_all_adapters(instance_for(api), api);
184
185 if adapters
186 .iter()
187 .any(|adapter| adapter.get_info().device_type != wgpu::DeviceType::Cpu)
188 {
189 return Some((api, adapters));
190 }
191
192 if software_only.is_none() && !adapters.is_empty() {
193 software_only = Some((api, adapters));
194 }
195 }
196
197 software_only
198}
199
200fn backend_candidates(backend: WgpuBackend) -> alloc::vec::Vec<wgpu::Backend> {
204 match backend {
205 WgpuBackend::Auto => AutoGraphicsApi::chain(),
206 WgpuBackend::Vulkan => alloc::vec![wgpu::Backend::Vulkan],
207 WgpuBackend::Metal => alloc::vec![wgpu::Backend::Metal],
208 WgpuBackend::Dx12 => alloc::vec![wgpu::Backend::Dx12],
209 WgpuBackend::Gl => alloc::vec![wgpu::Backend::Gl],
210 WgpuBackend::WebGpu => alloc::vec![wgpu::Backend::BrowserWebGpu],
211 }
212}
213
214#[cfg(not(target_family = "wasm"))]
216fn instance_for(backend: wgpu::Backend) -> wgpu::Instance {
217 wgpu::Instance::new(wgpu::InstanceDescriptor {
218 backends: backend.into(),
219 ..wgpu::InstanceDescriptor::new_without_display_handle()
220 })
221}
222
223pub(crate) fn resolve_backend(backend: WgpuBackend) -> wgpu::Backend {
229 #[cfg(not(target_family = "wasm"))]
230 if let Some((api, _)) = settle(backend) {
231 return api;
232 }
233
234 backend_candidates(backend)[0]
237}
238
239#[cfg(not(target_family = "wasm"))]
246fn adapter_device_ids(adapters: Vec<wgpu::Adapter>) -> Vec<DeviceId> {
247 let mut next = [0u16; 7];
248
249 adapters
250 .into_iter()
251 .map(|adapter| {
252 let type_id = match adapter.get_info().device_type {
253 wgpu::DeviceType::DiscreteGpu => 0,
254 wgpu::DeviceType::IntegratedGpu => 1,
255 wgpu::DeviceType::VirtualGpu => 2,
256 wgpu::DeviceType::Cpu => 3,
257 wgpu::DeviceType::Other => 6,
258 };
259
260 let index = match next.get_mut(type_id as usize).filter(|_| type_id != 3) {
262 Some(next) => {
263 let index = *next;
264 *next += 1;
265 index
266 }
267 None => 0,
268 };
269
270 DeviceId::new(type_id, index)
271 })
272 .collect()
273}
274
275#[cfg(not(target_family = "wasm"))]
276fn enumerate_all_adapters(instance: wgpu::Instance, backend: wgpu::Backend) -> Vec<wgpu::Adapter> {
277 cubecl_environment::future::block_on(instance.enumerate_adapters(backend.into()))
279}
280
281pub struct RuntimeOptions {
283 pub tasks_max: usize,
285 pub memory_config: MemoryConfiguration,
287}
288
289impl Default for RuntimeOptions {
290 fn default() -> Self {
291 #[cfg(test)]
292 const DEFAULT_MAX_TASKS: usize = 32;
293 #[cfg(not(test))]
294 const DEFAULT_MAX_TASKS: usize = 32;
295
296 let tasks_max = match std::env::var("CUBECL_WGPU_MAX_TASKS") {
297 Ok(value) => value
298 .parse::<usize>()
299 .expect("CUBECL_WGPU_MAX_TASKS should be a positive integer."),
300 Err(_) => DEFAULT_MAX_TASKS,
301 };
302
303 Self {
304 tasks_max,
305 memory_config: MemoryConfiguration::default(),
306 }
307 }
308}
309
310#[derive(Clone, Debug)]
314pub struct WgpuSetup {
315 pub instance: wgpu::Instance,
317 pub adapter: wgpu::Adapter,
319 pub device: wgpu::Device,
321 pub queue: wgpu::Queue,
323 pub backend: wgpu::Backend,
325}
326
327pub fn init_device(setup: WgpuSetup, options: RuntimeOptions) -> WgpuDevice {
337 use core::sync::atomic::{AtomicU32, Ordering};
338
339 static COUNTER: AtomicU32 = AtomicU32::new(0);
340
341 let device_id = COUNTER.fetch_add(1, Ordering::Relaxed);
342 if device_id == u32::MAX {
343 core::panic!("Memory ID overflowed");
344 }
345
346 let device_id = WgpuDevice::new(WgpuDeviceKind::Existing(device_id));
347 let server = create_server::<AutoCompiler>(setup, options, device_id.to_id());
348 let _ = Client::init(device_id.to_id(), server);
349 device_id
350}
351
352pub fn init_setup<G: GraphicsApi>(device: &WgpuDevice, options: RuntimeOptions) -> WgpuSetup {
365 cfg_if::cfg_if! {
366 if #[cfg(target_family = "wasm")] {
367 let _ = (device, options);
368 panic!("Creating a wgpu setup synchronously is unsupported on wasm. Use init_async instead");
369 } else {
370 future::block_on(init_setup_async::<G>(device, options))
371 }
372 }
373}
374
375pub async fn init_setup_async<G: GraphicsApi>(
388 device: &WgpuDevice,
389 options: RuntimeOptions,
390) -> WgpuSetup {
391 let backend = G::backend_for(device);
392
393 if device.backend != WgpuBackend::Auto {
394 let pinned = resolve_backend(device.backend);
395 assert_eq!(
396 backend, pinned,
397 "{device:?} is pinned to {pinned:?}, and cannot be set up on {backend:?}"
398 );
399 }
400
401 let setup = create_setup_for_device(device, backend).await;
402 let return_setup = setup.clone();
403 let server = create_server::<AutoCompiler>(setup, options, device.to_id());
404 let _ = Client::init(device.to_id(), server);
405 return_setup
406}
407
408fn runtime_name(backend: wgpu::Backend) -> &'static str {
410 match backend {
411 wgpu::Backend::Vulkan => {
412 #[cfg(feature = "spirv")]
413 return "wgpu<spirv>";
414
415 #[cfg(not(feature = "spirv"))]
416 return "wgpu<wgsl>";
417 }
418 wgpu::Backend::Metal => {
419 #[cfg(feature = "msl")]
420 return "wgpu<msl>";
421
422 #[cfg(not(feature = "msl"))]
423 return "wgpu<wgsl>";
424 }
425 _ => "wgpu<wgsl>",
426 }
427}
428
429pub(crate) fn create_server<C: WgpuCompiler>(
430 setup: WgpuSetup,
431 options: RuntimeOptions,
432 device_id: DeviceId,
433) -> WgpuServer<C> {
434 let limits = setup.device.limits();
435 let adapter_limits = setup.adapter.limits();
436 let mut adapter_info = setup.adapter.get_info();
437
438 if adapter_info.subgroup_min_size == 0 && adapter_info.subgroup_max_size == 0 {
442 adapter_info.subgroup_min_size = 8;
444 adapter_info.subgroup_max_size = 128;
446 }
447
448 let mem_props = MemoryDeviceProperties::new(
452 limits.max_storage_buffer_binding_size,
453 limits.min_uniform_buffer_offset_alignment as u64,
454 );
455 let max_count = adapter_limits.max_compute_workgroups_per_dimension;
456 let hardware_props = HardwareProperties {
457 load_width: 128,
458 #[cfg(apple_silicon)]
462 plane_size_min: 32,
463 #[cfg(not(apple_silicon))]
464 plane_size_min: adapter_info.subgroup_min_size,
465 #[cfg(apple_silicon)]
466 plane_size_max: 32,
467 #[cfg(not(apple_silicon))]
468 plane_size_max: adapter_info.subgroup_max_size,
469 max_bindings: limits
473 .max_storage_buffers_per_shader_stage
474 .saturating_sub(1),
475 max_shared_memory_size: limits.max_compute_workgroup_storage_size as usize,
476 max_cube_count: (max_count, max_count, max_count),
477 max_units_per_cube: adapter_limits.max_compute_invocations_per_workgroup,
478 max_cube_dim: (
479 adapter_limits.max_compute_workgroup_size_x,
480 adapter_limits.max_compute_workgroup_size_y,
481 adapter_limits.max_compute_workgroup_size_z,
482 ),
483 num_streaming_multiprocessors: None,
484 num_tensor_cores: None,
485 min_tensor_cores_dim: None,
486 num_cpu_cores: None, last_level_cache_size: None,
488 max_vector_size: 4,
489 cube_mma_reserved_shared_memory: 0,
491 };
492
493 let mut compilation_options = Default::default();
494
495 let features = setup.adapter.features();
496
497 let time_measurement = if features.contains(wgpu::Features::TIMESTAMP_QUERY) {
498 TimingMethod::Device
499 } else {
500 TimingMethod::System
501 };
502
503 let fingerprint = format!("spirv_{}_{}", adapter_info.vendor, adapter_info.device);
509
510 let mut device_props = DeviceProperties::new(
511 Default::default(),
512 mem_props,
513 hardware_props,
514 time_measurement,
515 DeviceIdentity {
516 name: adapter_info.name.clone(),
517 fingerprint,
518 physical: backend::physical_device(&setup.adapter, &adapter_info),
519 },
520 );
521
522 #[cfg(not(all(target_os = "macos", feature = "msl")))]
523 {
524 if features.contains(wgpu::Features::SUBGROUP)
525 && setup.adapter.get_info().device_type != wgpu::DeviceType::Cpu
526 {
527 use cubecl_ir::features::Plane;
528
529 device_props.features.plane.insert(Plane::Ops);
530 }
531 }
532
533 #[cfg(any(feature = "spirv", feature = "msl"))]
534 device_props
535 .features
536 .plane
537 .insert(cubecl_ir::features::Plane::NonUniformControlFlow);
538
539 backend::register_features(
540 &setup.adapter,
541 &mut device_props,
542 &mut compilation_options,
543 &options.memory_config,
544 );
545
546 let logger = alloc::sync::Arc::new(ServerLogger::default());
547
548 let allocator = ContiguousMemoryLayoutPolicy::new(device_props.memory.alignment as usize);
549 WgpuServer::new(
550 device_props.memory.clone(),
551 options.memory_config,
552 compilation_options,
553 setup.device.clone(),
554 setup.queue,
555 options.tasks_max,
556 setup.backend,
557 time_measurement,
558 ServerUtilities::new(
559 ServiceId::of::<WgpuServer<C>>(device_id),
560 runtime_name(setup.backend),
561 device_props,
562 WgpuRuntime::<C>::target_properties(),
563 logger,
564 allocator,
565 ),
566 )
567}
568
569pub(crate) async fn create_setup_for_device(
572 device: &WgpuDevice,
573 backend: wgpu::Backend,
574) -> WgpuSetup {
575 let (instance, adapter) = request_adapter(device, backend).await;
576 let (device, queue) = backend::request_device(&adapter).await;
577
578 log::info!(
579 "Created wgpu compute server on device {:?}",
580 adapter.get_info()
581 );
582
583 WgpuSetup {
584 instance,
585 adapter,
586 device,
587 queue,
588 backend,
589 }
590}
591
592async fn request_adapter(
593 device: &WgpuDevice,
594 backend: wgpu::Backend,
595) -> (wgpu::Instance, wgpu::Adapter) {
596 #[cfg(not(feature = "vulkan-validate"))]
597 let instance_flags = {
598 let debug = ServerLogger::default();
599 match (debug.profile_level(), debug.compilation_source_activated()) {
603 (Some(ProfileLevel::Full), _) => InstanceFlags::advanced_debugging(),
604 (_, true) => InstanceFlags::debugging(),
605 (_, false) => InstanceFlags::default(),
606 }
607 };
608 #[cfg(feature = "vulkan-validate")]
609 let instance_flags = InstanceFlags::advanced_debugging();
610 log::debug!("{instance_flags:?}");
611 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
612 backends: backend.into(),
613 flags: instance_flags,
614 ..wgpu::InstanceDescriptor::new_without_display_handle()
615 });
616
617 let override_device = match device.kind {
620 WgpuDeviceKind::DefaultDevice => get_device_override().map(|kind| WgpuDevice {
621 kind,
622 backend: device.backend,
623 }),
624 _ => None,
625 };
626
627 let device = override_device.unwrap_or_else(|| device.clone());
628
629 let adapter = match device.kind {
630 #[cfg(not(target_family = "wasm"))]
631 WgpuDeviceKind::DiscreteGpu(num) => {
632 select_from_adapter_list(
633 num,
634 "No Discrete GPU device found",
635 &instance,
636 &device,
637 backend,
638 )
639 .await
640 }
641 #[cfg(not(target_family = "wasm"))]
642 WgpuDeviceKind::IntegratedGpu(num) => {
643 select_from_adapter_list(
644 num,
645 "No Integrated GPU device found",
646 &instance,
647 &device,
648 backend,
649 )
650 .await
651 }
652 #[cfg(not(target_family = "wasm"))]
653 WgpuDeviceKind::VirtualGpu(num) => {
654 select_from_adapter_list(
655 num,
656 "No Virtual GPU device found",
657 &instance,
658 &device,
659 backend,
660 )
661 .await
662 }
663 #[cfg(not(target_family = "wasm"))]
664 WgpuDeviceKind::Other(num) => {
665 select_from_adapter_list(num, "No Other device found", &instance, &device, backend)
666 .await
667 }
668 #[cfg(not(target_family = "wasm"))]
669 WgpuDeviceKind::Cpu => {
670 select_from_adapter_list(0, "No CPU device found", &instance, &device, backend).await
671 }
672 #[cfg(target_family = "wasm")]
673 WgpuDeviceKind::IntegratedGpu(_) => {
674 request_adapter_with_preference(&instance, wgpu::PowerPreference::LowPower).await
675 }
676 WgpuDeviceKind::Existing(_) => {
677 unreachable!("Cannot select an adapter for an existing device.")
678 }
679 _ => {
680 request_adapter_with_preference(&instance, wgpu::PowerPreference::HighPerformance).await
681 }
682 };
683
684 (instance, adapter)
685}
686
687async fn request_adapter_with_preference(
688 instance: &wgpu::Instance,
689 power_preference: wgpu::PowerPreference,
690) -> wgpu::Adapter {
691 instance
692 .request_adapter(&RequestAdapterOptions {
693 power_preference,
694 force_fallback_adapter: false,
695 compatible_surface: None,
696 ..RequestAdapterOptions::default()
697 })
698 .await
699 .expect("No possible adapter available for backend. Falling back to first available.")
700}
701
702#[cfg(not(target_family = "wasm"))]
703async fn select_from_adapter_list(
704 num: usize,
705 error: &str,
706 instance: &wgpu::Instance,
707 device: &WgpuDevice,
708 backend: wgpu::Backend,
709) -> wgpu::Adapter {
710 let adapters = instance.enumerate_adapters(backend.into()).await;
715 let found = adapters
716 .iter()
717 .map(|adapter| adapter.get_info())
718 .collect::<Vec<_>>();
719
720 let is_same_type = |adapter: &wgpu::Adapter| {
721 let device_type = adapter.get_info().device_type;
722
723 match device.kind {
724 WgpuDeviceKind::DiscreteGpu(_) => device_type == wgpu::DeviceType::DiscreteGpu,
725 WgpuDeviceKind::IntegratedGpu(_) => device_type == wgpu::DeviceType::IntegratedGpu,
726 WgpuDeviceKind::VirtualGpu(_) => device_type == wgpu::DeviceType::VirtualGpu,
727 WgpuDeviceKind::Cpu => device_type == wgpu::DeviceType::Cpu,
728 WgpuDeviceKind::Other(_) => device_type == wgpu::DeviceType::Other,
729 WgpuDeviceKind::DefaultDevice => true,
730 WgpuDeviceKind::Existing(_) => {
731 unreachable!("Cannot select an adapter for an existing device.")
732 }
733 }
734 };
735
736 adapters
737 .into_iter()
738 .filter(is_same_type)
739 .nth(num)
740 .unwrap_or_else(|| panic!("{error}, adapters {found:?}"))
741}
742
743fn get_device_override() -> Option<WgpuDeviceKind> {
744 std::env::var("CUBECL_WGPU_DEFAULT_DEVICE")
747 .ok()
748 .and_then(|var| {
749 let override_device = if let Some(inner) = var.strip_prefix("DiscreteGpu(") {
750 inner
751 .strip_suffix(")")
752 .and_then(|s| s.parse().ok())
753 .map(WgpuDeviceKind::DiscreteGpu)
754 } else if let Some(inner) = var.strip_prefix("IntegratedGpu(") {
755 inner
756 .strip_suffix(")")
757 .and_then(|s| s.parse().ok())
758 .map(WgpuDeviceKind::IntegratedGpu)
759 } else if let Some(inner) = var.strip_prefix("VirtualGpu(") {
760 inner
761 .strip_suffix(")")
762 .and_then(|s| s.parse().ok())
763 .map(WgpuDeviceKind::VirtualGpu)
764 } else if var == "Cpu" {
765 Some(WgpuDeviceKind::Cpu)
766 } else {
767 None
768 };
769
770 if override_device.is_none() {
771 log::warn!("Unknown CUBECL_WGPU_DEVICE override {var}");
772 }
773 override_device
774 })
775}
776
777#[cfg(all(test, not(target_family = "wasm")))]
778mod device_tests {
779 use super::*;
780
781 const PINNED: [WgpuBackend; 4] = [
782 WgpuBackend::Vulkan,
783 WgpuBackend::Metal,
784 WgpuBackend::Dx12,
785 WgpuBackend::Gl,
786 ];
787
788 #[test]
793 fn each_adapter_is_listed_once() {
794 let ids = <WgpuRuntime>::enumerate_all_devices();
795
796 let adapters = settle(WgpuBackend::Auto).map_or(0, |(_, adapters)| adapters.len());
797
798 assert_eq!(ids.len(), adapters);
799 for id in ids {
800 assert_eq!(WgpuDevice::from_id(id).backend, WgpuBackend::Auto);
801 }
802 }
803
804 #[test]
808 fn a_device_is_found_on_the_api_it_names() {
809 for backend in PINNED {
810 let reachable = adapters_on(backend);
811
812 for id in reachable.iter() {
813 assert_eq!(
814 <WgpuRuntime>::find_device(*id),
815 Ok(()),
816 "{id} on {backend:?}"
817 );
818 }
819
820 let default = WgpuDevice::new(WgpuDeviceKind::DefaultDevice).on(backend);
821 assert_eq!(
822 <WgpuRuntime>::find_device(default.to_id()).is_ok(),
823 !reachable.is_empty(),
824 "the default device on {backend:?}"
825 );
826 }
827 }
828
829 #[test]
833 fn the_default_device_is_its_own_only_peer() {
834 for backend in PINNED.into_iter().chain([WgpuBackend::Auto]) {
835 let default = WgpuDevice::new(WgpuDeviceKind::DefaultDevice)
836 .on(backend)
837 .to_id();
838
839 let expected = match adapters_on(backend).is_empty() {
840 true => Vec::new(),
841 false => alloc::vec![default],
842 };
843
844 assert_eq!(<WgpuRuntime>::enumerate_devices_like(default), expected);
845 }
846 }
847
848 #[test]
851 fn auto_defers_to_the_api_a_device_pins() {
852 for (backend, api) in [
853 (WgpuBackend::Vulkan, wgpu::Backend::Vulkan),
854 (WgpuBackend::Metal, wgpu::Backend::Metal),
855 (WgpuBackend::Dx12, wgpu::Backend::Dx12),
856 (WgpuBackend::Gl, wgpu::Backend::Gl),
857 ] {
858 let device = WgpuDevice::new(WgpuDeviceKind::DefaultDevice).on(backend);
859
860 assert_eq!(AutoGraphicsApi::backend_for(&device), api);
861 }
862 }
863
864 #[test]
868 #[should_panic(expected = "is pinned to Gl")]
869 fn a_setup_on_another_api_than_the_pinned_one_is_refused() {
870 let device = WgpuDevice::new(WgpuDeviceKind::DefaultDevice).on(WgpuBackend::Gl);
871
872 init_setup::<crate::Vulkan>(&device, RuntimeOptions::default());
873 }
874
875 #[test]
878 fn a_pinned_device_is_enumerated_with_its_own_api() {
879 for backend in PINNED {
880 for id in adapters_on(backend) {
881 let peers = <WgpuRuntime>::enumerate_devices_like(id);
882
883 assert!(peers.contains(&id), "{id} among {peers:?}");
884 for peer in peers {
885 assert_eq!(WgpuDevice::from_id(peer).backend, backend);
886 }
887 }
888 }
889 }
890
891 #[test]
894 fn an_index_past_the_end_is_not_found_on_any_api() {
895 for backend in PINNED.into_iter().chain([WgpuBackend::Auto]) {
896 let device = WgpuDevice::new(WgpuDeviceKind::DiscreteGpu(4242)).on(backend);
897
898 let discrete = adapters_on(backend)
899 .into_iter()
900 .filter(|id| id.type_id == device.to_id().type_id)
901 .count();
902
903 assert_eq!(<WgpuRuntime>::find_device(device.to_id()), Err(discrete));
904 }
905 }
906}