Skip to main content

bevy_render/
settings.rs

1use crate::{
2    error_handler::DeviceErrorHandler,
3    render_resource::PipelineCache,
4    renderer::{self, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue},
5    FutureRenderResources,
6};
7use alloc::borrow::Cow;
8use bevy_ecs::world::World;
9use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats};
10use bevy_window::RawHandleWrapperHolder;
11
12use wgpu::MemoryBudgetThresholds;
13pub use wgpu::{
14    Backends, Dx12Compiler, Features as WgpuFeatures, Gles3MinorVersion, InstanceFlags,
15    Limits as WgpuLimits, MemoryHints, PowerPreference,
16};
17
18/// Configures the priority used when automatically configuring the features/limits of `wgpu`.
19#[derive(Clone)]
20pub enum WgpuSettingsPriority {
21    /// WebGPU default features and limits
22    WebGPU,
23    /// The maximum supported features and limits of the adapter and backend
24    Functionality,
25    /// WebGPU default limits plus additional constraints in order to be compatible with WebGL2
26    WebGL2,
27}
28
29/// Provides configuration for renderer initialization. Use [`RenderDevice::features`](RenderDevice::features),
30/// [`RenderDevice::limits`](RenderDevice::limits), and the [`RenderAdapterInfo`]
31/// resource to get runtime information about the actual adapter, backend, features, and limits.
32/// NOTE: [`Backends::DX12`](Backends::DX12), [`Backends::METAL`](Backends::METAL), and
33/// [`Backends::VULKAN`](Backends::VULKAN) are enabled by default for non-web and the best choice
34/// is automatically selected. Web using the `webgl` feature uses [`Backends::GL`](Backends::GL).
35/// NOTE: If you want to use [`Backends::GL`](Backends::GL) in a native app on `Windows` and/or `macOS`, you must
36/// use [`ANGLE`](https://github.com/gfx-rs/wgpu#angle) and enable the `gles` feature. This is
37/// because wgpu requires EGL to create a GL context without a window and only ANGLE supports that.
38#[derive(Clone)]
39pub struct WgpuSettings {
40    pub device_label: Option<Cow<'static, str>>,
41    pub backends: Option<Backends>,
42    pub power_preference: PowerPreference,
43    pub priority: WgpuSettingsPriority,
44    /// The features to ensure are enabled regardless of what the adapter/backend supports.
45    /// Setting these explicitly may cause renderer initialization to fail.
46    pub features: WgpuFeatures,
47    /// The features to ensure are disabled regardless of what the adapter/backend supports
48    pub disabled_features: Option<WgpuFeatures>,
49    /// The imposed limits.
50    pub limits: WgpuLimits,
51    /// The constraints on limits allowed regardless of what the adapter/backend supports
52    pub constrained_limits: Option<WgpuLimits>,
53    /// The shader compiler to use for the DX12 backend.
54    pub dx12_shader_compiler: Dx12Compiler,
55    /// Allows you to choose which minor version of GLES3 to use (3.0, 3.1, 3.2, or automatic)
56    /// This only applies when using ANGLE and the GL backend.
57    pub gles3_minor_version: Gles3MinorVersion,
58    /// These are for controlling WGPU's debug information to eg. enable validation and shader debug info in release builds.
59    pub instance_flags: InstanceFlags,
60    /// This hints to the WGPU device about the preferred memory allocation strategy.
61    pub memory_hints: MemoryHints,
62    /// The thresholds for device memory budget.
63    pub instance_memory_budget_thresholds: MemoryBudgetThresholds,
64    /// If true, will force wgpu to use a software renderer, if available.
65    pub force_fallback_adapter: bool,
66    /// The name of the adapter to use.
67    pub adapter_name: Option<String>,
68}
69
70impl Default for WgpuSettings {
71    fn default() -> Self {
72        let default_backends = if cfg!(all(
73            feature = "webgl",
74            target_arch = "wasm32",
75            not(feature = "webgpu")
76        )) {
77            Backends::GL
78        } else if cfg!(all(feature = "webgpu", target_arch = "wasm32")) {
79            Backends::BROWSER_WEBGPU
80        } else {
81            Backends::all()
82        };
83
84        let backends = Some(Backends::from_env().unwrap_or(default_backends));
85
86        let power_preference =
87            PowerPreference::from_env().unwrap_or(PowerPreference::HighPerformance);
88
89        let priority = settings_priority_from_env().unwrap_or(WgpuSettingsPriority::Functionality);
90
91        let limits = if cfg!(all(
92            feature = "webgl",
93            target_arch = "wasm32",
94            not(feature = "webgpu")
95        )) || matches!(priority, WgpuSettingsPriority::WebGL2)
96        {
97            wgpu::Limits::downlevel_webgl2_defaults()
98        } else {
99            #[expect(clippy::allow_attributes, reason = "`unused_mut` is not always linted")]
100            #[allow(
101                unused_mut,
102                reason = "This variable needs to be mutable if the `ci_limits` feature is enabled"
103            )]
104            let mut limits = wgpu::Limits::default();
105            #[cfg(feature = "ci_limits")]
106            {
107                limits.max_storage_textures_per_shader_stage = 4;
108                limits.max_texture_dimension_3d = 1024;
109            }
110            limits
111        };
112
113        let dx12_shader_compiler =
114            Dx12Compiler::from_env().unwrap_or(if cfg!(feature = "statically-linked-dxc") {
115                Dx12Compiler::StaticDxc
116            } else {
117                let dxc = "dxcompiler.dll";
118
119                if cfg!(target_os = "windows") && std::fs::metadata(dxc).is_ok() {
120                    Dx12Compiler::DynamicDxc {
121                        dxc_path: String::from(dxc),
122                    }
123                } else {
124                    Dx12Compiler::Fxc
125                }
126            });
127
128        let gles3_minor_version = Gles3MinorVersion::from_env().unwrap_or_default();
129
130        let mut instance_flags = InstanceFlags::default();
131        #[cfg(not(debug_assertions))]
132        {
133            // wgpu executes additional necessary logic during validation passes for the DX12 backend,
134            // so the `VALIDATION_INDIRECT_CALL` flag should stay for DX12.
135            if !backends.is_some_and(|backends| backends.contains(Backends::DX12)) {
136                // Removing this flag improves performance.
137                instance_flags.remove(InstanceFlags::VALIDATION_INDIRECT_CALL);
138            }
139        }
140        #[cfg(all(not(debug_assertions), feature = "raw_vulkan_init"))]
141        // intending to use vulkan even if backends may contain DX12
142        instance_flags.remove(InstanceFlags::VALIDATION_INDIRECT_CALL);
143
144        instance_flags = instance_flags.with_env();
145
146        Self {
147            device_label: Default::default(),
148            backends,
149            power_preference,
150            priority,
151            features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES,
152            disabled_features: None,
153            limits,
154            constrained_limits: None,
155            dx12_shader_compiler,
156            gles3_minor_version,
157            instance_flags,
158            memory_hints: MemoryHints::default(),
159            instance_memory_budget_thresholds: MemoryBudgetThresholds::default(),
160            force_fallback_adapter: false,
161            adapter_name: None,
162        }
163    }
164}
165
166#[derive(Clone)]
167pub struct RenderResources(
168    pub RenderDevice,
169    pub RenderQueue,
170    pub RenderAdapterInfo,
171    pub RenderAdapter,
172    pub RenderInstance,
173    #[cfg(feature = "raw_vulkan_init")] pub renderer::raw_vulkan_init::AdditionalVulkanFeatures,
174);
175
176impl RenderResources {
177    /// Effectively, this replaces the current render backend entirely with the given resources.
178    ///
179    /// We deconstruct the [`RenderResources`] and make them usable by the main and render worlds,
180    /// and insert [`PipelineCache`] and [`CompressedImageFormats`] which directly depend on having
181    /// references to these resources within them to be accurate. This causes all shaders to
182    /// be recompiled, and the set of supported images to possibly change. This is necessary
183    /// because the new backend may have different compression support or shader language.
184    pub(crate) fn unpack_into(
185        self,
186        main_world: &mut World,
187        render_world: &mut World,
188        synchronous_pipeline_compilation: bool,
189    ) {
190        let RenderResources(device, queue, adapter_info, render_adapter, instance, ..) = self;
191
192        let compressed_image_format_support =
193            CompressedImageFormatSupport(CompressedImageFormats::from_features(device.features()));
194
195        main_world.insert_resource(device.clone());
196        main_world.insert_resource(queue.clone());
197        main_world.insert_resource(adapter_info.clone());
198        main_world.insert_resource(render_adapter.clone());
199        main_world.insert_resource(compressed_image_format_support);
200
201        #[cfg(feature = "raw_vulkan_init")]
202        {
203            let additional_vulkan_features: renderer::raw_vulkan_init::AdditionalVulkanFeatures =
204                self.5;
205            render_world.insert_resource(additional_vulkan_features);
206        }
207
208        render_world.insert_resource(instance);
209        render_world.insert_resource(PipelineCache::new(
210            device.clone(),
211            synchronous_pipeline_compilation,
212        ));
213        render_world.insert_resource(DeviceErrorHandler::new(&device));
214        render_world.insert_resource(device);
215        render_world.insert_resource(queue);
216        render_world.insert_resource(render_adapter);
217        render_world.insert_resource(adapter_info);
218    }
219}
220
221/// An enum describing how the renderer will initialize resources. This is used when creating the [`RenderPlugin`](crate::RenderPlugin).
222pub enum RenderCreation {
223    /// Allows renderer resource initialization to happen outside of the rendering plugin.
224    Manual(Box<RenderResources>),
225    /// Lets the rendering plugin create resources itself.
226    Automatic(Box<WgpuSettings>),
227}
228
229impl RenderCreation {
230    /// Function to create a [`RenderCreation::Manual`] variant.
231    pub fn manual(
232        device: RenderDevice,
233        queue: RenderQueue,
234        adapter_info: RenderAdapterInfo,
235        adapter: RenderAdapter,
236        instance: RenderInstance,
237        #[cfg(feature = "raw_vulkan_init")]
238        additional_vulkan_features: renderer::raw_vulkan_init::AdditionalVulkanFeatures,
239    ) -> Self {
240        RenderResources(
241            device,
242            queue,
243            adapter_info,
244            adapter,
245            instance,
246            #[cfg(feature = "raw_vulkan_init")]
247            additional_vulkan_features,
248        )
249        .into()
250    }
251
252    /// Creates [`RenderResources`] from this [`RenderCreation`] and an optional primary window
253    /// and writes them into `future_resources`, possibly asynchronously.
254    ///
255    /// Returns true if creation was successful, false otherwise.
256    ///
257    /// Note: [`RenderCreation::Manual`] will ignore the provided primary window.
258    pub(crate) fn create_render(
259        &self,
260        future_resources: FutureRenderResources,
261        primary_window: Option<RawHandleWrapperHolder>,
262        #[cfg(feature = "raw_vulkan_init")]
263        raw_vulkan_init_settings: renderer::raw_vulkan_init::RawVulkanInitSettings,
264    ) -> bool {
265        match self {
266            RenderCreation::Manual(resources) => {
267                *future_resources.lock().unwrap() = Some(*resources.clone());
268            }
269            RenderCreation::Automatic(render_creation) => {
270                let Some(backends) = render_creation.backends else {
271                    return false;
272                };
273                let settings = render_creation.clone();
274
275                let async_renderer = async move {
276                    let render_resources = renderer::initialize_renderer(
277                        backends,
278                        primary_window,
279                        &settings,
280                        #[cfg(feature = "raw_vulkan_init")]
281                        raw_vulkan_init_settings,
282                    )
283                    .await;
284
285                    *future_resources.lock().unwrap() = Some(render_resources);
286                };
287
288                // In wasm, spawn a task and detach it for execution
289                #[cfg(target_arch = "wasm32")]
290                bevy_tasks::IoTaskPool::get()
291                    .spawn_local(async_renderer)
292                    .detach();
293                // Otherwise, just block for it to complete
294                #[cfg(not(target_arch = "wasm32"))]
295                bevy_tasks::block_on(async_renderer);
296            }
297        }
298        true
299    }
300}
301
302impl From<RenderResources> for RenderCreation {
303    fn from(value: RenderResources) -> Self {
304        Self::Manual(Box::new(value))
305    }
306}
307
308impl Default for RenderCreation {
309    fn default() -> Self {
310        Self::Automatic(Default::default())
311    }
312}
313
314impl From<WgpuSettings> for RenderCreation {
315    fn from(value: WgpuSettings) -> Self {
316        Self::Automatic(Box::new(value))
317    }
318}
319
320/// Get a features/limits priority from the environment variable `WGPU_SETTINGS_PRIO`
321pub fn settings_priority_from_env() -> Option<WgpuSettingsPriority> {
322    Some(
323        match std::env::var("WGPU_SETTINGS_PRIO")
324            .as_deref()
325            .map(str::to_lowercase)
326            .as_deref()
327        {
328            Ok("webgpu") => WgpuSettingsPriority::WebGPU,
329            Ok("functionality") => WgpuSettingsPriority::Functionality,
330            Ok("webgl2") => WgpuSettingsPriority::WebGL2,
331            _ => return None,
332        },
333    )
334}