[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4c6e26bc8ef45..6c334489b064a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -304,7 +304,7 @@ jobs:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Run Markdown Lint
- uses: super-linter/super-linter/slim@v8.2.1
+ uses: super-linter/super-linter/slim@v8.3.0
env:
MULTI_STATUS: false
VALIDATE_ALL_CODEBASE: false
From 1285ccf7c0548c2605bafabe3ce1dc04d564194f Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 1 Dec 2025 06:57:49 +0000
Subject: [PATCH 002/133] Bump crate-ci/typos from 1.39.2 to 1.40.0 (#21993)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.39.2 to
1.40.0.
Release notes
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6c334489b064a..b687b032e3d13 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -336,7 +336,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Check for typos
- uses: crate-ci/typos@v1.39.2
+ uses: crate-ci/typos@v1.40.0
- name: Typos info
if: failure()
run: |
From c378b7aa7e36a3629fa739ff823d006cb1ebd9cb Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 1 Dec 2025 07:01:32 +0000
Subject: [PATCH 003/133] Update criterion requirement from 0.7.0 to 0.8.0
(#21994)
Updates the requirements on
[criterion](https://github.com/criterion-rs/criterion.rs) to permit the
latest version.
Changelog
Add support for reporting throughput in elements and bytes -
Throughput::ElementsAndBytes allows the text summary to
report throughput in both units simultaneously.
Add alloca-based memory layout randomisation to mitigate memory
effects on measurements.
Add doc comment to benchmark runner in criterion_group macro
(removes linter warnings)
Fixed
Fix plotting NaN bug
Other
Remove Master API Docs links temporarily while we restore the docs
publishing.
[0.7.0] - 2025-07-25
Bump version of criterion-plot to align dependencies.
[0.6.0] - 2025-05-17
Changed
MSRV bumped to 1.80
The real_blackbox feature no longer has any impact.
Criterion always uses std::hint::black_box() now.
Users of criterion::black_box() should switch to
std::hint::black_box().
clap dependency unpinned.
Fixed
gnuplot version is now correctly detected when using certain Windows
binaries/configurations that used to fail
Added
Async benchmarking with Tokio may be done via a
tokio::runtime::Handle, not only a
tokio::runtime::Runtime
[0.5.1] - 2023-05-26
Fixed
Quick mode (--quick) no longer crashes with measured times over 5
seconds when --noplot is not active
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
benches/Cargo.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/benches/Cargo.toml b/benches/Cargo.toml
index f569def4edbfd..8940f1eed7105 100644
--- a/benches/Cargo.toml
+++ b/benches/Cargo.toml
@@ -10,7 +10,7 @@ autobenches = false
[dependencies]
# The primary crate that runs and analyzes our benchmarks. This is a regular dependency because the
# `bench!` macro refers to it in its documentation.
-criterion = { version = "0.7.0", features = ["html_reports"] }
+criterion = { version = "0.8.0", features = ["html_reports"] }
[dev-dependencies]
# Bevy crates
From 5fbc9f1d3efb6da8bb557c2e8ae97344c97e36ef Mon Sep 17 00:00:00 2001
From: ickshonpe
Date: Mon, 1 Dec 2025 23:14:21 +0000
Subject: [PATCH 004/133] `PointerbuttonState::clear` (#21997)
# Objective
To clear the `PointerButtonState`, `clear` is called on each of its
individual fields holding the button state data. Instead add a `clear`
method to `PointerButtonState`.
## Solution
Add a `clear` method to `PointerButtonState` to clear its data.
---------
Co-authored-by: Kevin Chen
---
crates/bevy_picking/src/events.rs | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs
index 4528001f0e429..4fb10121efde8 100644
--- a/crates/bevy_picking/src/events.rs
+++ b/crates/bevy_picking/src/events.rs
@@ -351,6 +351,15 @@ pub struct PointerButtonState {
pub dragging_over: HashMap,
}
+impl PointerButtonState {
+ /// Clears all press and drag data tracked for this button on its pointer.
+ pub fn clear(&mut self) {
+ self.pressing.clear();
+ self.dragging.clear();
+ self.dragging_over.clear();
+ }
+}
+
/// State for all pointers.
#[derive(Debug, Clone, Default, Resource)]
pub struct PointerState {
@@ -379,9 +388,7 @@ impl PointerState {
pub fn clear(&mut self, pointer_id: PointerId) {
for button in PointerButton::iter() {
if let Some(state) = self.pointer_buttons.get_mut(&(pointer_id, button)) {
- state.pressing.clear();
- state.dragging.clear();
- state.dragging_over.clear();
+ state.clear();
}
}
}
@@ -699,9 +706,7 @@ pub fn pointer_events(
}
// Finally, we can clear the state of everything relating to presses or drags.
- state.pressing.clear();
- state.dragging.clear();
- state.dragging_over.clear();
+ state.clear();
}
// Moved
PointerAction::Move { delta } => {
From 1f97c83bf8d70736c051dbf566da5b901ae90d52 Mon Sep 17 00:00:00 2001
From: Sarthak Singh <35749450+SarthakSingh31@users.noreply.github.com>
Date: Tue, 2 Dec 2025 13:35:12 +0530
Subject: [PATCH 005/133] Fix crash on exit due to destroyed surface texture
(#21976)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
# Objective
Fix crash when a window is closed on current main
Crash log
```
❯ RUST_BACKTRACE=full cargo run --example 3d_scene -F dev
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
Running `target/debug/examples/3d_scene`
2025-11-29T21:39:06.551816Z INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "Linux (Arch Linux rolling)", kernel: "6.17.9-arch1-1", cpu: "AMD Ryzen 9 9950X 16-Core Processor", core_count: "16", memory: "186.4 GiB" }
2025-11-29T21:39:06.589438Z INFO bevy_render::renderer: AdapterInfo { name: "NVIDIA GeForce RTX 5080", vendor: 4318, device: 11266, device_type: DiscreteGpu, driver: "NVIDIA", driver_info: "580.105.08", backend: Vulkan }
2025-11-29T21:39:06.953148Z INFO bevy_render::batching::gpu_preprocessing: GPU preprocessing is fully supported on this device.
2025-11-29T21:39:06.959263Z INFO bevy_winit::system: Creating new window 3d_scene (0v0)
2025-11-29T21:39:09.503214Z INFO bevy_window::system: No windows are open, exiting
2025-11-29T21:39:09.504687Z INFO bevy_winit::system: Closing window 0v0
2025-11-29T21:39:09.510808Z ERROR wgpu::backend::wgpu_core: Handling wgpu errors as fatal by default
thread '' (664835) panicked at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wgpu-26.0.1/src/backend/wgpu_core.rs:1852:22:
wgpu error: Validation Error
Caused by:
In Queue::submit
In a pass parameter
Texture with '' label has been destroyed
stack backtrace:
0: 0x557069cd5e42 - std[cbf56a46dac989f5]::backtrace_rs::backtrace::libunwind::trace
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/../../backtrace/src/backtrace/libunwind.rs:117:9
1: 0x557069cd5e42 - std[cbf56a46dac989f5]::backtrace_rs::backtrace::trace_unsynchronized::
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/../../backtrace/src/backtrace/mod.rs:66:14
2: 0x557069cd5e42 - std[cbf56a46dac989f5]::sys::backtrace::_print_fmt
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/sys/backtrace.rs:68:9
3: 0x557069cd5e42 - <::print::DisplayBacktrace as core[a3424aac13c77cf6]::fmt::Display>::fmt
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/sys/backtrace.rs:38:26
4: 0x557069cf05e7 - ::fmt
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/core/src/fmt/rt.rs:152:76
5: 0x557069cf05e7 - core[a3424aac13c77cf6]::fmt::write
6: 0x557069cdc5d6 - std[cbf56a46dac989f5]::io::default_write_fmt::
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/io/mod.rs:639:11
7: 0x557069cdc5d6 - ::write_fmt
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/io/mod.rs:1994:13
8: 0x557069cb255a - ::print
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/sys/backtrace.rs:41:9
9: 0x557069cb255a - std[cbf56a46dac989f5]::panicking::default_hook::{closure#0}
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/panicking.rs:292:27
10: 0x557069cce7e1 - std[cbf56a46dac989f5]::panicking::default_hook
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/panicking.rs:319:9
11: 0x5570696cd6b3 - core[a3424aac13c77cf6]::ops::function::Fn<(&'a std[cbf56a46dac989f5]::panic::PanicHookInfo<'b>,), Output = ()> + core[a3424aac13c77cf6]::marker::Send + core[a3424aac13c77cf6]::marker::Sync> as core[a3424aac13c77cf6]::ops::function::Fn<(&std[cbf56a46dac989f5]::panic::PanicHookInfo,)>>::call
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:2220:9
12: 0x5570696b9081 - bevy_ecs[ae55bfe1c74e77a3]::error::bevy_error::bevy_error_panic_hook:: core[a3424aac13c77cf6]::ops::function::Fn<(&'a std[cbf56a46dac989f5]::panic::PanicHookInfo<'b>,), Output = ()> + core[a3424aac13c77cf6]::marker::Send + core[a3424aac13c77cf6]::marker::Sync>>::{closure#0}
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/error/bevy_error.rs:162:9
13: 0x557069ccea82 - core[a3424aac13c77cf6]::ops::function::Fn<(&'a std[cbf56a46dac989f5]::panic::PanicHookInfo<'b>,), Output = ()> + core[a3424aac13c77cf6]::marker::Send + core[a3424aac13c77cf6]::marker::Sync> as core[a3424aac13c77cf6]::ops::function::Fn<(&std[cbf56a46dac989f5]::panic::PanicHookInfo,)>>::call
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/alloc/src/boxed.rs:2220:9
14: 0x557069ccea82 - std[cbf56a46dac989f5]::panicking::panic_with_hook
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/panicking.rs:833:13
15: 0x557069cb2618 - std[cbf56a46dac989f5]::panicking::panic_handler::{closure#0}
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/panicking.rs:698:13
16: 0x557069ca9779 - std[cbf56a46dac989f5]::sys::backtrace::__rust_end_short_backtrace::
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/sys/backtrace.rs:176:18
17: 0x557069cb37cd - __rustc[f3f8441e4970532f]::rust_begin_unwind
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/panicking.rs:689:5
18: 0x557069cf0d5c - core[a3424aac13c77cf6]::panicking::panic_fmt
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/core/src/panicking.rs:80:14
19: 0x5570675ffab5 - wgpu[932fed8fce9d4b4b]::backend::wgpu_core::default_error_handler
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wgpu-26.0.1/src/backend/wgpu_core.rs:659:5
20: 0x5570675ff6e5 - ::handle_error
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wgpu-26.0.1/src/backend/wgpu_core.rs:643:21
21: 0x5570675ff3f4 - ::handle_error_inner
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wgpu-26.0.1/src/backend/wgpu_core.rs:298:14
22: 0x5570675fcda3 - ::handle_error_nolabel::
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wgpu-26.0.1/src/backend/wgpu_core.rs:323:14
23: 0x55706760ed17 - ::submit
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wgpu-26.0.1/src/backend/wgpu_core.rs:1852:22
24: 0x5570672eef2a - ::submit::>
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wgpu-26.0.1/src/api/queue.rs:253:32
25: 0x557066f4965c - ::run::
at /home/ssingh/Projects/bevy/crates/bevy_render/src/renderer/graph_runner.rs:87:19
26: 0x5570670a05ad - bevy_render[cbe98d17828001b5]::renderer::render_system
at /home/ssingh/Projects/bevy/crates/bevy_render/src/renderer/mod.rs:48:15
27: 0x55706744d021 - >)>>::call_mut
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:166:5
28: 0x5570670ad864 - <&mut bevy_render[cbe98d17828001b5]::renderer::render_system as core[a3424aac13c77cf6]::ops::function::FnMut<(&mut bevy_ecs[ae55bfe1c74e77a3]::world::World, &mut bevy_ecs[ae55bfe1c74e77a3]::system::function_system::SystemState>)>>::call_mut
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:298:21
29: 0x55706735dc8e - <_ as bevy_ecs[ae55bfe1c74e77a3]::system::exclusive_function_system::ExclusiveSystemParamFunction _>>::run::call_inner::<(), &mut bevy_ecs[ae55bfe1c74e77a3]::system::function_system::SystemState>, &mut bevy_render[cbe98d17828001b5]::renderer::render_system>
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/system/exclusive_function_system.rs:270:21
30: 0x5570670ad95e - >)>>::run
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/system/exclusive_function_system.rs:273:17
31: 0x55706735e0ab - >), (), bevy_render[cbe98d17828001b5]::renderer::render_system> as bevy_ecs[ae55bfe1c74e77a3]::system::system::System>::run_unsafe::{closure#0}
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/system/exclusive_function_system.rs:135:33
32: 0x55706702ac65 - ::last_change_tick_scope::, >), (), bevy_render[cbe98d17828001b5]::renderer::render_system> as bevy_ecs[ae55bfe1c74e77a3]::system::system::System>::run_unsafe::{closure#0}>
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/world/mod.rs:3070:9
33: 0x55706735eb0a - >), (), bevy_render[cbe98d17828001b5]::renderer::render_system> as bevy_ecs[ae55bfe1c74e77a3]::system::system::System>::run_unsafe
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/system/exclusive_function_system.rs:113:15
34: 0x55706735f7ab - >), (), bevy_render[cbe98d17828001b5]::renderer::render_system> as bevy_ecs[ae55bfe1c74e77a3]::system::system::System>::run_without_applying_deferred
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/system/system.rs:138:23
35: 0x55706735f806 - >), (), bevy_render[cbe98d17828001b5]::renderer::render_system> as bevy_ecs[ae55bfe1c74e77a3]::system::system::System>::run
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/system/system.rs:119:24
36: 0x55706988d090 - bevy_ecs[ae55bfe1c74e77a3]::schedule::executor::__rust_begin_short_backtrace::run
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/schedule/executor/mod.rs:288:29
37: 0x5570697be94d - ::spawn_exclusive_system_task::{closure#1}::{closure#0}
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs:742:25
38: 0x5570697254f9 - <::spawn_exclusive_system_task::{closure#1}::{closure#0} as core[a3424aac13c77cf6]::ops::function::FnOnce<()>>::call_once
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
39: 0x55706977f259 - ::spawn_exclusive_system_task::{closure#1}::{closure#0}> as core[a3424aac13c77cf6]::ops::function::FnOnce<()>>::call_once
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9
40: 0x5570697c3253 - std[cbf56a46dac989f5]::panicking::catch_unwind::do_call::::spawn_exclusive_system_task::{closure#1}::{closure#0}>, ()>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40
41: 0x557069887a9b - __rust_try
42: 0x557069886fae - std[cbf56a46dac989f5]::panicking::catch_unwind::<(), core[a3424aac13c77cf6]::panic::unwind_safe::AssertUnwindSafe<::spawn_exclusive_system_task::{closure#1}::{closure#0}>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19
43: 0x557069886fae - std[cbf56a46dac989f5]::panic::catch_unwind::::spawn_exclusive_system_task::{closure#1}::{closure#0}>, ()>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14
44: 0x5570697bf233 - ::spawn_exclusive_system_task::{closure#1}
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs:740:27
45: 0x55706977f642 - ::spawn_exclusive_system_task::{closure#1}> as core[a3424aac13c77cf6]::future::future::Future>::poll
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:299:9
46: 0x557069765c04 - ::spawn_exclusive_system_task::{closure#1}>> as core[a3424aac13c77cf6]::future::future::Future>::poll::{closure#0}
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-lite-2.6.1/src/future.rs:653:53
47: 0x55706977f4f4 - ::spawn_exclusive_system_task::{closure#1}>> as core[a3424aac13c77cf6]::future::future::Future>::poll::{closure#0}> as core[a3424aac13c77cf6]::ops::function::FnOnce<()>>::call_once
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9
48: 0x5570697c374d - std[cbf56a46dac989f5]::panicking::catch_unwind::do_call::::spawn_exclusive_system_task::{closure#1}>> as core[a3424aac13c77cf6]::future::future::Future>::poll::{closure#0}>, core[a3424aac13c77cf6]::task::poll::Poll<()>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40
49: 0x557069887a9b - __rust_try
50: 0x557069887a24 - std[cbf56a46dac989f5]::panicking::catch_unwind::, core[a3424aac13c77cf6]::panic::unwind_safe::AssertUnwindSafe<::spawn_exclusive_system_task::{closure#1}>> as core[a3424aac13c77cf6]::future::future::Future>::poll::{closure#0}>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19
51: 0x557069887a24 - std[cbf56a46dac989f5]::panic::catch_unwind::::spawn_exclusive_system_task::{closure#1}>> as core[a3424aac13c77cf6]::future::future::Future>::poll::{closure#0}>, core[a3424aac13c77cf6]::task::poll::Poll<()>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14
52: 0x557069766ae8 - ::spawn_exclusive_system_task::{closure#1}>> as core[a3424aac13c77cf6]::future::future::Future>::poll
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-lite-2.6.1/src/future.rs:653:9
53: 0x557069892db3 - ::spawn_exclusive_system_task::{closure#1}>>, ::spawn_inner>, futures_lite[d9f7489fbab33684]::future::CatchUnwind::spawn_exclusive_system_task::{closure#1}>>>::{closure#0}> as core[a3424aac13c77cf6]::future::future::Future>::poll
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.2/src/lib.rs:1179:31
54: 0x557069896c98 - ::spawn_exclusive_system_task::{closure#1}>>, ::spawn_inner>, futures_lite[d9f7489fbab33684]::future::CatchUnwind::spawn_exclusive_system_task::{closure#1}>>>::{closure#0}>, core[a3424aac13c77cf6]::result::Result<(), alloc[9fa3fef380221c31]::boxed::Box>, ::schedule::{closure#0}, ()>>::run::{closure#1}
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-task-4.7.1/src/raw.rs:550:21
55: 0x557069725734 - <::spawn_exclusive_system_task::{closure#1}>>, ::spawn_inner>, futures_lite[d9f7489fbab33684]::future::CatchUnwind::spawn_exclusive_system_task::{closure#1}>>>::{closure#0}>, core[a3424aac13c77cf6]::result::Result<(), alloc[9fa3fef380221c31]::boxed::Box>, ::schedule::{closure#0}, ()>>::run::{closure#1} as core[a3424aac13c77cf6]::ops::function::FnOnce<()>>::call_once
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
56: 0x55706977f2db - ::spawn_exclusive_system_task::{closure#1}>>, ::spawn_inner>, futures_lite[d9f7489fbab33684]::future::CatchUnwind::spawn_exclusive_system_task::{closure#1}>>>::{closure#0}>, core[a3424aac13c77cf6]::result::Result<(), alloc[9fa3fef380221c31]::boxed::Box>, ::schedule::{closure#0}, ()>>::run::{closure#1}> as core[a3424aac13c77cf6]::ops::function::FnOnce<()>>::call_once
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9
57: 0x5570697c336e - std[cbf56a46dac989f5]::panicking::catch_unwind::do_call::::spawn_exclusive_system_task::{closure#1}>>, ::spawn_inner>, futures_lite[d9f7489fbab33684]::future::CatchUnwind::spawn_exclusive_system_task::{closure#1}>>>::{closure#0}>, core[a3424aac13c77cf6]::result::Result<(), alloc[9fa3fef380221c31]::boxed::Box>, ::schedule::{closure#0}, ()>>::run::{closure#1}>, core[a3424aac13c77cf6]::task::poll::Poll>>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40
58: 0x557069887a9b - __rust_try
59: 0x5570698871c4 - std[cbf56a46dac989f5]::panicking::catch_unwind::>>, core[a3424aac13c77cf6]::panic::unwind_safe::AssertUnwindSafe<::spawn_exclusive_system_task::{closure#1}>>, ::spawn_inner>, futures_lite[d9f7489fbab33684]::future::CatchUnwind::spawn_exclusive_system_task::{closure#1}>>>::{closure#0}>, core[a3424aac13c77cf6]::result::Result<(), alloc[9fa3fef380221c31]::boxed::Box>, ::schedule::{closure#0}, ()>>::run::{closure#1}>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19
60: 0x5570698871c4 - std[cbf56a46dac989f5]::panic::catch_unwind::::spawn_exclusive_system_task::{closure#1}>>, ::spawn_inner>, futures_lite[d9f7489fbab33684]::future::CatchUnwind::spawn_exclusive_system_task::{closure#1}>>>::{closure#0}>, core[a3424aac13c77cf6]::result::Result<(), alloc[9fa3fef380221c31]::boxed::Box>, ::schedule::{closure#0}, ()>>::run::{closure#1}>, core[a3424aac13c77cf6]::task::poll::Poll>>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14
61: 0x55706989b7cd - ::spawn_exclusive_system_task::{closure#1}>>, ::spawn_inner>, futures_lite[d9f7489fbab33684]::future::CatchUnwind::spawn_exclusive_system_task::{closure#1}>>>::{closure#0}>, core[a3424aac13c77cf6]::result::Result<(), alloc[9fa3fef380221c31]::boxed::Box>, ::schedule::{closure#0}, ()>>::run
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-task-4.7.1/src/raw.rs:549:23
62: 0x557069924fd2 - ::run
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-task-4.7.1/src/runnable.rs:781:18
63: 0x55706990a9bb - ::tick::{closure#0}
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.2/src/lib.rs:733:18
64: 0x55706990a4b5 - ::tick::{closure#0}
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.2/src/lib.rs:324:29
65: 0x5570697c9dec - ::tick::{closure#0}
at /home/ssingh/Projects/bevy/crates/bevy_tasks/src/thread_executor.rs:105:39
66: 0x55706975e735 - ::execute_scope::<(), ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}::{closure#0}>::{closure#0}::{closure#0}::{closure#0}
at /home/ssingh/Projects/bevy/crates/bevy_tasks/src/task_pool.rs:542:45
67: 0x55706977f583 - ::execute_scope<(), ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}::{closure#0}>::{closure#0}::{closure#0}::{closure#0}> as core[a3424aac13c77cf6]::future::future::Future>::poll
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:299:9
68: 0x557069765b84 - ::execute_scope<(), ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}::{closure#0}>::{closure#0}::{closure#0}::{closure#0}>> as core[a3424aac13c77cf6]::future::future::Future>::poll::{closure#0}
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-lite-2.6.1/src/future.rs:653:53
69: 0x55706977f474 - ::execute_scope<(), ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}::{closure#0}>::{closure#0}::{closure#0}::{closure#0}>> as core[a3424aac13c77cf6]::future::future::Future>::poll::{closure#0}> as core[a3424aac13c77cf6]::ops::function::FnOnce<()>>::call_once
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9
70: 0x5570697c3647 - std[cbf56a46dac989f5]::panicking::catch_unwind::do_call::::execute_scope<(), ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}::{closure#0}>::{closure#0}::{closure#0}::{closure#0}>> as core[a3424aac13c77cf6]::future::future::Future>::poll::{closure#0}>, core[a3424aac13c77cf6]::task::poll::Poll>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40
71: 0x557069887a9b - __rust_try
72: 0x5570698877da - std[cbf56a46dac989f5]::panicking::catch_unwind::, core[a3424aac13c77cf6]::panic::unwind_safe::AssertUnwindSafe<::execute_scope<(), ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}::{closure#0}>::{closure#0}::{closure#0}::{closure#0}>> as core[a3424aac13c77cf6]::future::future::Future>::poll::{closure#0}>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19
73: 0x5570698877da - std[cbf56a46dac989f5]::panic::catch_unwind::::execute_scope<(), ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}::{closure#0}>::{closure#0}::{closure#0}::{closure#0}>> as core[a3424aac13c77cf6]::future::future::Future>::poll::{closure#0}>, core[a3424aac13c77cf6]::task::poll::Poll>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14
74: 0x5570697667ea - ::execute_scope<(), ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}::{closure#0}>::{closure#0}::{closure#0}::{closure#0}>> as core[a3424aac13c77cf6]::future::future::Future>::poll
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-lite-2.6.1/src/future.rs:653:9
75: 0x55706975d6e3 - ::execute_scope::<(), ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}::{closure#0}>::{closure#0}::{closure#0}
at /home/ssingh/Projects/bevy/crates/bevy_tasks/src/task_pool.rs:545:77
76: 0x557069766039 - ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}::{closure#0}, ::execute_scope<(), ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}::{closure#0}>::{closure#0}::{closure#0}> as core[a3424aac13c77cf6]::future::future::Future>::poll
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-lite-2.6.1/src/future.rs:454:46
77: 0x55706975c15d - ::execute_scope::<(), ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}::{closure#0}>::{closure#0}
at /home/ssingh/Projects/bevy/crates/bevy_tasks/src/task_pool.rs:548:41
78: 0x55706975d1e7 - ::scope_with_executor_inner::<::run::{closure#1}, ()>::{closure#0}
at /home/ssingh/Projects/bevy/crates/bevy_tasks/src/task_pool.rs:459:85
79: 0x55706976577a - futures_lite[d9f7489fbab33684]::future::block_on::, ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}>::{closure#0}
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-lite-2.6.1/src/future.rs:96:35
80: 0x5570698fe6e4 - >>::try_with::, ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}>::{closure#0}, alloc[9fa3fef380221c31]::vec::Vec<()>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:508:12
81: 0x5570698fe563 - >>::with::, ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}>::{closure#0}, alloc[9fa3fef380221c31]::vec::Vec<()>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:472:20
82: 0x5570697654dd - futures_lite[d9f7489fbab33684]::future::block_on::, ::scope_with_executor_inner<::run::{closure#1}, ()>::{closure#0}>
at /home/ssingh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-lite-2.6.1/src/future.rs:75:11
83: 0x55706975b709 - ::scope_with_executor_inner::<::run::{closure#1}, ()>
at /home/ssingh/Projects/bevy/crates/bevy_tasks/src/task_pool.rs:413:13
84: 0x55706975c349 - ::scope_with_executor::<::run::{closure#1}, ()>::{closure#0}
at /home/ssingh/Projects/bevy/crates/bevy_tasks/src/task_pool.rs:343:22
85: 0x5570698fe407 - >>::try_with::<::scope_with_executor<::run::{closure#1}, ()>::{closure#0}, alloc[9fa3fef380221c31]::vec::Vec<()>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:508:12
86: 0x5570698fe29d - >>::with::<::scope_with_executor<::run::{closure#1}, ()>::{closure#0}, alloc[9fa3fef380221c31]::vec::Vec<()>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:472:20
87: 0x55706975b42c - ::scope_with_executor::<::run::{closure#1}, ()>
at /home/ssingh/Projects/bevy/crates/bevy_tasks/src/task_pool.rs:339:31
88: 0x5570698d5d34 - ::run
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs:279:57
89: 0x55706980e792 - ::run
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/schedule/schedule.rs:540:14
90: 0x5570696aca61 - ::run_schedule::>::{closure#0}
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/world/mod.rs:3673:57
91: 0x5570696abc76 - ::try_schedule_scope::<(), bevy_ecs[ae55bfe1c74e77a3]::intern::Interned, ::run_schedule>::{closure#0}>
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/world/mod.rs:3591:21
92: 0x5570696abad9 - ::schedule_scope::<(), bevy_ecs[ae55bfe1c74e77a3]::intern::Interned, ::run_schedule>::{closure#0}>
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/world/mod.rs:3643:14
93: 0x5570696abab8 - ::run_schedule::>
at /home/ssingh/Projects/bevy/crates/bevy_ecs/src/world/mod.rs:3673:14
94: 0x5570696a8f93 - ::run_default_schedule
at /home/ssingh/Projects/bevy/crates/bevy_app/src/sub_app.rs:141:24
95: 0x5570696a91e4 - ::update
at /home/ssingh/Projects/bevy/crates/bevy_app/src/sub_app.rs:147:14
96: 0x55706707b40e - ::cleanup::{closure#0}
at /home/ssingh/Projects/bevy/crates/bevy_render/src/pipelined_rendering.rs:168:32
97: 0x55706744e516 - std[cbf56a46dac989f5]::sys::backtrace::__rust_begin_short_backtrace::<::cleanup::{closure#0}, ()>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:160:18
98: 0x5570670439c0 - ::spawn_unchecked_::<::cleanup::{closure#0}, ()>::{closure#1}::{closure#0}
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/mod.rs:562:17
99: 0x557066f925c1 - ::spawn_unchecked_<::cleanup::{closure#0}, ()>::{closure#1}::{closure#0}> as core[a3424aac13c77cf6]::ops::function::FnOnce<()>>::call_once
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/panic/unwind_safe.rs:274:9
100: 0x55706703e4db - std[cbf56a46dac989f5]::panicking::catch_unwind::do_call::::spawn_unchecked_<::cleanup::{closure#0}, ()>::{closure#1}::{closure#0}>, ()>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40
101: 0x557067043a4b - __rust_try
102: 0x5570670435ea - std[cbf56a46dac989f5]::panicking::catch_unwind::<(), core[a3424aac13c77cf6]::panic::unwind_safe::AssertUnwindSafe<::spawn_unchecked_<::cleanup::{closure#0}, ()>::{closure#1}::{closure#0}>>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19
103: 0x5570670435ea - std[cbf56a46dac989f5]::panic::catch_unwind::::spawn_unchecked_<::cleanup::{closure#0}, ()>::{closure#1}::{closure#0}>, ()>
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14
104: 0x5570670435ea - ::spawn_unchecked_::<::cleanup::{closure#0}, ()>::{closure#1}
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/mod.rs:560:30
105: 0x55706742811f - <::spawn_unchecked_<::cleanup::{closure#0}, ()>::{closure#1} as core[a3424aac13c77cf6]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
at /home/ssingh/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
106: 0x557069cd501f - > as core[a3424aac13c77cf6]::ops::function::FnOnce<()>>::call_once
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/alloc/src/boxed.rs:2206:9
107: 0x557069cd501f - ::new::thread_start
at /rustc/cc3eee7fbe17ea4b7238531cb97e1b7b8bd6afce/library/std/src/sys/thread/unix.rs:124:17
108: 0x7f75f1a9698b -
109: 0x7f75f1b1a9cc -
110: 0x0 -
Encountered a panic in system `bevy_render::renderer::render_system`!
```
## Solution
- Remove the `ViewTarget` when it becomes unavailable
## Testing
- Testing by running the bevy examples on linux
---------
Co-authored-by: IceSentry
---
crates/bevy_render/src/view/mod.rs | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs
index 9a3d610087c63..0226f22c770b8 100644
--- a/crates/bevy_render/src/view/mod.rs
+++ b/crates/bevy_render/src/view/mod.rs
@@ -1048,12 +1048,18 @@ pub fn prepare_view_targets(
) {
let mut textures = >::default();
for (entity, camera, view, texture_usage, msaa) in cameras.iter() {
- let (Some(target_size), Some(target)) = (camera.physical_target_size, &camera.target)
- else {
- continue;
- };
+ let (Some(target_size), Some(out_attachment)) = (
+ camera.physical_target_size,
+ camera
+ .target
+ .as_ref()
+ .and_then(|target| view_target_attachments.get(target)),
+ ) else {
+ // If we can't find an output attachment we need to remove the ViewTarget
+ // component to make sure the camera doesn't try rendering to an invalid
+ // output attachment.
+ commands.entity(entity).try_remove::();
- let Some(out_attachment) = view_target_attachments.get(target) else {
continue;
};
From f9d59f82066e86d4cecc16df807e10b33db21879 Mon Sep 17 00:00:00 2001
From: Daniel Skates
Date: Wed, 3 Dec 2025 01:03:54 +0800
Subject: [PATCH 006/133] Fix cargo features lint (#22008)
# Objective
`markdownlint` fails on `docs/cargo_features.md` as it doesn't like the
space before the pipe at the end of this line
## Solution
- Add a `.`
## Testing
- CI
---
Cargo.toml | 6 +++---
docs/cargo_features.md | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
index d039936d83779..aa3982b0c3a12 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -222,7 +222,7 @@ common_api = [
# COLLECTION: Features used to build 2D Bevy apps (does not include a render backend). You generally don't need to worry about this unless you are using a custom renderer.
2d_api = ["common_api", "bevy_sprite"]
-# COLLECTION: Bevy's built-in 2D renderer, built on top of `bevy_render`
+# COLLECTION: Bevy's built-in 2D renderer, built on top of `bevy_render`.
2d_bevy_render = [
"2d_api",
"bevy_render",
@@ -245,7 +245,7 @@ common_api = [
"zstd_rust",
]
-# COLLECTION: Bevy's built-in 3D renderer, built on top of `bevy_render`
+# COLLECTION: Bevy's built-in 3D renderer, built on top of `bevy_render`.
3d_bevy_render = [
"3d_api",
"bevy_render",
@@ -261,7 +261,7 @@ common_api = [
# COLLECTION: Features used to build UI Bevy apps (does not include a render backend). You generally don't need to worry about this unless you are using a custom renderer.
ui_api = ["default_app", "common_api", "bevy_ui"]
-# COLLECTION: Bevy's built-in UI renderer, built on top of `bevy_render`
+# COLLECTION: Bevy's built-in UI renderer, built on top of `bevy_render`.
ui_bevy_render = [
"ui_api",
"bevy_render",
diff --git a/docs/cargo_features.md b/docs/cargo_features.md
index e26c8b51c38af..5f7e33960539d 100644
--- a/docs/cargo_features.md
+++ b/docs/cargo_features.md
@@ -44,11 +44,11 @@ collections to build your own "profile" equivalent, without needing to manually
|default_platform|These are platform support features, such as OS support/features, windowing and input backends, etc.|
|common_api|Default scene definition features. Note that this does not include an actual renderer, such as bevy_render (Bevy's default render backend).|
|2d_api|Features used to build 2D Bevy apps (does not include a render backend). You generally don't need to worry about this unless you are using a custom renderer.|
-|2d_bevy_render|Bevy's built-in 2D renderer, built on top of `bevy_render` |
+|2d_bevy_render|Bevy's built-in 2D renderer, built on top of `bevy_render`.|
|3d_api|Features used to build 3D Bevy apps (does not include a render backend). You generally don't need to worry about this unless you are using a custom renderer.|
-|3d_bevy_render|Bevy's built-in 3D renderer, built on top of `bevy_render` |
+|3d_bevy_render|Bevy's built-in 3D renderer, built on top of `bevy_render`.|
|ui_api|Features used to build UI Bevy apps (does not include a render backend). You generally don't need to worry about this unless you are using a custom renderer.|
-|ui_bevy_render|Bevy's built-in UI renderer, built on top of `bevy_render` |
+|ui_bevy_render|Bevy's built-in UI renderer, built on top of `bevy_render`.|
|default_no_std|Recommended defaults for no_std applications|
### Feature List
From 243d5627f26b5e1392cca3a61f8c2138819cf869 Mon Sep 17 00:00:00 2001
From: ickshonpe
Date: Tue, 2 Dec 2025 17:04:24 +0000
Subject: [PATCH 007/133] remove unnecessary clone from `load_font_to_fontdb`
(#22009)
# Objective
`load_font_to_fontdb` clones the font handle needlessly.
## Solution
Just call id() on it.
---
crates/bevy_text/src/pipeline.rs | 36 +++++++++++++++-----------------
1 file changed, 17 insertions(+), 19 deletions(-)
diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs
index 0289b78dcdfa9..9fa918ba73ac0 100644
--- a/crates/bevy_text/src/pipeline.rs
+++ b/crates/bevy_text/src/pipeline.rs
@@ -620,25 +620,23 @@ pub fn load_font_to_fontdb(
map_handle_to_font_id: &mut HashMap, (cosmic_text::fontdb::ID, Arc)>,
fonts: &Assets,
) -> FontFaceInfo {
- let font_handle = text_font.font.clone();
- let (face_id, family_name) = map_handle_to_font_id
- .entry(font_handle.id())
- .or_insert_with(|| {
- let font = fonts.get(font_handle.id()).expect(
- "Tried getting a font that was not available, probably due to not being loaded yet",
- );
- let data = Arc::clone(&font.data);
- let ids = font_system
- .db_mut()
- .load_font_source(cosmic_text::fontdb::Source::Binary(data));
-
- // TODO: it is assumed this is the right font face
- let face_id = *ids.last().unwrap();
- let face = font_system.db().face(face_id).unwrap();
-
- let family_name = Arc::from(face.families[0].0.as_str());
- (face_id, family_name)
- });
+ let font_id = text_font.font.id();
+ let (face_id, family_name) = map_handle_to_font_id.entry(font_id).or_insert_with(|| {
+ let font = fonts.get(font_id).expect(
+ "Tried getting a font that was not available, probably due to not being loaded yet",
+ );
+ let data = Arc::clone(&font.data);
+ let ids = font_system
+ .db_mut()
+ .load_font_source(cosmic_text::fontdb::Source::Binary(data));
+
+ // TODO: it is assumed this is the right font face
+ let face_id = *ids.last().unwrap();
+ let face = font_system.db().face(face_id).unwrap();
+
+ let family_name = Arc::from(face.families[0].0.as_str());
+ (face_id, family_name)
+ });
let face = font_system.db().face(*face_id).unwrap();
From 71114e1ddece7e1be430205551c4804ff2b06160 Mon Sep 17 00:00:00 2001
From: IceSentry
Date: Tue, 2 Dec 2025 12:33:32 -0500
Subject: [PATCH 008/133] Add debug_group to render graph (#22003)
# Objective
- Debugging an application with multiple cameras can be really annoying
because it's hard to know at a glance which pass is associated with
which camera.
- wgpu exposes a feature to set debug_group that will add labels around
groups of passes and draw commands
## Solution
- Expose the debug_group feature to the render graph.
- Each sub graph can now specify a debug group
- Add a new debug group label to each camera with the camera order and
entity id
- I also added the new debug_group to the tracy span so you can more
easily see which span is associated with which camera
## Testing
- I tested the 3d_scene and split_screen examples
---
## Showcase
Here's what it looked like before
Here's what the split_screen example looks like inside nvidia nsight
with the new debug group labels. You can clearly see each camera clearly
identified.
---
.../src/render_graph/camera_driver_node.rs | 10 +++++++-
.../bevy_render/src/render_graph/context.rs | 3 +++
crates/bevy_render/src/render_graph/node.rs | 2 +-
.../bevy_render/src/renderer/graph_runner.rs | 23 ++++++++++++++++---
crates/bevy_ui_render/src/lib.rs | 2 +-
5 files changed, 34 insertions(+), 6 deletions(-)
diff --git a/crates/bevy_render/src/render_graph/camera_driver_node.rs b/crates/bevy_render/src/render_graph/camera_driver_node.rs
index 5a8dc581625ec..4ba4e93d33f0c 100644
--- a/crates/bevy_render/src/render_graph/camera_driver_node.rs
+++ b/crates/bevy_render/src/render_graph/camera_driver_node.rs
@@ -54,7 +54,15 @@ impl Node for CameraDriverNode {
}
}
if run_graph {
- graph.run_sub_graph(camera.render_graph, vec![], Some(sorted_camera.entity))?;
+ graph.run_sub_graph(
+ camera.render_graph,
+ vec![],
+ Some(sorted_camera.entity),
+ Some(format!(
+ "Camera {} ({})",
+ sorted_camera.order, sorted_camera.entity
+ )),
+ )?;
}
}
diff --git a/crates/bevy_render/src/render_graph/context.rs b/crates/bevy_render/src/render_graph/context.rs
index 4c6ecd30c1d67..b1f35edc1d1d8 100644
--- a/crates/bevy_render/src/render_graph/context.rs
+++ b/crates/bevy_render/src/render_graph/context.rs
@@ -14,6 +14,7 @@ pub struct RunSubGraph {
pub sub_graph: InternedRenderSubGraph,
pub inputs: Vec,
pub view_entity: Option,
+ pub debug_group: Option,
}
/// The context with all graph information required to run a [`Node`](super::Node).
@@ -185,6 +186,7 @@ impl<'a> RenderGraphContext<'a> {
name: impl RenderSubGraph,
inputs: Vec,
view_entity: Option,
+ debug_group: Option,
) -> Result<(), RunSubGraphError> {
let name = name.intern();
let sub_graph = self
@@ -219,6 +221,7 @@ impl<'a> RenderGraphContext<'a> {
sub_graph: name,
inputs,
view_entity,
+ debug_group,
});
Ok(())
diff --git a/crates/bevy_render/src/render_graph/node.rs b/crates/bevy_render/src/render_graph/node.rs
index 4355892487c00..c75218c0a2941 100644
--- a/crates/bevy_render/src/render_graph/node.rs
+++ b/crates/bevy_render/src/render_graph/node.rs
@@ -343,7 +343,7 @@ impl Node for RunGraphOnViewNode {
_render_context: &mut RenderContext,
_world: &World,
) -> Result<(), NodeRunError> {
- graph.run_sub_graph(self.sub_graph, vec![], Some(graph.view_entity()))?;
+ graph.run_sub_graph(self.sub_graph, vec![], Some(graph.view_entity()), None)?;
Ok(())
}
}
diff --git a/crates/bevy_render/src/renderer/graph_runner.rs b/crates/bevy_render/src/renderer/graph_runner.rs
index ff2e421982dc7..c1aa664a07bd4 100644
--- a/crates/bevy_render/src/renderer/graph_runner.rs
+++ b/crates/bevy_render/src/renderer/graph_runner.rs
@@ -76,7 +76,7 @@ impl RenderGraphRunner {
}
let mut render_context = RenderContext::new(render_device, diagnostics_recorder);
- Self::run_graph(graph, None, &mut render_context, world, &[], None)?;
+ Self::run_graph(graph, None, &mut render_context, world, &[], None, None)?;
finalizer(render_context.command_encoder());
let (render_device, mut diagnostics_recorder) = {
@@ -108,18 +108,30 @@ impl RenderGraphRunner {
world: &'w World,
inputs: &[SlotValue],
view_entity: Option,
+ debug_group: Option,
) -> Result<(), RenderGraphRunnerError> {
let mut node_outputs: HashMap> =
HashMap::default();
#[cfg(feature = "trace")]
- let span = if let Some(label) = &sub_graph {
- info_span!("run_graph", name = format!("{label:?}"))
+ let span = if let Some(render_label) = &sub_graph {
+ let name = format!("{render_label:?}");
+ if let Some(debug_group) = debug_group.as_ref() {
+ info_span!("run_graph", name = name, debug_group = debug_group)
+ } else {
+ info_span!("run_graph", name = name)
+ }
} else {
info_span!("run_graph", name = "main_graph")
};
#[cfg(feature = "trace")]
let _guard = span.enter();
+ if let Some(debug_group) = debug_group.as_ref() {
+ render_context
+ .command_encoder()
+ .push_debug_group(debug_group);
+ }
+
// Queue up nodes without inputs, which can be run immediately
let mut node_queue: VecDeque<&NodeState> = graph
.iter_nodes()
@@ -235,6 +247,7 @@ impl RenderGraphRunner {
world,
&run_sub_graph.inputs,
run_sub_graph.view_entity,
+ run_sub_graph.debug_group,
)?;
}
}
@@ -262,6 +275,10 @@ impl RenderGraphRunner {
}
}
+ if debug_group.is_some() {
+ render_context.command_encoder().pop_debug_group();
+ }
+
Ok(())
}
}
diff --git a/crates/bevy_ui_render/src/lib.rs b/crates/bevy_ui_render/src/lib.rs
index 4a292d86ba322..c385bca66d35d 100644
--- a/crates/bevy_ui_render/src/lib.rs
+++ b/crates/bevy_ui_render/src/lib.rs
@@ -426,7 +426,7 @@ impl RenderGraphNode for RunUiSubgraphOnUiViewNode {
};
// Run the subgraph on the UI view.
- graph.run_sub_graph(SubGraphUi, vec![], Some(ui_camera_view.0))?;
+ graph.run_sub_graph(SubGraphUi, vec![], Some(ui_camera_view.0), None)?;
Ok(())
}
}
From a06de1fb68f3c221b31514e738a00724f13d4d7e Mon Sep 17 00:00:00 2001
From: ickshonpe
Date: Tue, 2 Dec 2025 19:19:37 +0000
Subject: [PATCH 009/133] UI text module refactor (#21630)
# Objective
The `measure_text_system` and `text_system` systems don't do anything
except iterate a query and then delegate to two other functions,
`create_text_measure` and `queue_text`, respectively. This doesn't serve
any purpose and just makes these systems harder to understand.
## Solution
Flatten the module by moving the code from `create_text_measure` and
`queue_text` into the systems and remove those functions.
---
crates/bevy_ui/src/widget/text.rs | 249 ++++++++++++------------------
1 file changed, 97 insertions(+), 152 deletions(-)
diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs
index d604bc6977cd9..c84b463ddf3cb 100644
--- a/crates/bevy_ui/src/widget/text.rs
+++ b/crates/bevy_ui/src/widget/text.rs
@@ -12,7 +12,7 @@ use bevy_ecs::{
query::With,
reflect::ReflectComponent,
system::{Query, Res, ResMut},
- world::{Mut, Ref},
+ world::Ref,
};
use bevy_image::prelude::*;
use bevy_math::Vec2;
@@ -223,55 +223,6 @@ impl Measure for TextMeasure {
}
}
-#[inline]
-fn create_text_measure<'a>(
- entity: Entity,
- fonts: &Assets,
- scale_factor: f64,
- spans: impl Iterator,
- block: Ref,
- text_pipeline: &mut TextPipeline,
- mut content_size: Mut,
- mut text_flags: Mut,
- mut computed: Mut,
- font_system: &mut CosmicFontSystem,
-) {
- match text_pipeline.create_text_measure(
- entity,
- fonts,
- spans,
- scale_factor,
- &block,
- computed.as_mut(),
- font_system,
- ) {
- Ok(measure) => {
- if block.linebreak == LineBreak::NoWrap {
- content_size.set(NodeMeasure::Fixed(FixedMeasure { size: measure.max }));
- } else {
- content_size.set(NodeMeasure::Text(TextMeasure { info: measure }));
- }
-
- // Text measure func created successfully, so set `TextNodeFlags` to schedule a recompute
- text_flags.needs_measure_fn = false;
- text_flags.needs_recompute = true;
- }
- Err(TextError::NoSuchFont) => {
- // Try again next frame
- text_flags.needs_measure_fn = true;
- }
- Err(
- e @ (TextError::FailedToAddGlyph(_)
- | TextError::FailedToGetGlyphImage(_)
- | TextError::MissingAtlasLayout
- | TextError::MissingAtlasTexture
- | TextError::InconsistentAtlasState),
- ) => {
- panic!("Fatal error when processing text: {e}.");
- }
- };
-}
-
/// Generates a new [`Measure`] for a text node on changes to its [`Text`] component.
///
/// A `Measure` is used by the UI's layout algorithm to determine the appropriate amount of space
@@ -300,98 +251,61 @@ pub fn measure_text_system(
mut text_pipeline: ResMut,
mut font_system: ResMut,
) {
- for (entity, block, content_size, text_flags, computed, computed_target, computed_node) in
- &mut text_query
+ for (
+ entity,
+ block,
+ mut content_size,
+ mut text_flags,
+ mut computed,
+ computed_target,
+ computed_node,
+ ) in &mut text_query
{
// Note: the ComputedTextBlock::needs_rerender bool is cleared in create_text_measure().
// 1e-5 epsilon to ignore tiny scale factor float errors
- if 1e-5
+ if !(1e-5
< (computed_target.scale_factor() - computed_node.inverse_scale_factor.recip()).abs()
|| computed.needs_rerender()
|| text_flags.needs_measure_fn
- || content_size.is_added()
+ || content_size.is_added())
{
- create_text_measure(
- entity,
- &fonts,
- computed_target.scale_factor.into(),
- text_reader.iter(entity),
- block,
- &mut text_pipeline,
- content_size,
- text_flags,
- computed,
- &mut font_system,
- );
+ continue;
}
- }
-}
-
-#[inline]
-fn queue_text(
- entity: Entity,
- fonts: &Assets,
- text_pipeline: &mut TextPipeline,
- font_atlas_set: &mut FontAtlasSet,
- texture_atlases: &mut Assets,
- textures: &mut Assets,
- scale_factor: f32,
- inverse_scale_factor: f32,
- block: &TextLayout,
- node: Ref,
- mut text_flags: Mut,
- text_layout_info: Mut,
- computed: &mut ComputedTextBlock,
- text_reader: &mut TextUiReader,
- font_system: &mut CosmicFontSystem,
- swash_cache: &mut SwashCache,
-) {
- // Skip the text node if it is waiting for a new measure func
- if text_flags.needs_measure_fn {
- return;
- }
- let physical_node_size = if block.linebreak == LineBreak::NoWrap {
- // With `NoWrap` set, no constraints are placed on the width of the text.
- TextBounds::UNBOUNDED
- } else {
- // `scale_factor` is already multiplied by `UiScale`
- TextBounds::new(node.unrounded_size.x, node.unrounded_size.y)
- };
+ match text_pipeline.create_text_measure(
+ entity,
+ fonts.as_ref(),
+ text_reader.iter(entity),
+ computed_target.scale_factor.into(),
+ &block,
+ computed.as_mut(),
+ &mut font_system,
+ ) {
+ Ok(measure) => {
+ if block.linebreak == LineBreak::NoWrap {
+ content_size.set(NodeMeasure::Fixed(FixedMeasure { size: measure.max }));
+ } else {
+ content_size.set(NodeMeasure::Text(TextMeasure { info: measure }));
+ }
- let text_layout_info = text_layout_info.into_inner();
- match text_pipeline.queue_text(
- text_layout_info,
- fonts,
- text_reader.iter(entity),
- scale_factor.into(),
- block,
- physical_node_size,
- font_atlas_set,
- texture_atlases,
- textures,
- computed,
- font_system,
- swash_cache,
- ) {
- Err(TextError::NoSuchFont) => {
- // There was an error processing the text layout, try again next frame
- text_flags.needs_recompute = true;
- }
- Err(
- e @ (TextError::FailedToAddGlyph(_)
- | TextError::FailedToGetGlyphImage(_)
- | TextError::MissingAtlasLayout
- | TextError::MissingAtlasTexture
- | TextError::InconsistentAtlasState),
- ) => {
- panic!("Fatal error when processing text: {e}.");
- }
- Ok(()) => {
- text_layout_info.scale_factor = scale_factor;
- text_layout_info.size *= inverse_scale_factor;
- text_flags.needs_recompute = false;
- }
+ // Text measure func created successfully, so set `TextNodeFlags` to schedule a recompute
+ text_flags.needs_measure_fn = false;
+ text_flags.needs_recompute = true;
+ }
+ Err(TextError::NoSuchFont) => {
+ // Try again next frame
+ text_flags.needs_measure_fn = true;
+ }
+ Err(
+ e @ (TextError::FailedToAddGlyph(_)
+ | TextError::FailedToGetGlyphImage(_)
+ | TextError::MissingAtlasLayout
+ | TextError::MissingAtlasTexture
+ | TextError::InconsistentAtlasState),
+ ) => {
+ panic!("Fatal error when processing text: {e}.");
+ }
+ };
}
}
@@ -421,26 +335,57 @@ pub fn text_system(
mut font_system: ResMut,
mut swash_cache: ResMut,
) {
- for (entity, node, block, text_layout_info, text_flags, mut computed) in &mut text_query {
- if node.is_changed() || text_flags.needs_recompute {
- queue_text(
- entity,
- &fonts,
- &mut text_pipeline,
- &mut font_atlas_set,
- &mut texture_atlases,
- &mut textures,
- node.inverse_scale_factor.recip(),
- node.inverse_scale_factor,
- block,
- node,
- text_flags,
- text_layout_info,
- computed.as_mut(),
- &mut text_reader,
- &mut font_system,
- &mut swash_cache,
- );
+ for (entity, node, block, text_layout_info, mut text_flags, mut computed) in &mut text_query {
+ // Skip the text node if it is waiting for a new measure func
+ if text_flags.needs_measure_fn {
+ continue;
+ }
+
+ if !(node.is_changed() || text_flags.needs_recompute) {
+ continue;
+ }
+
+ let physical_node_size = if block.linebreak == LineBreak::NoWrap {
+ // With `NoWrap` set, no constraints are placed on the width of the text.
+ TextBounds::UNBOUNDED
+ } else {
+ // `scale_factor` is already multiplied by `UiScale`
+ TextBounds::new(node.unrounded_size.x, node.unrounded_size.y)
+ };
+
+ let text_layout_info = text_layout_info.into_inner();
+ match text_pipeline.queue_text(
+ text_layout_info,
+ &fonts,
+ text_reader.iter(entity),
+ node.inverse_scale_factor.recip() as f64,
+ block,
+ physical_node_size,
+ &mut font_atlas_set,
+ &mut texture_atlases,
+ &mut textures,
+ &mut computed,
+ &mut font_system,
+ &mut swash_cache,
+ ) {
+ Err(TextError::NoSuchFont) => {
+ // There was an error processing the text layout, try again next frame
+ text_flags.needs_recompute = true;
+ }
+ Err(
+ e @ (TextError::FailedToAddGlyph(_)
+ | TextError::FailedToGetGlyphImage(_)
+ | TextError::MissingAtlasLayout
+ | TextError::MissingAtlasTexture
+ | TextError::InconsistentAtlasState),
+ ) => {
+ panic!("Fatal error when processing text: {e}.");
+ }
+ Ok(()) => {
+ text_layout_info.scale_factor = node.inverse_scale_factor.recip();
+ text_layout_info.size *= node.inverse_scale_factor;
+ text_flags.needs_recompute = false;
+ }
}
}
}
From 8c78e46987aaa0036219ebc996ba0f1ad13dbe57 Mon Sep 17 00:00:00 2001
From: HeartofPhos <43216176+HeartofPhos@users.noreply.github.com>
Date: Wed, 3 Dec 2025 02:35:59 +0200
Subject: [PATCH 010/133] #21980 Fix Component/Relationship derive using non
fully qualified path (#21981)
# Objective
Fixes #21980
## Solution
Use fully qualified path for `Entity`
## Testing
The following code now compiles
```rust
mod test {
fn derive_component_relationship_hygiene() {
#[derive(Debug, bevy::prelude::Component)]
#[relationship(relationship_target = RelTarget)]
struct Rel(pub bevy::prelude::Entity);
#[derive(Debug, bevy::prelude::Component)]
#[relationship_target(relationship = Rel)]
struct RelTarget(bevy::prelude::Entity);
}
}```
Co-authored-by: Heart <>
---
crates/bevy_ecs/macros/src/component.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs
index 5ec637da1a4f7..f37defab0b111 100644
--- a/crates/bevy_ecs/macros/src/component.rs
+++ b/crates/bevy_ecs/macros/src/component.rs
@@ -826,7 +826,7 @@ fn derive_relationship(
}
#[inline]
- fn set_risky(&mut self, entity: Entity) {
+ fn set_risky(&mut self, entity: #bevy_ecs_path::entity::Entity) {
self.#relationship_member = entity;
}
}
From 8978b479912e3bba0aabd8939f7a4339b14157b9 Mon Sep 17 00:00:00 2001
From: ickshonpe
Date: Wed, 3 Dec 2025 01:04:12 +0000
Subject: [PATCH 011/133] Fix dragging-over panic (#21999)
# Objective
Problems:
* If a drag starts over a hovered entity, `picking_events` doesn't add
the hovered entity to the `dragging_over` map in `PointerButtonState`.
This causes a panic when the pointer is moved and `picking_events` tries
to update the non-existant `dragging_over` state for the hovered entity.
* `DragEnter` isn't dispatched on `DragStart`. This doesn't seem ideal.
The drag isn't crossing a geometric boundary, but it is causing the
dragged over entities to enter into a dragged over state.
Fixes #21998
## Solution
* Update the `dragging_state` for all hovered entities every frame,
before handling any `PointerInput`s.
* Reworked the `DragEnter` dispatch logic. `DragEnter` now also fires
when a drag starts over an already hovered entity.
## Testing
The examples using drag events should no longer panic:
```
cargo run --example standard_widgets --features="experimental_bevy_ui_widgets"
```
```
cargo run --example ui_drag_and_drop
````
---------
Co-authored-by: Kevin Chen
---
crates/bevy_picking/src/events.rs | 75 +++++++++++++------
.../dragenter-now-fires-on-drag-starts.md | 6 ++
2 files changed, 59 insertions(+), 22 deletions(-)
create mode 100644 release-content/migration-guides/dragenter-now-fires-on-drag-starts.md
diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs
index 4fb10121efde8..1385961e468d5 100644
--- a/crates/bevy_picking/src/events.rs
+++ b/crates/bevy_picking/src/events.rs
@@ -255,7 +255,7 @@ pub struct DragEnd {
pub distance: Vec2,
}
-/// Fires when a pointer dragging the `dragged` entity enters the [target entity](EntityEvent::event_target).
+/// Fires when a pointer dragging the `dragged` entity enters the [target entity](EntityEvent::event_target)
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct DragEnter {
@@ -535,31 +535,33 @@ pub fn pointer_events(
}
}
- // If the entity is hovered...
+ // Iterate all currently hovered entities for each pointer
for (pointer_id, hovered_entity, hit) in hover_map
.iter()
.flat_map(|(id, hashmap)| hashmap.iter().map(|data| (*id, *data.0, data.1.clone())))
{
- // ...but was not hovered last frame...
- if !previous_hover_map
- .get(&pointer_id)
- .iter()
- .any(|e| e.contains_key(&hovered_entity))
- {
- let Some(location) = pointer_location(pointer_id) else {
- debug!(
- "Unable to get location for pointer {:?} during pointer over",
- pointer_id
- );
- continue;
- };
-
- // Possibly send DragEnter events
- for button in PointerButton::iter() {
- let state = pointer_state.get_mut(pointer_id, button);
+ // Continue if the pointer does not have a valid location.
+ let Some(location) = pointer_location(pointer_id) else {
+ debug!(
+ "Unable to get location for pointer {:?} during pointer over",
+ pointer_id
+ );
+ continue;
+ };
+ // For each button update its `dragging_over` state and possibly emit DragEnter events.
+ for button in PointerButton::iter() {
+ let state = pointer_state.get_mut(pointer_id, button);
+
+ // Only update the `dragging_over` state if there is at least one entity being dragged.
+ // Only emit DragEnter events for this `hovered_entity`, if it had no previous `dragging_over` state.
+ if !state.dragging.is_empty()
+ && state
+ .dragging_over
+ .insert(hovered_entity, hit.clone())
+ .is_none()
+ {
for drag_target in state.dragging.keys() {
- state.dragging_over.insert(hovered_entity, hit.clone());
let drag_enter_event = Pointer::new(
pointer_id,
location.clone(),
@@ -574,8 +576,14 @@ pub fn pointer_events(
message_writers.drag_enter_events.write(drag_enter_event);
}
}
+ }
- // Always send Over events
+ // Emit an Over event if the `hovered_entity` was not hovered by the same pointer the previous frame.
+ if !previous_hover_map
+ .get(&pointer_id)
+ .iter()
+ .any(|e| e.contains_key(&hovered_entity))
+ {
let over_event = Pointer::new(
pointer_id,
location.clone(),
@@ -738,8 +746,32 @@ pub fn pointer_events(
},
*press_target,
);
+
commands.trigger(drag_start_event.clone());
message_writers.drag_start_events.write(drag_start_event);
+
+ // Insert dragging over state and emit DragEnter for hovered entities.
+ for (hovered_entity, hit) in hover_map
+ .get(&pointer_id)
+ .iter()
+ .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
+ .filter(|(hovered_entity, _)| *hovered_entity != *press_target)
+ {
+ // Inserting the `dragging_over` state here ensures the `DragEnter` event won't be dispatched twice.
+ state.dragging_over.insert(hovered_entity, hit.clone());
+ let drag_enter_event = Pointer::new(
+ pointer_id,
+ location.clone(),
+ DragEnter {
+ button,
+ dragged: *press_target,
+ hit: hit.clone(),
+ },
+ hovered_entity,
+ );
+ commands.trigger(drag_enter_event.clone());
+ message_writers.drag_enter_events.write(drag_enter_event);
+ }
}
// Emit Drag events to the entities we are dragging
@@ -771,7 +803,6 @@ pub fn pointer_events(
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
.filter(|(hovered_entity, _)| *hovered_entity != *drag_target)
{
- *state.dragging_over.get_mut(&hovered_entity).unwrap() = hit.clone();
let drag_over_event = Pointer::new(
pointer_id,
location.clone(),
diff --git a/release-content/migration-guides/dragenter-now-fires-on-drag-starts.md b/release-content/migration-guides/dragenter-now-fires-on-drag-starts.md
new file mode 100644
index 0000000000000..187067a05cb82
--- /dev/null
+++ b/release-content/migration-guides/dragenter-now-fires-on-drag-starts.md
@@ -0,0 +1,6 @@
+---
+title: "DragEnter now fires on drag starts"
+pull_requests: [21999]
+---
+
+`DragEnter` now also fires when a drag starts over an already hovered entity.
From aa14ada6d45225b0123b4799e7baf2722edabc6d Mon Sep 17 00:00:00 2001
From: Nico Zweifel <34443492+NicoZweifel@users.noreply.github.com>
Date: Wed, 3 Dec 2025 08:00:48 +0100
Subject: [PATCH 012/133] Fix extended_material_bindless example wgpu
validation error on MacOS/Metal (#21729)
# Objective
- Fixes https://github.com/bevyengine/bevy/issues/21719
## Solution
- Implementing `bindless_supported` of the `AsBindGroup` trait for
`ExtendedMaterial`:
```rust
fn bindless_supported(render_device: &RenderDevice) -> bool {
B::bindless_supported(render_device) && E::bindless_supported(render_device)
}
```
Before it just returned the default (true), leading to the mismatch.
I made small, mostly stylistic, improvements to `fn
bind_group_layout_entries` and a few other locations like a comment in
`extended_material` during debugging but I could move that out into a
separate PR as well. Since the changes are rather limited and related to
the issue I decided to leave it in for now.
## Testing
- Tested on Windows/NixOS and MacOS, tested multiple examples but most
testing happened on the `extended_material_bindless` and
`extended_material` examples.
---
crates/bevy_pbr/src/extended_material.rs | 31 ++++++++++++++----------
1 file changed, 18 insertions(+), 13 deletions(-)
diff --git a/crates/bevy_pbr/src/extended_material.rs b/crates/bevy_pbr/src/extended_material.rs
index 258408cf4cc62..535128d00ec1e 100644
--- a/crates/bevy_pbr/src/extended_material.rs
+++ b/crates/bevy_pbr/src/extended_material.rs
@@ -196,6 +196,10 @@ impl AsBindGroup for ExtendedMaterial {
}
}
+ fn bindless_supported(render_device: &RenderDevice) -> bool {
+ B::bindless_supported(render_device) && E::bindless_supported(render_device)
+ }
+
fn label() -> &'static str {
E::label()
}
@@ -216,7 +220,7 @@ impl AsBindGroup for ExtendedMaterial {
) -> Result {
force_non_bindless = force_non_bindless || Self::bindless_slot_count().is_none();
- // add together the bindings of the base material and the user material
+ // add together the bindings of the base material and the extension
let UnpreparedBindGroup { mut bindings } = B::unprepared_bind_group(
&self.base,
layout,
@@ -224,7 +228,9 @@ impl AsBindGroup for ExtendedMaterial {
base_param,
force_non_bindless,
)?;
- let extended_bindgroup = E::unprepared_bind_group(
+ let UnpreparedBindGroup {
+ bindings: extension_bindings,
+ } = E::unprepared_bind_group(
&self.extension,
layout,
render_device,
@@ -232,7 +238,7 @@ impl AsBindGroup for ExtendedMaterial {
force_non_bindless,
)?;
- bindings.extend(extended_bindgroup.bindings.0);
+ bindings.extend(extension_bindings.0);
Ok(UnpreparedBindGroup { bindings })
}
@@ -251,17 +257,16 @@ impl AsBindGroup for ExtendedMaterial {
// when bindless mode is on, because of the common bindless resource
// arrays, and we need to eliminate the duplicates or `wgpu` will
// complain.
- let mut entries = vec![];
- let mut seen_bindings = HashSet::<_>::with_hasher(FixedHasher);
- for entry in B::bind_group_layout_entries(render_device, force_non_bindless)
+ let base_entries = B::bind_group_layout_entries(render_device, force_non_bindless);
+ let extension_entries = E::bind_group_layout_entries(render_device, force_non_bindless);
+
+ let mut seen_bindings = HashSet::::with_hasher(FixedHasher);
+
+ base_entries
.into_iter()
- .chain(E::bind_group_layout_entries(render_device, force_non_bindless).into_iter())
- {
- if seen_bindings.insert(entry.binding) {
- entries.push(entry);
- }
- }
- entries
+ .chain(extension_entries)
+ .filter(|entry| seen_bindings.insert(entry.binding))
+ .collect()
}
fn bindless_descriptor() -> Option {
From 41f59f1891067799eac93796b3e1ffb9775002b5 Mon Sep 17 00:00:00 2001
From: Carter Anderson
Date: Wed, 3 Dec 2025 12:16:24 -0800
Subject: [PATCH 013/133] TypeData dependencies (#22016)
# Objective
The proposed ["Resources as
entities"](https://github.com/bevyengine/bevy/pull/20934 ) PR makes
Resource implement Component. This creates a situation where we need to
do:
```rust
#[derive(Resource, Reflect)]
#[reflect(Resource, Component)]
struct Thing;
```
I'm of the mind that we should add the ability for TypeData to have
"dependencies". Ex `reflect(Resource)` implies `reflect(Component)`. For
"subtrait" types, I think supporting this is logical / users would
appreciate it.
## Solution
1. Add a new `FromType::insert_dependencies` function with a default
impl (which makes this a non-breaking change). This does kind of
overload the `FromType` trait (ex: a name like `GetTypeData` might be
better with this new context), but this is a pretty niche trait / piece
of functionality, and I like the idea of not breaking people.
2. Add a new `TypeRegistration::register_type_data` function,
which initializes the TypeData `T` for a given type `V` , inserts that
type data, and also inserts any dependent type data using
`insert_dependencies`.
3. Adjust the `Reflect` macro to use `register_type_data` instead of
`insert(FromType::::from_type())`
This makes it possible to do the following:
```rust
impl FromType for ReflectResource {
fn from_type() -> Self {
ReflectResource
}
fn insert_dependencies(type_registration: &mut TypeRegistration) {
type_registration.register_type_data::();
}
}
```
Which then allows dropping `reflect(Component)`:
```rust
#[derive(Resource, Reflect)]
#[reflect(Resource)]
struct Thing;
```
## Testing
I added a unit test :stuck_out_tongue_winking_eye:
---
.../bevy_reflect/derive/src/registration.rs | 2 +-
crates/bevy_reflect/src/lib.rs | 30 +++++++++++++++++++
crates/bevy_reflect/src/type_registry.rs | 11 +++++++
3 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/crates/bevy_reflect/derive/src/registration.rs b/crates/bevy_reflect/derive/src/registration.rs
index 2d8174cfb6095..d3ec6e0ad68a7 100644
--- a/crates/bevy_reflect/derive/src/registration.rs
+++ b/crates/bevy_reflect/derive/src/registration.rs
@@ -49,7 +49,7 @@ pub(crate) fn impl_get_type_registration<'a>(
registration.insert::<#bevy_reflect_path::ReflectFromPtr>(#bevy_reflect_path::FromType::::from_type());
#from_reflect_data
#serialization_data
- #(registration.insert::<#registration_data>(#bevy_reflect_path::FromType::::from_type());)*
+ #(registration.register_type_data::<#registration_data, Self>();)*
registration
}
diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs
index 72397bfe39674..7ead2e662e3f9 100644
--- a/crates/bevy_reflect/src/lib.rs
+++ b/crates/bevy_reflect/src/lib.rs
@@ -3606,6 +3606,36 @@ bevy_reflect::tests::Test {
assert!(registry.contains(TypeId::of::()));
assert!(registry.contains(TypeId::of::()));
}
+
+ #[test]
+ fn type_data_dependency() {
+ #[derive(Reflect)]
+ #[reflect(A)]
+ struct X;
+
+ #[derive(Clone)]
+ struct ReflectA;
+
+ impl FromType for ReflectA {
+ fn from_type() -> Self {
+ ReflectA
+ }
+
+ fn insert_dependencies(type_registration: &mut TypeRegistration) {
+ type_registration.insert(ReflectB);
+ }
+ }
+
+ #[derive(Clone)]
+ struct ReflectB;
+
+ let mut registry = TypeRegistry::new();
+ registry.register::();
+
+ let registration = registry.get(TypeId::of::()).unwrap();
+ assert!(registration.data::().is_some());
+ assert!(registration.data::().is_some());
+ }
}
#[cfg(feature = "glam")]
diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs
index 5c79a7bd77840..bf1950b7af9cb 100644
--- a/crates/bevy_reflect/src/type_registry.rs
+++ b/crates/bevy_reflect/src/type_registry.rs
@@ -588,6 +588,13 @@ impl TypeRegistration {
self.data.insert(TypeId::of::(), Box::new(data));
}
+ /// Inserts the [`TypeData`] instance of `T` created for `V`, and inserts any
+ /// [`TypeData`] dependencies for that combination of `T` and `V`.
+ pub fn register_type_data, V>(&mut self) {
+ self.insert(T::from_type());
+ T::insert_dependencies(self);
+ }
+
/// Returns a reference to the value of type `T` in this registration's
/// [type data].
///
@@ -747,6 +754,10 @@ where
pub trait FromType {
/// Creates an instance of `Self` for type `T`.
fn from_type() -> Self;
+ /// Inserts [`TypeData`] dependencies of this [`TypeData`].
+ /// This is especially useful for trait [`TypeData`] that has a supertrait (ex: `A: B`).
+ /// When the [`TypeData`] for `A` is inserted, the `B` [`TypeData`] will also be inserted.
+ fn insert_dependencies(_type_registration: &mut TypeRegistration) {}
}
/// A struct used to serialize reflected instances of a type.
From 540d5853dae31006b3e2bef057c3c6299d1dba6b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?=
Date: Wed, 3 Dec 2025 14:29:39 -0800
Subject: [PATCH 014/133] Add Clone derive to ViewTarget (#22024)
For esoteric reasons we need to send this to the MainWorld for
processing. All the fields are GPU resources or Arc'd so it can be
trivially cloned.
---
crates/bevy_render/src/view/mod.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs
index 0226f22c770b8..040d0ce7bc3cc 100644
--- a/crates/bevy_render/src/view/mod.rs
+++ b/crates/bevy_render/src/view/mod.rs
@@ -601,7 +601,7 @@ pub struct ViewUniformOffset {
pub offset: u32,
}
-#[derive(Component)]
+#[derive(Component, Clone)]
pub struct ViewTarget {
main_textures: MainTargetTextures,
main_texture_format: TextureFormat,
From 1720e5b71b29a8b508ec0ab9f297a42c7ed16de2 Mon Sep 17 00:00:00 2001
From: ickshonpe
Date: Thu, 4 Dec 2025 02:35:26 +0000
Subject: [PATCH 015/133] Ignore text buffer metrics (#22022)
# Objective
Since we set explicit font size and line height attributes for every
text section, the `Metrics` values passed to the text buffer don't
matter. We can just initialize the buffer with some dummy value and then
ignore it.
## Solution
* Initialize the text buffer to some dummy values.
* Remove the code that collects and sets the metrics during text
updates.
---
crates/bevy_text/src/pipeline.rs | 15 +--------------
crates/bevy_text/src/text.rs | 2 +-
2 files changed, 2 insertions(+), 15 deletions(-)
diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs
index 9fa918ba73ac0..05f916f086462 100644
--- a/crates/bevy_text/src/pipeline.rs
+++ b/crates/bevy_text/src/pipeline.rs
@@ -105,8 +105,6 @@ impl TextPipeline {
// Collect span information into a vec. This is necessary because font loading requires mut access
// to FontSystem, which the cosmic-text Buffer also needs.
- let mut max_font_size: f32 = 0.;
- let mut max_line_height: f32 = 0.0;
let mut spans: Vec<(usize, &str, &TextFont, FontFaceInfo, Color, LineHeight)> =
core::mem::take(&mut self.spans_buffer)
.into_iter()
@@ -147,10 +145,6 @@ impl TextPipeline {
return Err(TextError::NoSuchFont);
}
- // Get max font size for use in cosmic Metrics.
- max_font_size = max_font_size.max(text_font.font_size);
- max_line_height = max_line_height.max(line_height.eval(text_font.font_size));
-
// Load Bevy fonts into cosmic-text's font system.
let face_info = load_font_to_fontdb(
text_font,
@@ -170,13 +164,6 @@ impl TextPipeline {
spans.push((span_index, span, text_font, face_info, color, line_height));
}
- let mut metrics = Metrics::new(max_font_size, max_line_height).scale(scale_factor as f32);
- // Metrics of 0.0 cause `Buffer::set_metrics` to panic. We hack around this by 'falling
- // through' to call `Buffer::set_rich_text` with zero spans so any cached text will be cleared without
- // deallocating the buffer.
- metrics.font_size = metrics.font_size.max(0.000001);
- metrics.line_height = metrics.line_height.max(0.000001);
-
// Map text sections to cosmic-text spans, and ignore sections with negative or zero fontsizes,
// since they cannot be rendered by cosmic-text.
//
@@ -201,7 +188,7 @@ impl TextPipeline {
// Update the buffer.
let buffer = &mut computed.buffer;
- buffer.set_metrics_and_size(font_system, metrics, bounds.width, bounds.height);
+ buffer.set_size(font_system, bounds.width, bounds.height);
buffer.set_wrap(
font_system,
diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs
index f100eca0c786b..6b022487bca6e 100644
--- a/crates/bevy_text/src/text.rs
+++ b/crates/bevy_text/src/text.rs
@@ -18,7 +18,7 @@ pub struct CosmicBuffer(pub Buffer);
impl Default for CosmicBuffer {
fn default() -> Self {
- Self(Buffer::new_empty(Metrics::new(0.0, 0.000001)))
+ Self(Buffer::new_empty(Metrics::new(20.0, 20.0)))
}
}
From 6cbc4b242096e9ef6cf6fc3bd07a3e06d9b916a6 Mon Sep 17 00:00:00 2001
From: ickshonpe
Date: Thu, 4 Dec 2025 02:38:40 +0000
Subject: [PATCH 016/133] Remove redundant `shape_until_scroll` (#22020)
# Objective
In `TextPipeline::update_buffer` the `shape_until_scroll` call is
redundant.
The preceding line that calls `set_rich_text`, calls
`shape_until_scroll` itself before returning.
## Solution
Remove the `shape_until_scroll` call.
## Testing
The `testbed_ui` screenshot CI will fail with no text rendered if this
is incorrect.
---
crates/bevy_text/src/pipeline.rs | 2 --
1 file changed, 2 deletions(-)
diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs
index 05f916f086462..5d3fe166d1f82 100644
--- a/crates/bevy_text/src/pipeline.rs
+++ b/crates/bevy_text/src/pipeline.rs
@@ -208,8 +208,6 @@ impl TextPipeline {
Some(justify.into()),
);
- buffer.shape_until_scroll(font_system, false);
-
// Workaround for alignment not working for unbounded text.
// See https://github.com/pop-os/cosmic-text/issues/343
if bounds.width.is_none() && justify != Justify::Left {
From e8520c1faf5f0a1e164947bcfebf275f1c7e7942 Mon Sep 17 00:00:00 2001
From: Sigma-dev <72008323+Sigma-dev@users.noreply.github.com>
Date: Thu, 4 Dec 2025 07:19:30 +0100
Subject: [PATCH 017/133] Added required features data to docs export (#21975)
# Objective
This PR is a sister PR necessary for the website to have
`required_features` data.
You will find more information on the motivations there:
https://github.com/bevyengine/bevy-website/pull/2313
## Solution
- Add the required_features data to the examples docs export
## Testing
I tested both together and it worked fine
---
tools/example-showcase/src/main.rs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tools/example-showcase/src/main.rs b/tools/example-showcase/src/main.rs
index a437533674fe1..dbc60564623bf 100644
--- a/tools/example-showcase/src/main.rs
+++ b/tools/example-showcase/src/main.rs
@@ -596,6 +596,7 @@ code_path = \"content/examples{}/{}\"
shader_code_paths = {:?}
github_code_path = \"{}\"
header_message = \"Examples ({})\"
+required_features = {:?}
+++
{}
@@ -637,6 +638,7 @@ header_message = \"Examples ({})\"
WebApi::Webgpu => "WebGPU",
WebApi::Webgl2 => "WebGL2",
},
+ to_show.required_features,
docblock,
)
.as_bytes(),
From 6e88ae2db28e5b365c7309f00efb871f210758cb Mon Sep 17 00:00:00 2001
From: Carter Anderson
Date: Thu, 4 Dec 2025 18:31:59 -0800
Subject: [PATCH 018/133] Inline register_type_data to cut down on binary size
(#22030)
# Objective
My [TypeData dependencies](#22016 ) PR regressed native binary size by
~8mb.
(thank you [twitcher](https://github.com/bevyengine/twitcher/))
## Solution
- Inline `register_type_data`, which restores the original binary size:
- `release breakout` before #22016: 127.3mb
- `release breakout` after #22016: 136.4mb
- `release breakout` after this PR: 127.4mb
---
crates/bevy_reflect/src/type_registry.rs | 1 +
1 file changed, 1 insertion(+)
diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs
index bf1950b7af9cb..8ccd81ce710f7 100644
--- a/crates/bevy_reflect/src/type_registry.rs
+++ b/crates/bevy_reflect/src/type_registry.rs
@@ -590,6 +590,7 @@ impl TypeRegistration {
/// Inserts the [`TypeData`] instance of `T` created for `V`, and inserts any
/// [`TypeData`] dependencies for that combination of `T` and `V`.
+ #[inline]
pub fn register_type_data, V>(&mut self) {
self.insert(T::from_type());
T::insert_dependencies(self);
From 700ee46292c68dfb8251e43690bbce56fe12f79b Mon Sep 17 00:00:00 2001
From: Mike
Date: Fri, 5 Dec 2025 00:01:33 -0800
Subject: [PATCH 019/133] Fix some lints from rust 1.89 bump (#22034)
# Objective
- In #21822 the MSRV bump triggers some new lint warnings, so this pr
fixes them.
## Solution
- Bumped the msrv in bevy_ecs and fixed the lints. But this pr does not
actually bump the msrv. These fixes are all for combining nested if's
together.
---
crates/bevy_ecs/src/component/clone.rs | 20 +++++-----
crates/bevy_ecs/src/entity/clone_entities.rs | 10 ++---
crates/bevy_ecs/src/observer/mod.rs | 24 ++++++------
crates/bevy_ecs/src/query/fetch.rs | 8 ++--
crates/bevy_ecs/src/relationship/mod.rs | 41 ++++++++++----------
crates/bevy_ecs/src/system/system.rs | 8 ++--
6 files changed, 54 insertions(+), 57 deletions(-)
diff --git a/crates/bevy_ecs/src/component/clone.rs b/crates/bevy_ecs/src/component/clone.rs
index 3a3784350e36a..a645463923bb6 100644
--- a/crates/bevy_ecs/src/component/clone.rs
+++ b/crates/bevy_ecs/src/component/clone.rs
@@ -110,20 +110,18 @@ pub fn component_clone_via_reflect(source: &SourceComponent, ctx: &mut Component
// Try to clone using ReflectFromReflect
if let Some(reflect_from_reflect) =
registry.get_type_data::(type_id)
- {
- if let Some(mut component) =
+ && let Some(mut component) =
reflect_from_reflect.from_reflect(source_component_reflect.as_partial_reflect())
+ {
+ if let Some(reflect_component) =
+ registry.get_type_data::(type_id)
{
- if let Some(reflect_component) =
- registry.get_type_data::(type_id)
- {
- reflect_component.map_entities(&mut *component, ctx.entity_mapper());
- }
- drop(registry);
-
- ctx.write_target_component_reflect(component);
- return;
+ reflect_component.map_entities(&mut *component, ctx.entity_mapper());
}
+ drop(registry);
+
+ ctx.write_target_component_reflect(component);
+ return;
}
// Else, try to clone using ReflectDefault
if let Some(reflect_default) =
diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs
index ac0b8f3894ee6..0f7105e0eb0ce 100644
--- a/crates/bevy_ecs/src/entity/clone_entities.rs
+++ b/crates/bevy_ecs/src/entity/clone_entities.rs
@@ -1144,11 +1144,11 @@ impl OptOut {
#[inline]
fn filter_deny(&mut self, id: ComponentId, world: &World) {
self.deny.insert(id);
- if self.attach_required_by_components {
- if let Some(required_by) = world.components().get_required_by(id) {
- self.deny.extend(required_by.iter());
- };
- }
+ if self.attach_required_by_components
+ && let Some(required_by) = world.components().get_required_by(id)
+ {
+ self.deny.extend(required_by.iter());
+ };
}
}
diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs
index 7ba90f68e06b2..e10ec4c5e0ddd 100644
--- a/crates/bevy_ecs/src/observer/mod.rs
+++ b/crates/bevy_ecs/src/observer/mod.rs
@@ -219,18 +219,18 @@ impl World {
&& observers.entity_component_observers.is_empty()
{
cache.component_observers.remove(component);
- if let Some(flag) = Observers::is_archetype_cached(event_key) {
- if let Some(by_component) = archetypes.by_component.get(component) {
- for archetype in by_component.keys() {
- let archetype = &mut archetypes.archetypes[archetype.index()];
- if archetype.contains(*component) {
- let no_longer_observed = archetype
- .iter_components()
- .all(|id| !cache.component_observers.contains_key(&id));
-
- if no_longer_observed {
- archetype.flags.set(flag, false);
- }
+ if let Some(flag) = Observers::is_archetype_cached(event_key)
+ && let Some(by_component) = archetypes.by_component.get(component)
+ {
+ for archetype in by_component.keys() {
+ let archetype = &mut archetypes.archetypes[archetype.index()];
+ if archetype.contains(*component) {
+ let no_longer_observed = archetype
+ .iter_components()
+ .all(|id| !cache.component_observers.contains_key(&id));
+
+ if no_longer_observed {
+ archetype.flags.set(flag, false);
}
}
}
diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs
index d672b6ae4d0b7..2cab429557c7c 100644
--- a/crates/bevy_ecs/src/query/fetch.rs
+++ b/crates/bevy_ecs/src/query/fetch.rs
@@ -3319,10 +3319,10 @@ mod tests {
fn system(query: Query) {
for entity_ref in &query {
- if let Some(c) = entity_ref.get_ref::() {
- if !c.is_added() {
- panic!("Expected C to be added");
- }
+ if let Some(c) = entity_ref.get_ref::()
+ && !c.is_added()
+ {
+ panic!("Expected C to be added");
}
}
}
diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs
index 2778568dead6e..62849f4977675 100644
--- a/crates/bevy_ecs/src/relationship/mod.rs
+++ b/crates/bevy_ecs/src/relationship/mod.rs
@@ -188,28 +188,27 @@ pub trait Relationship: Component + Sized {
}
}
let target_entity = world.entity(entity).get::().unwrap().get();
- if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) {
- if let Some(mut relationship_target) =
+ if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity)
+ && let Some(mut relationship_target) =
target_entity_mut.get_mut::()
- {
- relationship_target.collection_mut_risky().remove(entity);
- if relationship_target.len() == 0 {
- let command = |mut entity: EntityWorldMut| {
- // this "remove" operation must check emptiness because in the event that an identical
- // relationship is inserted on top, this despawn would result in the removal of that identical
- // relationship ... not what we want!
- if entity
- .get::()
- .is_some_and(RelationshipTarget::is_empty)
- {
- entity.remove::();
- }
- };
-
- world
- .commands()
- .queue_silenced(command.with_entity(target_entity));
- }
+ {
+ relationship_target.collection_mut_risky().remove(entity);
+ if relationship_target.len() == 0 {
+ let command = |mut entity: EntityWorldMut| {
+ // this "remove" operation must check emptiness because in the event that an identical
+ // relationship is inserted on top, this despawn would result in the removal of that identical
+ // relationship ... not what we want!
+ if entity
+ .get::()
+ .is_some_and(RelationshipTarget::is_empty)
+ {
+ entity.remove::();
+ }
+ };
+
+ world
+ .commands()
+ .queue_silenced(command.with_entity(target_entity));
}
}
}
diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs
index a69a1355a66e1..234af6bdf2fee 100644
--- a/crates/bevy_ecs/src/system/system.rs
+++ b/crates/bevy_ecs/src/system/system.rs
@@ -442,10 +442,10 @@ where
// Note that the `downcast_mut` check is based on the static type,
// and can be optimized out after monomorphization.
let any: &mut dyn Any = &mut value;
- if let Some(err) = any.downcast_mut::() {
- if err.skipped {
- return Self::Skipped(core::mem::replace(err, SystemParamValidationError::EMPTY));
- }
+ if let Some(err) = any.downcast_mut::()
+ && err.skipped
+ {
+ return Self::Skipped(core::mem::replace(err, SystemParamValidationError::EMPTY));
}
Self::Failed(From::from(value))
}
From ecbb5626d41321f067c03b84cce68c4333ee89ea Mon Sep 17 00:00:00 2001
From: bilsen <40690317+bilsen@users.noreply.github.com>
Date: Fri, 5 Dec 2025 19:14:55 +0100
Subject: [PATCH 020/133] Clarify docs for `ExtractSchedule` (#22012)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The previous docs incorrectly states that the `ExtractSchedule` is run
on the main world. Also some small additional changes.
---------
Co-authored-by: Ivar Källström
Co-authored-by: JMS55 <47158642+JMS55@users.noreply.github.com>
---
crates/bevy_render/src/lib.rs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs
index 11b6ceb63bce4..b1e7837b3ec04 100644
--- a/crates/bevy_render/src/lib.rs
+++ b/crates/bevy_render/src/lib.rs
@@ -247,13 +247,13 @@ impl Render {
}
}
-/// Schedule which extract data from the main world and inserts it into the render world.
+/// Schedule in which data from the main world is 'extracted' into the render world.
///
/// This step should be kept as short as possible to increase the "pipelining potential" for
/// running the next frame while rendering the current frame.
///
-/// This schedule is run on the main world, but its buffers are not applied
-/// until it is returned to the render world.
+/// This schedule is run on the render world, but it also has access to the main world.
+/// See [`MainWorld`] and [`Extract`] for details on how to access main world data from this schedule.
#[derive(ScheduleLabel, PartialEq, Eq, Debug, Clone, Hash, Default)]
pub struct ExtractSchedule;
From dba7c749649c257640489a7c2503d75aec197fd3 Mon Sep 17 00:00:00 2001
From: ickshonpe
Date: Sat, 6 Dec 2025 17:10:14 +0000
Subject: [PATCH 021/133] Don't update the text buffer in `text_system`
(#21966)
# Objective
Text is shaped in `measure_text_system`. When the schedule reaches
`text_system` there's no need to reupdate the cosmic-text buffer a
second time. `text_system` should only be updating any stale
`TextLayoutInfo` components.
## Solution
* Add a new method `update_text_layout_info` to `TextPipeline`. This
method updates the given `TextLayoutInfo` without performing any
shaping.
* Call `update_text_layout_info` instead of `queue_text` from
`text_system`.
* Only query for `TextFont`, instead of the full `TextUiReader`.
#
The next step is to remove `TextPipeline::queue_text`. I didn't do that
here as it's a fairly large refactor and I have a bunch of other open
text PRs I'd like to get merged first.
## Testing
#### yellow = this PR, red = main
```
cargo run --example many_glyphs --release --features trace_tracy,debug -- --no-text2d --recompute-text
```
```
cargo run --example many_buttons --release --features trace_tracy,debug -- --text --respawn
```
---
crates/bevy_text/src/pipeline.rs | 190 +++++++++++++++++++++++++++++-
crates/bevy_ui/src/widget/text.rs | 96 +++++++--------
2 files changed, 233 insertions(+), 53 deletions(-)
diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs
index 5d3fe166d1f82..08d98e8dbea14 100644
--- a/crates/bevy_text/src/pipeline.rs
+++ b/crates/bevy_text/src/pipeline.rs
@@ -4,8 +4,11 @@ use bevy_asset::{AssetId, Assets};
use bevy_color::Color;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
- component::Component, entity::Entity, reflect::ReflectComponent, resource::Resource,
- system::ResMut,
+ component::Component,
+ entity::Entity,
+ reflect::ReflectComponent,
+ resource::Resource,
+ system::{Query, ResMut},
};
use bevy_image::prelude::*;
use bevy_log::{once, warn};
@@ -498,6 +501,189 @@ impl TextPipeline {
.cloned()
.map(|(id, _)| id)
}
+
+ /// Update [`TextLayoutInfo`] with the new [`PositionedGlyph`] layout.
+ pub fn update_text_layout_info<'a>(
+ &mut self,
+ layout_info: &mut TextLayoutInfo,
+ text_font_query: Query<&'a TextFont>,
+ scale_factor: f64,
+ font_atlas_set: &mut FontAtlasSet,
+ texture_atlases: &mut Assets,
+ textures: &mut Assets,
+ computed: &mut ComputedTextBlock,
+ font_system: &mut CosmicFontSystem,
+ swash_cache: &mut SwashCache,
+ bounds: TextBounds,
+ ) -> Result<(), TextError> {
+ layout_info.glyphs.clear();
+ layout_info.run_geometry.clear();
+ layout_info.size = Default::default();
+
+ self.glyph_info.clear();
+
+ for text_font in text_font_query.iter_many(computed.entities.iter().map(|e| e.entity)) {
+ let mut section_info = (
+ text_font.font.id(),
+ text_font.font_smoothing,
+ text_font.font_size,
+ 0.0,
+ 0.0,
+ 0.0,
+ );
+
+ if let Some((id, _)) = self.map_handle_to_font_id.get(§ion_info.0) {
+ let weight = font_system
+ .db()
+ .face(*id)
+ .map(|f| f.weight)
+ .unwrap_or(cosmic_text::Weight::NORMAL);
+ if let Some(font) = font_system.get_font(*id, weight) {
+ let swash = font.as_swash();
+ let metrics = swash.metrics(&[]);
+ let upem = metrics.units_per_em as f32;
+ let scalar = section_info.2 * scale_factor as f32 / upem;
+ section_info.3 = (metrics.strikeout_offset * scalar).round();
+ section_info.4 = (metrics.stroke_size * scalar).round().max(1.);
+ section_info.5 = (metrics.underline_offset * scalar).round();
+ }
+ }
+ self.glyph_info.push(section_info);
+ }
+
+ let buffer = &mut computed.buffer;
+ buffer.set_size(font_system, bounds.width, bounds.height);
+ let box_size = buffer_dimensions(buffer);
+
+ let result = buffer.layout_runs().try_for_each(|run| {
+ let mut current_section: Option = None;
+ let mut start = 0.;
+ let mut end = 0.;
+ let result = run
+ .glyphs
+ .iter()
+ .map(move |layout_glyph| (layout_glyph, run.line_y, run.line_i))
+ .try_for_each(|(layout_glyph, line_y, line_i)| {
+ match current_section {
+ Some(section) => {
+ if section != layout_glyph.metadata {
+ layout_info.run_geometry.push(RunGeometry {
+ span_index: section,
+ bounds: Rect::new(
+ start,
+ run.line_top,
+ end,
+ run.line_top + run.line_height,
+ ),
+ strikethrough_y: (run.line_y - self.glyph_info[section].3)
+ .round(),
+ strikethrough_thickness: self.glyph_info[section].4,
+ underline_y: (run.line_y - self.glyph_info[section].5).round(),
+ underline_thickness: self.glyph_info[section].4,
+ });
+ start = end.max(layout_glyph.x);
+ current_section = Some(layout_glyph.metadata);
+ }
+ end = layout_glyph.x + layout_glyph.w;
+ }
+ None => {
+ current_section = Some(layout_glyph.metadata);
+ start = layout_glyph.x;
+ end = start + layout_glyph.w;
+ }
+ }
+
+ let mut temp_glyph;
+ let span_index = layout_glyph.metadata;
+ let font_id = self.glyph_info[span_index].0;
+ let font_smoothing = self.glyph_info[span_index].1;
+
+ let layout_glyph = if font_smoothing == FontSmoothing::None {
+ // If font smoothing is disabled, round the glyph positions and sizes,
+ // effectively discarding all subpixel layout.
+ temp_glyph = layout_glyph.clone();
+ temp_glyph.x = temp_glyph.x.round();
+ temp_glyph.y = temp_glyph.y.round();
+ temp_glyph.w = temp_glyph.w.round();
+ temp_glyph.x_offset = temp_glyph.x_offset.round();
+ temp_glyph.y_offset = temp_glyph.y_offset.round();
+ temp_glyph.line_height_opt = temp_glyph.line_height_opt.map(f32::round);
+
+ &temp_glyph
+ } else {
+ layout_glyph
+ };
+
+ let physical_glyph = layout_glyph.physical((0., 0.), 1.);
+
+ let font_atlases = font_atlas_set
+ .entry(FontAtlasKey(
+ font_id,
+ physical_glyph.cache_key.font_size_bits,
+ font_smoothing,
+ ))
+ .or_default();
+
+ let atlas_info = get_glyph_atlas_info(font_atlases, physical_glyph.cache_key)
+ .map(Ok)
+ .unwrap_or_else(|| {
+ add_glyph_to_atlas(
+ font_atlases,
+ texture_atlases,
+ textures,
+ &mut font_system.0,
+ &mut swash_cache.0,
+ layout_glyph,
+ font_smoothing,
+ )
+ })?;
+
+ let texture_atlas = texture_atlases.get(atlas_info.texture_atlas).unwrap();
+ let location = atlas_info.location;
+ let glyph_rect = texture_atlas.textures[location.glyph_index];
+ let left = location.offset.x as f32;
+ let top = location.offset.y as f32;
+ let glyph_size = UVec2::new(glyph_rect.width(), glyph_rect.height());
+
+ // offset by half the size because the origin is center
+ let x = glyph_size.x as f32 / 2.0 + left + physical_glyph.x as f32;
+ let y =
+ line_y.round() + physical_glyph.y as f32 - top + glyph_size.y as f32 / 2.0;
+
+ let position = Vec2::new(x, y);
+
+ let pos_glyph = PositionedGlyph {
+ position,
+ size: glyph_size.as_vec2(),
+ atlas_info,
+ span_index,
+ byte_index: layout_glyph.start,
+ byte_length: layout_glyph.end - layout_glyph.start,
+ line_index: line_i,
+ };
+ layout_info.glyphs.push(pos_glyph);
+ Ok(())
+ });
+ if let Some(section) = current_section {
+ layout_info.run_geometry.push(RunGeometry {
+ span_index: section,
+ bounds: Rect::new(start, run.line_top, end, run.line_top + run.line_height),
+ strikethrough_y: (run.line_y - self.glyph_info[section].3).round(),
+ strikethrough_thickness: self.glyph_info[section].4,
+ underline_y: (run.line_y - self.glyph_info[section].5).round(),
+ underline_thickness: self.glyph_info[section].4,
+ });
+ }
+
+ result
+ });
+
+ // Check result.
+ result?;
+
+ layout_info.size = box_size;
+ Ok(())
+ }
}
/// Render information for a corresponding text block.
diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs
index c84b463ddf3cb..c132bee60165d 100644
--- a/crates/bevy_ui/src/widget/text.rs
+++ b/crates/bevy_ui/src/widget/text.rs
@@ -319,72 +319,66 @@ pub fn measure_text_system(
/// It does not modify or observe existing ones. The exception is when adding new glyphs to a [`bevy_text::FontAtlas`].
pub fn text_system(
mut textures: ResMut>,
- fonts: Res>,
mut texture_atlases: ResMut>,
mut font_atlas_set: ResMut,
mut text_pipeline: ResMut,
mut text_query: Query<(
- Entity,
Ref,
&TextLayout,
&mut TextLayoutInfo,
&mut TextNodeFlags,
&mut ComputedTextBlock,
)>,
- mut text_reader: TextUiReader,
+ text_font_query: Query<&TextFont>,
mut font_system: ResMut,
mut swash_cache: ResMut,
) {
- for (entity, node, block, text_layout_info, mut text_flags, mut computed) in &mut text_query {
- // Skip the text node if it is waiting for a new measure func
- if text_flags.needs_measure_fn {
- continue;
- }
-
- if !(node.is_changed() || text_flags.needs_recompute) {
- continue;
- }
+ for (node, block, mut text_layout_info, mut text_flags, mut computed) in &mut text_query {
+ if node.is_changed() || text_flags.needs_recompute {
+ // Skip the text node if it is waiting for a new measure func
+ if text_flags.needs_measure_fn {
+ continue;
+ }
- let physical_node_size = if block.linebreak == LineBreak::NoWrap {
- // With `NoWrap` set, no constraints are placed on the width of the text.
- TextBounds::UNBOUNDED
- } else {
- // `scale_factor` is already multiplied by `UiScale`
- TextBounds::new(node.unrounded_size.x, node.unrounded_size.y)
- };
+ let scale_factor = node.inverse_scale_factor().recip().into();
+ let physical_node_size = if block.linebreak == LineBreak::NoWrap {
+ // With `NoWrap` set, no constraints are placed on the width of the text.
+ TextBounds::UNBOUNDED
+ } else {
+ // `scale_factor` is already multiplied by `UiScale`
+ TextBounds::new(node.unrounded_size.x, node.unrounded_size.y)
+ };
- let text_layout_info = text_layout_info.into_inner();
- match text_pipeline.queue_text(
- text_layout_info,
- &fonts,
- text_reader.iter(entity),
- node.inverse_scale_factor.recip() as f64,
- block,
- physical_node_size,
- &mut font_atlas_set,
- &mut texture_atlases,
- &mut textures,
- &mut computed,
- &mut font_system,
- &mut swash_cache,
- ) {
- Err(TextError::NoSuchFont) => {
- // There was an error processing the text layout, try again next frame
- text_flags.needs_recompute = true;
- }
- Err(
- e @ (TextError::FailedToAddGlyph(_)
- | TextError::FailedToGetGlyphImage(_)
- | TextError::MissingAtlasLayout
- | TextError::MissingAtlasTexture
- | TextError::InconsistentAtlasState),
- ) => {
- panic!("Fatal error when processing text: {e}.");
- }
- Ok(()) => {
- text_layout_info.scale_factor = node.inverse_scale_factor.recip();
- text_layout_info.size *= node.inverse_scale_factor;
- text_flags.needs_recompute = false;
+ match text_pipeline.update_text_layout_info(
+ &mut text_layout_info,
+ text_font_query,
+ scale_factor,
+ &mut font_atlas_set,
+ &mut texture_atlases,
+ &mut textures,
+ &mut computed,
+ &mut font_system,
+ &mut swash_cache,
+ physical_node_size,
+ ) {
+ Err(TextError::NoSuchFont) => {
+ // There was an error processing the text layout, try again next frame
+ text_flags.needs_recompute = true;
+ }
+ Err(
+ e @ (TextError::FailedToAddGlyph(_)
+ | TextError::FailedToGetGlyphImage(_)
+ | TextError::MissingAtlasLayout
+ | TextError::MissingAtlasTexture
+ | TextError::InconsistentAtlasState),
+ ) => {
+ panic!("Fatal error when processing text: {e}.");
+ }
+ Ok(()) => {
+ text_layout_info.scale_factor = scale_factor as f32;
+ text_layout_info.size *= node.inverse_scale_factor();
+ text_flags.needs_recompute = false;
+ }
}
}
}
From 008ca5d1b3fb99ae866d9dfcf02e399f7e17d32e Mon Sep 17 00:00:00 2001
From: Kevin Reid
Date: Sat, 6 Dec 2025 18:39:24 -0800
Subject: [PATCH 022/133] Remove `getrandom` dependency from `bevy_platform`.
(#21971)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
# Objective
Fixes #21336.
`bevy_platform` and dependents such as `bevy_ecs` may be used on web
without incurring the `RUSTFLAGS` requirement of `getrandom`.
## Solution
Remove dependency `bevy_platform` → `getrandom`.
Note: There was a suggestion of adding a dependency `bevy_math` →
`getrandom` to replace it, but `bevy_math` does not actually depend
transitively on `getrandom` at all (except in its examples), nor does
any other Bevy library. If making `getrandom` work is desired for
convenience of Bevy users, we could add `bevy_internal/web` →
`getrandom/wasm_js`, but that would not be for the use of Bevy itself at
all, and it would mean that Bevy users would hit the `RUSTFLAGS`
requirement even if they don't truly need it.
## Testing
- Ran `cargo run -p ci -- compile` and `cargo run -p ci -- test`.
- Tested depending on this version of `bevy_ecs` in my own wasm32
project.
- Have not tested any further feature combinations.
---
crates/bevy_platform/Cargo.toml | 4 ----
1 file changed, 4 deletions(-)
diff --git a/crates/bevy_platform/Cargo.toml b/crates/bevy_platform/Cargo.toml
index c428a6bf7b79d..e17d5a4bc0c72 100644
--- a/crates/bevy_platform/Cargo.toml
+++ b/crates/bevy_platform/Cargo.toml
@@ -50,7 +50,6 @@ critical-section = ["dep:critical-section", "portable-atomic/critical-section"]
web = [
"std",
"dep:web-time",
- "dep:getrandom",
"dep:wasm-bindgen-futures",
"dep:wasm-bindgen",
"dep:js-sys",
@@ -76,9 +75,6 @@ rayon = { version = "1", default-features = false, optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-time = { version = "1.1", default-features = false, optional = true }
-getrandom = { version = "0.3.0", default-features = false, optional = true, features = [
- "wasm_js",
-] }
wasm-bindgen-futures = { version = "0.4", default-features = false, optional = true }
futures-channel = { version = "0.3", default-features = false }
js-sys = { version = "0.3", default-features = false, optional = true }
From ad1c38a3f9624b7883075e140eebe4977ed7fa53 Mon Sep 17 00:00:00 2001
From: andriyDev
Date: Sun, 7 Dec 2025 10:00:57 -0800
Subject: [PATCH 023/133] Fix the doc test for WebAssetPlugin (#21968)
# Objective
- Fix `cargo t -p bevy_asset --all-features`.
- Note: `all-features` do not run on CI!
- The doc tests for the `WebAssetPlugin` don't compile at all. There are
bad imports and we don't even add the `AssetPlugin`.
## Solution
- Add missing imports, and add missing plugins to the `App`.
## Testing
- Ran `cargo t -p bevy_asset --all-features` and it passes now!
---
crates/bevy_asset/src/io/web.rs | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/crates/bevy_asset/src/io/web.rs b/crates/bevy_asset/src/io/web.rs
index 196e36325c1a2..e6ec6d615bf3a 100644
--- a/crates/bevy_asset/src/io/web.rs
+++ b/crates/bevy_asset/src/io/web.rs
@@ -23,12 +23,12 @@ use tracing::warn;
/// Example usage:
///
/// ```rust
-/// # use bevy_app::{App, Startup};
-/// # use bevy_ecs::prelude::{Commands, Res};
-/// # use bevy_asset::web::{WebAssetPlugin, AssetServer};
+/// # use bevy_app::{App, Startup, TaskPoolPlugin};
+/// # use bevy_ecs::prelude::{Commands, Component, Res};
+/// # use bevy_asset::{Asset, AssetApp, AssetPlugin, AssetServer, Handle, io::web::WebAssetPlugin};
+/// # use bevy_reflect::TypePath;
/// # struct DefaultPlugins;
-/// # impl DefaultPlugins { fn set(plugin: WebAssetPlugin) -> WebAssetPlugin { plugin } }
-/// # use bevy_asset::web::AssetServer;
+/// # impl DefaultPlugins { fn set(&self, plugin: WebAssetPlugin) -> WebAssetPlugin { plugin } }
/// # #[derive(Asset, TypePath, Default)]
/// # struct Image;
/// # #[derive(Component)]
@@ -39,6 +39,8 @@ use tracing::warn;
/// .add_plugins(DefaultPlugins.set(WebAssetPlugin {
/// silence_startup_warning: true,
/// }))
+/// # .add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()))
+/// # .init_asset::()
/// # .add_systems(Startup, setup).run();
/// # }
/// // ...
From 25d4cedfc686bead62a7cbeb85a8312e7d8e1a5c Mon Sep 17 00:00:00 2001
From: Chris Biscardi
Date: Sun, 7 Dec 2025 10:18:36 -0800
Subject: [PATCH 024/133] enable easier modification of image samplers in image
loaders (#22042)
# Objective
Previously, to modify a sampler when loading an image with settings it
was required to set all fields individually. Much of the time, these
settings are all the same so to enable repeating textures you have to
set the address_mode 3 times. To set filters you have to set them
another 3 times.
```rust
MeshMaterial3d(materials.add(
StandardMaterial {
base_color_texture:
Some(
asset_server.load_with_settings(
"floor_graph_base_color.png",
|settings: &mut ImageLoaderSettings| {
let descriptor = settings.sampler.get_or_init_descriptor();
descriptor.address_mode_u = bevy::image::ImageAddressMode::Repeat;
descriptor.address_mode_v = bevy::image::ImageAddressMode::Repeat;
descriptor.address_mode_w = bevy::image::ImageAddressMode::Repeat;
descriptor.mag_filter = ImageFilterMode::Linear;
descriptor.min_filter = ImageFilterMode::Linear;
descriptor.mipmap_filter = ImageFilterMode::Linear;
}
),
),
unlit: true,
cull_mode: None,
uv_transform: Affine2::from_scale(Vec2::new(30., 90.)),
..default()
},
)),
```
## Solution
Add two new helpers, loosely modeled after the `Transform::with_`
functions. Further modifications can still be made to the descriptor for
use cases which want to set one of the three fields differently, or
additionally set anisotropy_clamp, etc.
```rust
let descriptor = settings
.sampler
.get_or_init_descriptor()
.set_address_mode(ImageAddressMode::Repeat)
.set_filter(ImageFilterMode::Linear);
```
## Testing
New test in bevy_image
## Showcase
```rust
MeshMaterial3d(materials.add(
StandardMaterial {
base_color_texture:
Some(
asset_server.load_with_settings(
"floor_graph_base_color.png",
|settings: &mut ImageLoaderSettings| {
let descriptor = settings
.sampler
.get_or_init_descriptor()
.set_address_mode(ImageAddressMode::Repeat)
.set_filter(ImageFilterMode::Linear);
}
),
),
unlit: true,
cull_mode: None,
uv_transform: Affine2::from_scale(Vec2::new(30., 90.)),
..default()
},
)),
```
This PR now includes:
- `set_filter`
- `set_address_mode`
- `set_anisotropic_filter`
---------
Co-authored-by: Greeble <166992735+greeble-dev@users.noreply.github.com>
---
crates/bevy_image/src/image.rs | 60 ++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs
index b296ecdf9388c..aeeb58b1a7d98 100644
--- a/crates/bevy_image/src/image.rs
+++ b/crates/bevy_image/src/image.rs
@@ -699,6 +699,36 @@ impl ImageSamplerDescriptor {
}
}
+ /// Returns this sampler descriptor with a new `ImageFilterMode` min, mag, and mipmap filters
+ #[inline]
+ pub fn set_filter(&mut self, filter: ImageFilterMode) -> &mut Self {
+ self.mag_filter = filter;
+ self.min_filter = filter;
+ self.mipmap_filter = filter;
+ self
+ }
+
+ /// Returns this sampler descriptor with a new `ImageAddressMode` for u, v, and w
+ #[inline]
+ pub fn set_address_mode(&mut self, address_mode: ImageAddressMode) -> &mut Self {
+ self.address_mode_u = address_mode;
+ self.address_mode_v = address_mode;
+ self.address_mode_w = address_mode;
+ self
+ }
+
+ /// Returns this sampler descriptor with an `anisotropy_clamp` value and also
+ /// set filters to `ImageFilterMode::Linear`, which is required to
+ /// use anisotropy.
+ #[inline]
+ pub fn set_anisotropic_filter(&mut self, anisotropy_clamp: u16) -> &mut Self {
+ self.mag_filter = ImageFilterMode::Linear;
+ self.min_filter = ImageFilterMode::Linear;
+ self.mipmap_filter = ImageFilterMode::Linear;
+ self.anisotropy_clamp = anisotropy_clamp;
+ self
+ }
+
pub fn as_wgpu(&self) -> SamplerDescriptor