Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/bevy_anti_alias/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.18.0-dev" }

# other
tracing = { version = "0.1", default-features = false, features = ["std"] }
dlss_wgpu = { version = "1", optional = true }
dlss_wgpu = { version = "2", optional = true }
uuid = { version = "1", optional = true }

[lints]
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_camera/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ bevy_color = { path = "../bevy_color", version = "0.18.0-dev", features = [
bevy_window = { path = "../bevy_window", version = "0.18.0-dev" }

# other
wgpu-types = { version = "26", default-features = false }
wgpu-types = { version = "27", default-features = false }
serde = { version = "1", default-features = false, features = ["derive"] }
thiserror = { version = "2", default-features = false }
downcast-rs = { version = "2", default-features = false, features = ["std"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_color/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ serde = { version = "1.0", features = [
], default-features = false, optional = true }
thiserror = { version = "2", default-features = false }
derive_more = { version = "2", default-features = false, features = ["from"] }
wgpu-types = { version = "26", default-features = false, optional = true }
wgpu-types = { version = "27", default-features = false, optional = true }
encase = { version = "0.12", default-features = false, optional = true }

[features]
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_image/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ image = { version = "0.25.2", default-features = false }
# misc
bitflags = { version = "2.3", features = ["serde"] }
bytemuck = { version = "1.5" }
wgpu-types = { version = "26", default-features = false }
wgpu-types = { version = "27", default-features = false }
serde = { version = "1", features = ["derive"] }
thiserror = { version = "2", default-features = false }
futures-lite = "2.0.1"
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_mesh/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ bevy_platform = { path = "../bevy_platform", version = "0.18.0-dev", default-fea
# other
bitflags = { version = "2.3", features = ["serde"] }
bytemuck = { version = "1.5" }
wgpu-types = { version = "26", default-features = false }
wgpu-types = { version = "27", default-features = false }
serde = { version = "1", default-features = false, features = [
"derive",
], optional = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_reflect/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ uuid = { version = "1.13.1", default-features = false, optional = true, features
"serde",
] }
variadics_please = "1.1"
wgpu-types = { version = "26", features = [
wgpu-types = { version = "27", features = [
"serde",
], optional = true, default-features = false }

Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ image = { version = "0.25.2", default-features = false }
# It is enabled for now to avoid having to do a significant overhaul of the renderer just for wasm.
# When the 'atomics' feature is enabled `fragile-send-sync-non-atomic` does nothing
# and Bevy instead wraps `wgpu` types to verify they are not used off their origin thread.
wgpu = { version = "26", default-features = false, features = [
wgpu = { version = "27", default-features = false, features = [
"wgsl",
"dx12",
"metal",
"vulkan",
"naga-ir",
"fragile-send-sync-non-atomic-wasm",
] }
naga = { version = "26", features = ["wgsl-in"] }
naga = { version = "27", features = ["wgsl-in"] }
bytemuck = { version = "1.5", features = ["derive", "must_cast"] }
downcast-rs = { version = "2", default-features = false, features = ["std"] }
thiserror = { version = "2", default-features = false }
Expand Down
8 changes: 3 additions & 5 deletions crates/bevy_render/src/batching/gpu_preprocessing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1109,11 +1109,9 @@ impl FromWorld for GpuPreprocessingSupport {
|| crate::get_mali_driver_version(adapter_info).is_some_and(|version| version < 48)
}

let culling_feature_support = device.features().contains(
Features::INDIRECT_FIRST_INSTANCE
| Features::MULTI_DRAW_INDIRECT
| Features::PUSH_CONSTANTS,
);
let culling_feature_support = device
.features()
.contains(Features::INDIRECT_FIRST_INSTANCE | Features::PUSH_CONSTANTS);
// Depth downsampling for occlusion culling requires 12 textures
let limit_support = device.limits().max_storage_textures_per_shader_stage >= 12 &&
// Even if the adapter supports compute, we might be simulating a lack of
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_render/src/diagnostic/tracy_gpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ fn initial_timestamp(device: &RenderDevice, queue: &RenderQueue) -> i64 {

map_buffer.slice(..).map_async(MapMode::Read, |_| ());
device
.poll(PollType::Wait)
.poll(PollType::wait_indefinitely())
.expect("Failed to poll device for map async");

let view = map_buffer.slice(..).get_mapped_range();
Expand Down
16 changes: 8 additions & 8 deletions crates/bevy_render/src/render_resource/uniform_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ impl<T: ShaderType + WriteInto> DynamicUniformBuffer<T> {
max_count: usize,
device: &RenderDevice,
queue: &'a RenderQueue,
) -> Option<DynamicUniformBufferWriter<'a, T>> {
) -> Option<DynamicUniformBufferWriter<T>> {
let alignment = if cfg!(target_abi = "sim") {
// On iOS simulator on silicon macs, metal validation check that the host OS alignment
// is respected, but the device reports the correct value for iOS, which is smaller.
Expand Down Expand Up @@ -357,27 +357,27 @@ impl<T: ShaderType + WriteInto> DynamicUniformBuffer<T> {
/// A writer that can be used to directly write elements into the target buffer.
///
/// For more information, see [`DynamicUniformBuffer::get_writer`].
pub struct DynamicUniformBufferWriter<'a, T> {
buffer: encase::DynamicUniformBuffer<QueueWriteBufferViewWrapper<'a>>,
pub struct DynamicUniformBufferWriter<T> {
buffer: encase::DynamicUniformBuffer<QueueWriteBufferViewWrapper>,
_marker: PhantomData<fn() -> T>,
}

impl<'a, T: ShaderType + WriteInto> DynamicUniformBufferWriter<'a, T> {
impl<T: ShaderType + WriteInto> DynamicUniformBufferWriter<T> {
pub fn write(&mut self, value: &T) -> u32 {
self.buffer.write(value).unwrap() as u32
}
}

/// A wrapper to work around the orphan rule so that [`wgpu::QueueWriteBufferView`] can implement
/// A wrapper to work around the orphan rule so that [`wgpu::QueueWriteBufferView`] can implement
/// [`BufferMut`].
struct QueueWriteBufferViewWrapper<'a> {
buffer_view: wgpu::QueueWriteBufferView<'a>,
struct QueueWriteBufferViewWrapper {
buffer_view: wgpu::QueueWriteBufferView,
// Must be kept separately and cannot be retrieved from buffer_view, as the read-only access will
// invoke a panic.
capacity: usize,
}

impl<'a> BufferMut for QueueWriteBufferViewWrapper<'a> {
impl BufferMut for QueueWriteBufferViewWrapper {
#[inline]
fn capacity(&self) -> usize {
self.capacity
Expand Down
21 changes: 20 additions & 1 deletion crates/bevy_render/src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ pub async fn initialize_renderer(
},
dx12: wgpu::Dx12BackendOptions {
shader_compiler: options.dx12_shader_compiler.clone(),
presentation_system: wgpu::wgt::Dx12SwapchainKind::from_env().unwrap_or_default(),
latency_waitable_object: wgpu::wgt::Dx12UseFrameLatencyWaitableObject::from_env()
.unwrap_or_default(),
},
noop: wgpu::NoopBackendOptions { enable: false },
},
Expand Down Expand Up @@ -431,14 +434,30 @@ pub async fn initialize_renderer(
max_subgroup_size: limits
.max_subgroup_size
.min(constrained_limits.max_subgroup_size),
max_acceleration_structures_per_shader_stage: 0,
max_acceleration_structures_per_shader_stage: limits
.max_acceleration_structures_per_shader_stage
.min(constrained_limits.max_acceleration_structures_per_shader_stage),
max_task_workgroup_total_count: limits
.max_task_workgroup_total_count
.min(constrained_limits.max_task_workgroup_total_count),
max_task_workgroups_per_dimension: limits
.max_task_workgroups_per_dimension
.min(constrained_limits.max_task_workgroups_per_dimension),
max_mesh_output_layers: limits
.max_mesh_output_layers
.min(constrained_limits.max_mesh_output_layers),
max_mesh_multiview_count: limits
.max_mesh_multiview_count
.min(constrained_limits.max_mesh_multiview_count),
};
}

let device_descriptor = wgpu::DeviceDescriptor {
label: options.device_label.as_ref().map(AsRef::as_ref),
required_features: features,
required_limits: limits,
// SAFETY: We're ok using experimental wgpu features in Bevy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that experimental features are not just features that may not work properly — they are features that can cause undefined behavior, which is why this is unsafe. Consider that a user of Bevy rendering might prefer not to enter that territory. Are you sure that you want to enable this unconditionally?

Regardless of the answer to that question, this // SAFETY comment could be improved.

Copy link
Contributor Author

@JMS55 JMS55 Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Bevy, we enable all wgpu features by default.

Then, specific plugins can check if certain features are enabled or not, and either say the plugin is not supported on this platform or fallback to other options.

I don't think there's much of a use case for limiting experimental features at the device level.

If you don't want e.g. raytracing to be available, you just shouldn't use Bevy plugins that depend on raytracing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • The intent of this wgpu v27 API change is that the user of the API is opting in to testing experimental features that may cause undefined behavior, as marked by an unsafe operation.
  • Bevy here serves as a (very complex) API wrapping wgpu API.
  • Therefore, Bevy should not be exposing experimental features that may cause undefined behavior, without similarly marking it (which could consist of simply having the ExperimentalFeatures token be passed into the plugin configuration — there doesn't have to be any actual unsafe fn in Bevy).

This is a (somewhat atypical) case of the general Rust principle that it should not be possible to cause UB via calling safe functions. “The user can just not call the function” is not considered sufficient in Rust.

[cc @cwfitzgerald who implemented this requirement in #8163]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Therefore, Bevy should not be exposing experimental features that may cause undefined behavior, without similarly marking it

Generally this is done via feature flags in Bevy. For instance bevy_solari is not part of DefaultPlugins - you need to specifically compile with the feature for Bevy's raytracing functionality.

But we don't gate it at the device level, and I feel strongly that this is a good design choice for us, as otherwise it requires specific plugin setup ordering.

Copy link
Contributor

@kpreid kpreid Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“Can cause undefined behavior from safe code” makes something not a good design choice. Avoiding that situation is the whole point of Rust. Please don't do this. I speak both as a contributor to wgpu and a user of Bevy.

Copy link
Contributor

@IceSentry IceSentry Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah, I mean, it can be called "unsafe_wgpu_experimental", I was more wondering if having a feature would be enough for this. The actual name of that feature I don't really mind. Makes sense to have unsafe in the name I guess.

I'm not sure what you mean in 1.? Isn't using a feature flag exposing the decision to use the unsafe api to the user already? I guess I never specified but I was assuming the feature would not be part of the default features so a user would have to specify it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean in 1.? Isn't using a feature flag exposing the decision to use the unsafe api to the user already?

Features don’t have “the” user. A feature is enabled if any dependent enables it. That dependent could be a random plugin library which enables it "because we need it" without documenting that fact, leaving the application developer unaware that the risk is being taken. (I admit this gets into “well, people can write bad code no matter what” territory.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right, I see what you mean. I'm not sure how we can avoid that though. Even if we used some kind of runtime check instead of feature flag a plugin could still set it without a user knowing. Right now I can't think of a way to make sure a user is always aware that they are opting into it. Other than maybe logging a warning at the start if it's set to make sure users are at least aware of it. But then people will likely want a way to hide that warning and then we are back to plugins hiding it unknowingly 🙃.

Copy link
Contributor Author

@JMS55 JMS55 Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My perspective is that 99% of users are not using experimental wgpu features directly. The only real people using them are developers of rendering plugins.

Bevy users should be able to trust that plugin authors used GPU features correctly, and not have to deal with enabling experimental flags themselves just to use a plugin.

We already disable stuff like checked shader code and indirect draw call validation in Bevy, it's trivial to misuse GPU APIs if you really want to.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if we used some kind of runtime check instead of feature flag a plugin could still set it without a user knowing.

Not advocating this necessarily, but it could be a flag you have to set on app, which plugins don't control. We could also have an unsafe plugin API which doesn't solve your plugins adding plugins but could help.

Bevy users should be able to trust that plugin authors used GPU features correctly, and not have to deal with enabling experimental flags themselves just to use a plugin.

IMO this is dangerously close to the "git gud" argument used by C++ partisans against safety in general. If anything, the fact that GPU APIs are easy to misuse makes safety more important. I also think it's important to distinguish UB on the GPU which sucks but is mostly limited to DoS and UB within the driver which is much more classically UB scary.

I take your point that the use of these features is already behind a flag and explicitly noted as experimental, but I think there's a tension here between having experimental features in tree (which inherently get a bit less review and testing, fewer developers working on them, etc) and safety.

I think an additional flag is a good practical middle ground. But minimally, whatever rationale we choose should be documented here more explicitly even if we are just okay blanket enabling this.

experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() },
memory_hints: options.memory_hints.clone(),
// See https://github.com/gfx-rs/wgpu/issues/5974
trace: Trace::Off,
Expand Down
13 changes: 6 additions & 7 deletions crates/bevy_render/src/renderer/render_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,18 @@ impl RenderDevice {
wgpu::ShaderSource::SpirV(source)
if self
.features()
.contains(wgpu::Features::SPIRV_SHADER_PASSTHROUGH) =>
.contains(wgpu::Features::EXPERIMENTAL_PASSTHROUGH_SHADERS) =>
{
// SAFETY:
// This call passes binary data to the backend as-is and can potentially result in a driver crash or bogus behavior.
// No attempt is made to ensure that data is valid SPIR-V.
unsafe {
self.device.create_shader_module_passthrough(
wgpu::ShaderModuleDescriptorPassthrough::SpirV(
wgpu::ShaderModuleDescriptorSpirV {
label: desc.label,
source: source.clone(),
},
),
wgpu::ShaderModuleDescriptorPassthrough {
label: desc.label,
spirv: Some(source.clone()),
..Default::default()
},
)
}
}
Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_shader/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.18.0-dev" }

# other
wgpu-types = { version = "26", default-features = false }
naga = { version = "26", features = ["wgsl-in"] }
wgpu-types = { version = "27", default-features = false }
naga = { version = "27", features = ["wgsl-in"] }
serde = { version = "1", features = ["derive"] }
thiserror = { version = "2", default-features = false }
wesl = { version = "0.2.0", optional = true }
Expand All @@ -25,12 +25,12 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# Omit the `glsl` feature in non-WebAssembly by default.
naga_oil = { version = "0.19", default-features = false, features = [
naga_oil = { version = "0.20", default-features = false, features = [
"test_shader",
] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
naga_oil = { version = "0.19" }
naga_oil = { version = "0.20" }

[features]
shader_format_glsl = ["naga/glsl-in", "naga/wgsl-out", "naga_oil/glsl"]
Expand Down
3 changes: 1 addition & 2 deletions crates/bevy_solari/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ impl PluginGroup for SolariPlugins {
impl SolariPlugins {
/// [`WgpuFeatures`] required for these plugins to function.
pub fn required_wgpu_features() -> WgpuFeatures {
WgpuFeatures::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE
| WgpuFeatures::EXPERIMENTAL_RAY_QUERY
WgpuFeatures::EXPERIMENTAL_RAY_QUERY
| WgpuFeatures::BUFFER_BINDING_ARRAY
| WgpuFeatures::TEXTURE_BINDING_ARRAY
| WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_sprite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ bevy_text = { path = "../bevy_text", version = "0.18.0-dev", optional = true }
# other
radsort = "0.1"
tracing = { version = "0.1", default-features = false, features = ["std"] }
wgpu-types = { version = "26", default-features = false }
wgpu-types = { version = "27", default-features = false }

[dev-dependencies]
approx = "0.5.1"
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_text/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ bevy_platform = { path = "../bevy_platform", version = "0.18.0-dev", default-fea
] }

# other
wgpu-types = { version = "26", default-features = false }
wgpu-types = { version = "27", default-features = false }
cosmic-text = { version = "0.15", features = ["shape-run-cache"] }
thiserror = { version = "2", default-features = false }
serde = { version = "1", features = ["derive"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_winit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ bevy_asset = { path = "../bevy_asset", version = "0.18.0-dev", optional = true }
## used by custom_cursor
bevy_image = { path = "../bevy_image", version = "0.18.0-dev", optional = true }
## used by custom_cursor
wgpu-types = { version = "26", optional = true }
wgpu-types = { version = "27", optional = true }
## used by custom_cursor
bytemuck = { version = "1.5", optional = true }

Expand Down
2 changes: 1 addition & 1 deletion examples/app/headless_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ fn receive_image_from_buffer(

// This blocks until the gpu is done executing everything
render_device
.poll(PollType::Wait)
.poll(PollType::wait_indefinitely())
.expect("Failed to poll device for map async");

// This blocks until the buffer is mapped
Expand Down
Loading