renderling/internal/
cpu.rs

1//! Internal CPU utilities and stuff.
2use std::sync::Arc;
3
4use snafu::{OptionExt, ResultExt};
5
6use crate::context::{
7    CannotCreateAdaptorSnafu, CannotRequestDeviceSnafu, ContextError, IncompatibleSurfaceSnafu,
8    RenderTarget, RenderTargetInner,
9};
10
11/// Create a new [`wgpu::Adapter`].
12pub async fn adapter(
13    instance: &wgpu::Instance,
14    compatible_surface: Option<&wgpu::Surface<'_>>,
15) -> Result<wgpu::Adapter, ContextError> {
16    log::trace!(
17        "creating adapter for a {} context",
18        if compatible_surface.is_none() {
19            "headless"
20        } else {
21            "surface-based"
22        }
23    );
24    let adapter = instance
25        .request_adapter(&wgpu::RequestAdapterOptions {
26            power_preference: wgpu::PowerPreference::default(),
27            compatible_surface,
28            force_fallback_adapter: false,
29        })
30        .await
31        .context(CannotCreateAdaptorSnafu)?;
32
33    log::info!("Adapter selected: {:?}", adapter.get_info());
34    let info = adapter.get_info();
35    log::info!(
36        "using adapter: '{}' backend:{:?} driver:'{}'",
37        info.name,
38        info.backend,
39        info.driver
40    );
41    Ok(adapter)
42}
43
44/// Create a new [`wgpu::Device`].
45pub async fn device(
46    adapter: &wgpu::Adapter,
47) -> Result<(wgpu::Device, wgpu::Queue), wgpu::RequestDeviceError> {
48    let wanted_features = wgpu::Features::INDIRECT_FIRST_INSTANCE
49        | wgpu::Features::MULTI_DRAW_INDIRECT
50        //// when debugging rust-gpu shader miscompilation it's nice to have this
51        //| wgpu::Features::SPIRV_SHADER_PASSTHROUGH
52        // this one is a funny requirement, it seems it is needed if using storage buffers in
53        // vertex shaders, even if those shaders are read-only
54        | wgpu::Features::VERTEX_WRITABLE_STORAGE
55        | wgpu::Features::CLEAR_TEXTURE;
56    let supported_features = adapter.features();
57    let required_features = wanted_features.intersection(supported_features);
58    let unsupported_features = wanted_features.difference(supported_features);
59    if !unsupported_features.is_empty() {
60        log::error!("requested but unsupported features: {unsupported_features:#?}");
61        log::warn!("requested and supported features: {supported_features:#?}");
62    }
63    let limits = adapter.limits();
64    log::info!("adapter limits: {limits:#?}");
65    adapter
66        .request_device(&wgpu::DeviceDescriptor {
67            required_features,
68            required_limits: adapter.limits(),
69            label: None,
70            memory_hints: wgpu::MemoryHints::default(),
71            trace: wgpu::Trace::Off,
72        })
73        .await
74}
75
76/// Create a new instance.
77///
78/// This is for internal use. It is not necessary to create your own `wgpu`
79/// instance to use this library.
80pub fn new_instance(backends: Option<wgpu::Backends>) -> wgpu::Instance {
81    log::info!(
82        "creating instance - available backends: {:#?}",
83        wgpu::Instance::enabled_backend_features()
84    );
85    // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
86    let backends = backends.unwrap_or(wgpu::Backends::PRIMARY);
87    let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
88        backends,
89        ..Default::default()
90    });
91
92    #[cfg(not(target_arch = "wasm32"))]
93    {
94        let adapters = instance.enumerate_adapters(backends);
95        log::trace!("available adapters: {adapters:#?}");
96    }
97
98    instance
99}
100
101/// Create a new suite of `wgpu` machinery using a window or canvas.
102///
103/// ## Note
104/// This function is used internally.
105pub async fn new_windowed_adapter_device_queue(
106    width: u32,
107    height: u32,
108    instance: &wgpu::Instance,
109    window: impl Into<wgpu::SurfaceTarget<'static>>,
110) -> Result<(wgpu::Adapter, wgpu::Device, wgpu::Queue, RenderTarget), ContextError> {
111    let surface = instance
112        .create_surface(window)
113        .map_err(|e| ContextError::CreateSurface { source: e })?;
114    let adapter = adapter(instance, Some(&surface)).await?;
115    let surface_caps = surface.get_capabilities(&adapter);
116    let fmt = if surface_caps
117        .formats
118        .contains(&wgpu::TextureFormat::Rgba8UnormSrgb)
119    {
120        wgpu::TextureFormat::Rgba8UnormSrgb
121    } else {
122        surface_caps
123            .formats
124            .iter()
125            .copied()
126            .find(|f| f.is_srgb())
127            .unwrap_or(surface_caps.formats[0])
128    };
129    let view_fmts = if fmt.is_srgb() {
130        vec![]
131    } else {
132        vec![fmt.add_srgb_suffix()]
133    };
134    log::info!("surface capabilities: {surface_caps:#?}");
135    let mut surface_config = surface
136        .get_default_config(&adapter, width, height)
137        .context(IncompatibleSurfaceSnafu)?;
138    surface_config.view_formats = view_fmts;
139    let (device, queue) = device(&adapter).await.context(CannotRequestDeviceSnafu)?;
140    surface.configure(&device, &surface_config);
141    let target = RenderTarget(RenderTargetInner::Surface {
142        surface,
143        surface_config,
144    });
145    Ok((adapter, device, queue, target))
146}
147
148/// Create a new suite of `wgpu` machinery that renders to a texture.
149///
150/// ## Note
151/// This function is used internally.
152pub async fn new_headless_device_queue_and_target(
153    width: u32,
154    height: u32,
155    instance: &wgpu::Instance,
156) -> Result<(wgpu::Adapter, wgpu::Device, wgpu::Queue, RenderTarget), ContextError> {
157    let adapter = adapter(instance, None).await?;
158    let texture_desc = wgpu::TextureDescriptor {
159        size: wgpu::Extent3d {
160            width,
161            height,
162            depth_or_array_layers: 1,
163        },
164        mip_level_count: 1,
165        sample_count: 1,
166        dimension: wgpu::TextureDimension::D2,
167        format: wgpu::TextureFormat::Rgba8UnormSrgb,
168        usage: wgpu::TextureUsages::COPY_SRC
169            | wgpu::TextureUsages::RENDER_ATTACHMENT
170            | wgpu::TextureUsages::TEXTURE_BINDING,
171        label: None,
172        view_formats: &[],
173    };
174    let (device, queue) = device(&adapter).await.context(CannotRequestDeviceSnafu)?;
175    let texture = Arc::new(device.create_texture(&texture_desc));
176    let target = RenderTarget(RenderTargetInner::Texture { texture });
177    Ok((adapter, device, queue, target))
178}