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