StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | pub mod renderer; |
| 2 | mod resources; |
| 3 | mod shader_types; |
| 4 | mod texture_with_bind_group; |
| 5 | |
| 6 | use std::sync::{Arc, LazyLock, Mutex}; |
| 7 | |
| 8 | use wgpu::wgt::WgpuHasDisplayHandle; |
| 9 | |
| 10 | pub use renderer::Renderer; |
| 11 | pub use resources::{adapter_has_rendering_offset_bug, Resources}; |
| 12 | |
| 13 | use crate::platform::GraphicsBackend; |
| 14 | #[cfg(not(target_family = "wasm"))] |
| 15 | use crate::{rendering::GPUPowerPreference, windowing}; |
| 16 | |
| 17 | static WGPU_INSTANCE: LazyLock<Mutex<Option<Arc<wgpu::Instance>>>> = LazyLock::new(Mutex::default); |
| 18 | |
| 19 | /// Drops and recreates the global shared [`wgpu::Instance`]. |
| 20 | pub fn reset_wgpu_instance(display_handle: Box<dyn wgpu::wgt::WgpuHasDisplayHandle>) { |
| 21 | // Drop the existing wgpu instance. |
| 22 | { |
| 23 | let mut instance = WGPU_INSTANCE |
| 24 | .lock() |
| 25 | .expect("wgpu instance lock should not be poisoned"); |
| 26 | let _ = instance.take(); |
| 27 | } |
| 28 | |
| 29 | // Create a new one. |
| 30 | init_wgpu_instance(display_handle); |
| 31 | } |
| 32 | |
| 33 | /// Initializes the global wgpu instance. This MUST be called before [`get_wgpu_instance()`]. |
| 34 | pub fn init_wgpu_instance(display_handle: Box<dyn WgpuHasDisplayHandle>) { |
| 35 | // Check whether DirectComposition should be explicitly disabled on Windows. |
| 36 | let disable_dcomp = std::env::var("WARP_USE_DIRECT_COMPOSITION") |
| 37 | .ok() |
| 38 | .is_some_and(|val| { |
| 39 | let val = val.to_lowercase(); |
| 40 | val == "0" || val == "false" |
| 41 | }); |
| 42 | |
| 43 | // A helper function to create a wgpu instance with the appropriate configuration. |
| 44 | let create_instance = move || { |
| 45 | let dx12_shader_compiler = get_dx12_shader_compiler(); |
| 46 | Arc::new(wgpu::Instance::new(wgpu::InstanceDescriptor { |
| 47 | backends: wgpu_backend_options(), |
| 48 | backend_options: wgpu::BackendOptions { |
| 49 | dx12: wgpu::Dx12BackendOptions { |
| 50 | presentation_system: if disable_dcomp { |
| 51 | wgpu::wgt::Dx12SwapchainKind::DxgiFromHwnd |
| 52 | } else { |
| 53 | wgpu::wgt::Dx12SwapchainKind::DxgiFromVisual |
| 54 | }, |
| 55 | shader_compiler: dx12_shader_compiler.unwrap_or(wgpu::Dx12Compiler::Fxc), |
| 56 | ..Default::default() |
| 57 | }, |
| 58 | ..Default::default() |
| 59 | }, |
| 60 | flags: wgpu::InstanceFlags::empty(), |
| 61 | memory_budget_thresholds: Default::default(), |
| 62 | display: Some(display_handle), |
| 63 | })) |
| 64 | }; |
| 65 | |
| 66 | // A helper function for initializing the WGPU_INSTANCE static variable. |
| 67 | // |
| 68 | // If `lock_acquired_tx` is provided, it will be used to signal when the lock has been acquired, allowing |
| 69 | // for asynchronous initialization in a dedicated thread while ensuring that `get_wgpu_instance()` cannot |
| 70 | // race with the initialization. |
| 71 | let init_static_var = |lock_acquired_tx: Option<std::sync::mpsc::Sender<()>>| { |
| 72 | let mut instance_lock_guard = WGPU_INSTANCE |
| 73 | .lock() |
| 74 | .expect("wgpu instance lock should not be poisoned"); |
| 75 | |
| 76 | if let Some(tx) = lock_acquired_tx { |
| 77 | tx.send(()).expect("Failed to send lock acquired signal"); |
| 78 | } |
| 79 | |
| 80 | instance_lock_guard.get_or_insert_with(|| { |
| 81 | #[cfg(target_os = "linux")] |
| 82 | { |
| 83 | use crate::windowing::{winit::app::WINDOWING_SYSTEM, WindowingSystem}; |
| 84 | // If the user hasn't enabled (and is making use of) native Wayland |
| 85 | // support, due to the fact that we force use of X11 in |
| 86 | // ui/src/windowing/winit/app.rs, we need to make sure wgpu doesn't |
| 87 | // attempt to configure the instance to use Wayland, as that causes |
| 88 | // crashes due to a mismatch between the instance and the window |
| 89 | // handle we pass in later when constructing GPU resources. |
| 90 | if WINDOWING_SYSTEM |
| 91 | .get() |
| 92 | .is_some_and(|windowing_system| *windowing_system == WindowingSystem::X11) |
| 93 | || std::env::var_os("WAYLAND_DISPLAY").is_none() |
| 94 | { |
| 95 | let old_wayland_display = std::env::var_os("WAYLAND_DISPLAY"); |
| 96 | std::env::set_var("WAYLAND_DISPLAY", ""); |
| 97 | let instance = create_instance(); |
| 98 | match old_wayland_display { |
| 99 | Some(wayland_display) => { |
| 100 | std::env::set_var("WAYLAND_DISPLAY", wayland_display) |
| 101 | } |
| 102 | None => std::env::remove_var("WAYLAND_DISPLAY"), |
| 103 | }; |
| 104 | return instance; |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | create_instance() |
| 109 | }); |
| 110 | }; |
| 111 | |
| 112 | cfg_if::cfg_if! { |
| 113 | if #[cfg(target_family = "wasm")] { |
| 114 | // On wasm, synchronously initialize the wgpu static variable. |
| 115 | init_static_var(None); |
| 116 | } else { |
| 117 | // On other platforms, initialize the wgpu static variable in a separate thread to parallelize |
| 118 | // wgpu instance initialization with other application initialization. We block until we have |
| 119 | // acquired the lock on the instance, ensuring that this function doesn't return until it is |
| 120 | // safe to call `get_wgpu_instance()`. |
| 121 | let (tx, rx) = std::sync::mpsc::channel(); |
| 122 | std::thread::spawn(move || { |
| 123 | init_static_var(Some(tx)); |
| 124 | }); |
| 125 | let _ = rx.recv(); |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | /// Helper function to get a [`wgpu::Instance`]. |
| 131 | /// |
| 132 | /// This should always be used over [`wgpu::Instance::new`] or |
| 133 | /// [`wgpu::Instance::default`] to ensure that configuration is consistent |
| 134 | /// across the app. |
| 135 | fn get_wgpu_instance() -> Arc<wgpu::Instance> { |
| 136 | WGPU_INSTANCE |
| 137 | .lock() |
| 138 | .expect("wgpu instance lock should not be poisoned") |
| 139 | .as_ref() |
| 140 | .expect("wgpu instance should have been initialized") |
| 141 | .clone() |
| 142 | } |
| 143 | |
| 144 | /// Returns the set of wgpu backends that we can select from. |
| 145 | fn wgpu_backend_options() -> wgpu::Backends { |
| 146 | wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all()) |
| 147 | } |
| 148 | |
| 149 | #[cfg(not(target_family = "wasm"))] |
| 150 | pub async fn print_wgpu_adapters( |
| 151 | gpu_power_preference: GPUPowerPreference, |
| 152 | backend_preference: Option<GraphicsBackend>, |
| 153 | windowing_system: Option<windowing::System>, |
| 154 | ) { |
| 155 | let instance = get_wgpu_instance(); |
| 156 | let backends = wgpu_backend_options(); |
| 157 | let adapters = instance.enumerate_adapters(backends).await; |
| 158 | |
| 159 | let sorted = resources::sort_adapters( |
| 160 | adapters, |
| 161 | backend_preference.map(to_wgpu_backend), |
| 162 | &gpu_power_preference, |
| 163 | windowing_system, |
| 164 | // This value is only ever true after failing to render frames, which we never attempt when |
| 165 | // running in this mode. |
| 166 | false, /* downrank_non_nvidia_vulkan_adapters */ |
| 167 | ); |
| 168 | |
| 169 | for adapter in sorted { |
| 170 | let info = adapter.get_info(); |
| 171 | let device_type = info.device_type; |
| 172 | let device_name = info.name; |
| 173 | let backend = info.backend; |
| 174 | let driver = if info.driver.is_empty() { |
| 175 | "?" |
| 176 | } else { |
| 177 | &info.driver |
| 178 | }; |
| 179 | let driver_info = if info.driver_info.is_empty() { |
| 180 | String::new() |
| 181 | } else { |
| 182 | format!(" ({})", info.driver_info) |
| 183 | }; |
| 184 | println!("{device_type:?}: {device_name}\n\tBackend: {backend:?}\n\tDriver: {driver}{driver_info}"); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | /// Returns `true` if a low power GPU is available for rendering. Typically, this is true for |
| 189 | /// machines with two GPUs -- a dedicated discrete high-performance GPU and a lower power |
| 190 | /// integrated GPU. |
| 191 | #[cfg(not(target_family = "wasm"))] |
| 192 | pub async fn is_low_power_gpu_available() -> bool { |
| 193 | get_wgpu_instance() |
| 194 | .enumerate_adapters(::wgpu::Backends::all()) |
| 195 | .await |
| 196 | .iter() |
| 197 | .any(|adapter| adapter.get_info().device_type == ::wgpu::DeviceType::IntegratedGpu) |
| 198 | } |
| 199 | |
| 200 | #[cfg(target_family = "wasm")] |
| 201 | pub async fn is_low_power_gpu_available() -> bool { |
| 202 | // We return false here because we only support WebGL (not WebGPU) on the web and the former |
| 203 | // does not allow configuration of a low or high power GPU. |
| 204 | false |
| 205 | } |
| 206 | |
| 207 | #[cfg(windows)] |
| 208 | fn get_dx12_shader_compiler() -> Option<wgpu::Dx12Compiler> { |
| 209 | let dxc_path = crate::platform::windows::DXC_PATH.get()?; |
| 210 | |
| 211 | dxc_path |
| 212 | .as_ref() |
| 213 | .map(|dxc_path| wgpu::Dx12Compiler::DynamicDxc { |
| 214 | dxc_path: dxc_path.dxc_path.clone(), |
| 215 | }) |
| 216 | } |
| 217 | |
| 218 | #[cfg(not(windows))] |
| 219 | fn get_dx12_shader_compiler() -> Option<wgpu::Dx12Compiler> { |
| 220 | None |
| 221 | } |
| 222 | |
| 223 | /// Converts a [`wgpu::Backend`] to a [`GraphicsBackend`]. |
| 224 | #[cfg_attr(target_os = "macos", expect(dead_code))] |
| 225 | pub(crate) fn from_wgpu_backend(backend: wgpu::Backend) -> GraphicsBackend { |
| 226 | match backend { |
| 227 | wgpu::Backend::Noop => GraphicsBackend::Empty, |
| 228 | wgpu::Backend::Vulkan => GraphicsBackend::Vulkan, |
| 229 | wgpu::Backend::Metal => GraphicsBackend::Metal, |
| 230 | wgpu::Backend::Dx12 => GraphicsBackend::Dx12, |
| 231 | wgpu::Backend::Gl => GraphicsBackend::Gl, |
| 232 | wgpu::Backend::BrowserWebGpu => GraphicsBackend::BrowserWebGpu, |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | /// Converts a [`GraphicsBackend`] to a [`wgpu::Backend`]. |
| 237 | pub(crate) fn to_wgpu_backend(backend: GraphicsBackend) -> wgpu::Backend { |
| 238 | match backend { |
| 239 | GraphicsBackend::Empty => wgpu::Backend::Noop, |
| 240 | GraphicsBackend::Dx12 => wgpu::Backend::Dx12, |
| 241 | GraphicsBackend::Vulkan => wgpu::Backend::Vulkan, |
| 242 | GraphicsBackend::Gl => wgpu::Backend::Gl, |
| 243 | GraphicsBackend::Metal => wgpu::Backend::Metal, |
| 244 | GraphicsBackend::BrowserWebGpu => wgpu::Backend::BrowserWebGpu, |
| 245 | } |
| 246 | } |
| 247 |