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            render_adapter.clone(),
212            synchronous_pipeline_compilation,
213        ));
214        render_world.insert_resource(DeviceErrorHandler::new(&device));
215        render_world.insert_resource(device);
216        render_world.insert_resource(queue);
217        render_world.insert_resource(render_adapter);
218        render_world.insert_resource(adapter_info);
219    }
220}
221
222/// An enum describing how the renderer will initialize resources. This is used when creating the [`RenderPlugin`](crate::RenderPlugin).
223pub enum RenderCreation {
224    /// Allows renderer resource initialization to happen outside of the rendering plugin.
225    Manual(RenderResources),
226    /// Lets the rendering plugin create resources itself.
227    Automatic(Box<WgpuSettings>),
228}
229
230impl RenderCreation {
231    /// Function to create a [`RenderCreation::Manual`] variant.
232    pub fn manual(
233        device: RenderDevice,
234        queue: RenderQueue,
235        adapter_info: RenderAdapterInfo,
236        adapter: RenderAdapter,
237        instance: RenderInstance,
238        #[cfg(feature = "raw_vulkan_init")]
239        additional_vulkan_features: renderer::raw_vulkan_init::AdditionalVulkanFeatures,
240    ) -> Self {
241        RenderResources(
242            device,
243            queue,
244            adapter_info,
245            adapter,
246            instance,
247            #[cfg(feature = "raw_vulkan_init")]
248            additional_vulkan_features,
249        )
250        .into()
251    }
252
253    /// Creates [`RenderResources`] from this [`RenderCreation`] and an optional primary window
254    /// and writes them into `future_resources`, possibly asynchronously.
255    ///
256    /// Returns true if creation was successful, false otherwise.
257    ///
258    /// Note: [`RenderCreation::Manual`] will ignore the provided primary window.
259    pub(crate) fn create_render(
260        &self,
261        future_resources: FutureRenderResources,
262        primary_window: Option<RawHandleWrapperHolder>,
263        #[cfg(feature = "raw_vulkan_init")]
264        raw_vulkan_init_settings: renderer::raw_vulkan_init::RawVulkanInitSettings,
265    ) -> bool {
266        match self {
267            RenderCreation::Manual(resources) => {
268                *future_resources.lock().unwrap() = Some(resources.clone());
269            }
270            RenderCreation::Automatic(render_creation) => {
271                let Some(backends) = render_creation.backends else {
272                    return false;
273                };
274                let settings = render_creation.clone();
275
276                let async_renderer = async move {
277                    let render_resources = renderer::initialize_renderer(
278                        backends,
279                        primary_window,
280                        &settings,
281                        #[cfg(feature = "raw_vulkan_init")]
282                        raw_vulkan_init_settings,
283                    )
284                    .await;
285
286                    *future_resources.lock().unwrap() = Some(render_resources);
287                };
288
289                // In wasm, spawn a task and detach it for execution
290                #[cfg(target_arch = "wasm32")]
291                bevy_tasks::IoTaskPool::get()
292                    .spawn_local(async_renderer)
293                    .detach();
294                // Otherwise, just block for it to complete
295                #[cfg(not(target_arch = "wasm32"))]
296                bevy_tasks::block_on(async_renderer);
297            }
298        }
299        true
300    }
301}
302
303impl From<RenderResources> for RenderCreation {
304    fn from(value: RenderResources) -> Self {
305        Self::Manual(value)
306    }
307}
308
309impl Default for RenderCreation {
310    fn default() -> Self {
311        Self::Automatic(Default::default())
312    }
313}
314
315impl From<WgpuSettings> for RenderCreation {
316    fn from(value: WgpuSettings) -> Self {
317        Self::Automatic(Box::new(value))
318    }
319}
320
321/// Get a features/limits priority from the environment variable `WGPU_SETTINGS_PRIO`
322pub fn settings_priority_from_env() -> Option<WgpuSettingsPriority> {
323    Some(
324        match std::env::var("WGPU_SETTINGS_PRIO")
325            .as_deref()
326            .map(str::to_lowercase)
327            .as_deref()
328        {
329            Ok("webgpu") => WgpuSettingsPriority::WebGPU,
330            Ok("functionality") => WgpuSettingsPriority::Functionality,
331            Ok("webgl2") => WgpuSettingsPriority::WebGL2,
332            _ => return None,
333        },
334    )
335}