1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use tracing::{debug, error};

#[derive(Debug)]
pub struct Device {
    pub device: wgpu::Device,
    pub queue: wgpu::Queue,
}

impl Device {
    pub async fn from_preferred_adapter(
        instance: &wgpu::Instance,
        surface: &wgpu::Surface,
    ) -> Result<(Self, wgpu::Adapter, wgpu::Features), DeviceError> {
        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: wgpu::PowerPreference::None,
                force_fallback_adapter: false,
                compatible_surface: Some(surface),
            })
            .await
            .ok_or(DeviceError::RequestAdapter)?;

        debug!("Using adapter: {:?}", adapter.get_info());

        let (device, features) = Device::new(&adapter).await?;

        Ok((device, adapter, features))
    }

    pub async fn try_from_all_adapters(
        instance: &wgpu::Instance,
    ) -> Result<(Self, wgpu::Adapter, wgpu::Features), DeviceError> {
        let mut all_adapters =
            instance.enumerate_adapters(wgpu::Backends::all());

        let result = loop {
            let Some(adapter) = all_adapters.next() else {
                debug!("No more adapters to try");
                break None;
            };

            let (device, features) = match Device::new(&adapter).await {
                Ok((device, adapter)) => (device, adapter),
                Err(err) => {
                    error!(
                        "Failed to get device from adapter {:?}: {:?}",
                        adapter.get_info(),
                        err,
                    );
                    continue;
                }
            };

            break Some((device, adapter, features));
        };

        for adapter in all_adapters {
            debug!(
                "Remaining adapter that wasn't tried: {:?}",
                adapter.get_info()
            );
        }

        result.ok_or(DeviceError::FoundNoWorkingAdapter)
    }

    pub async fn new(
        adapter: &wgpu::Adapter,
    ) -> Result<(Self, wgpu::Features), DeviceError> {
        let features = {
            let desired_features = wgpu::Features::POLYGON_MODE_LINE;
            let available_features = adapter.features();

            // By requesting the intersection of desired and available features,
            // we prevent two things:
            //
            // 1. That requesting the device panics, which would happen if we
            //    requested unavailable features.
            // 2. That a developer ends up accidentally using features that
            //    happen to be available on their machine, but that aren't
            //    necessarily available for all the users.
            desired_features.intersection(available_features)
        };

        let limits = {
            // This is the lowest of the available defaults. It should guarantee
            // that we can run pretty much everywhere.
            let lowest_limits = wgpu::Limits::downlevel_webgl2_defaults();

            // However, these lowest limits aren't necessarily capable of
            // supporting the screen resolution of our current platform, so
            // let's amend them.
            let supported_limits = adapter.limits();
            lowest_limits.using_resolution(supported_limits)
        };

        let (device, queue) = adapter
            .request_device(
                &wgpu::DeviceDescriptor {
                    label: None,
                    features,
                    limits,
                },
                None,
            )
            .await?;

        Ok((Device { device, queue }, features))
    }
}

/// Render device initialization error
#[derive(Debug, thiserror::Error)]
pub enum DeviceError {
    /// Failed to request adapter
    #[error("Failed to request adapter")]
    RequestAdapter,

    /// Failed to request device
    #[error("Failed to request device")]
    RequestDevice(#[from] wgpu::RequestDeviceError),

    /// Found no working adapter to get a device from
    #[error("Found no working adapter to get a device from")]
    FoundNoWorkingAdapter,
}