Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/rendering/wgpu/mod.rs
1pub mod renderer;
2mod resources;
3mod shader_types;
4mod texture_with_bind_group;
5 
6use std::sync::{Arc, LazyLock, Mutex};
7 
8use wgpu::wgt::WgpuHasDisplayHandle;
9 
10pub use renderer::Renderer;
11pub use resources::{adapter_has_rendering_offset_bug, Resources};
12 
13use crate::platform::GraphicsBackend;
14#[cfg(not(target_family = "wasm"))]
15use crate::{rendering::GPUPowerPreference, windowing};
16 
17static WGPU_INSTANCE: LazyLock<Mutex<Option<Arc<wgpu::Instance>>>> = LazyLock::new(Mutex::default);
18 
19/// Drops and recreates the global shared [`wgpu::Instance`].
20pub 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()`].
34pub 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.
135fn 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.
145fn wgpu_backend_options() -> wgpu::Backends {
146 wgpu::Backends::from_env().unwrap_or(wgpu::Backends::all())
147}
148 
149#[cfg(not(target_family = "wasm"))]
150pub 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"))]
192pub 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")]
201pub 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)]
208fn 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))]
219fn 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))]
225pub(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`].
237pub(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