# Objective
- Another step towards unifying our orthonormal basis construction
#20050
- Preserve behavior but fix a bug. Unification will be a followup after
these two PRs and will need more thorough testing.
## Solution
- Make shadow cubemap sampling orthonormalize have the same function
signature as the other orthonormal basis functions in bevy
## Testing
- 3d_scene + lighting examples
Noticed that we're converting perceptual_roughness to roughness for SSAO
specular occlusion up here, _but_ that happens _before_ we sample the
metallic_roughness texture map. So we're using the wrong roughness. I
assume this is a bug and was not intentional.
Suggest reviewing while hiding the whitespace diff.
## Objective
Fixes#20058
## Solution
Fix the `dynamic_offsets` array being too small if a mesh has morphs and
skins and motion blur, and the renderer isn't using storage buffers
(i.e. WebGL2). The bug was introduced in #13572.
## Testing
- Minimal repro: https://github.com/M4tsuri/bevy_reproduce.
- Also examples `animated_mesh`, `morph_targets`,
`test_invalid_skinned_meshes`.
- As far as I can tell Bevy doesn't have any examples or tests that can
repro the problem combination.
Tested with WebGL and native, Win10/Chrome/Nvidia.
# Objective
The extraction systems for materials, meshes, and skins previously
iterated over `RemovedComponents<ViewVisibility>` in addition to more
specific variants like `RemovedComponents<MeshMaterial3d<M>>`. This
caused each system to loop through and check many irrelevant despawned
entities—sometimes multiple times. With many material types, this
overhead added up and became noticeable in frames with many despawns.
<img width="1091" alt="Screenshot 2025-02-21 at 10 28 01 AM"
src="https://github.com/user-attachments/assets/63fec1c9-232c-45f6-9150-daf8751ecf85"
/>
## Solution
This PR removes superfluous `RemovedComponents` iteration for
`ViewVisibility` and `GlobalTransform`, ensuring that we only iterate
over the most specific `RemovedComponents` relevant to the system (e.g.,
material components, mesh components). This is guaranteed to match what
the system originally collected.
### Before (red) / After (yellow):
<img width="838" alt="Screenshot 2025-02-21 at 10 46 17 AM"
src="https://github.com/user-attachments/assets/0e06b06d-7e91-4da5-a919-b843eb442a72"
/>
Log plot to highlight the long tail that this PR is addressing.
# Objective
- Related to #19024.
## Solution
- This is a mix of several ways to get rid of weak handles. The primary
strategy is putting strong asset handles in resources that the rendering
code clones into its pipelines (or whatever).
- This does not handle every remaining case, but we are slowly clearing
them out.
## Testing
- `anti_aliasing` example still works.
- `fog_volumes` example still works.
# Objective
- nice bevy::camera bevy::mesh bevy::light imports
- skip bevy_light in 2d
## Solution
- add optional crates to internal
- make light only included when building pbr
## Testing
- 3d_scene
# Objective
- make lights usable without bevy_render
## Solution
- make a new crate for lights to live in
## Testing
- 3d_scene, lighting, volumetric_fog, ssr, transmission, pcss,
light_textures
Note: no breaking changes because of re-exports, except for light
textures, which were introduced this cycle so it doesn't matter anyways
# Objective
- Calculating gradients in variable-termination loop is bad, and we dont
need to here
## Solution
- Sample mip 0 always
## Testing
- volumetric_fog example
# Objective
- prepare bevy_light for split
- make struct named better
- put it where it belongs
## Solution
- do those things
## Testing
- 3d_scene, lighting
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
- prepare bevy_light for split
## Solution
- split render world extract related cluster code from main world ecs
stuff
re-exports make this not breaking
# Objective
- for smallvec some crates specify default features false, other dont.
turns out we dont need them
## Solution
- remove
## Testing
- 3d_scene
# Objective
- prepare bevy_light for split
## Solution
- extract cascade module (this is not strictly necessary for bevy_light)
- clean up imports to be less globby and tangled
- move light specific stuff into light modules
- move light system and type init from pbr into new LightPlugin
## Testing
- 3d_scene, lighting
NOTE TO REVIEWERS: it may help to review commits independently.
# Objective
- Make bevy_light possible by making it possible to split out
clusterable into bevy_camera
## Solution
- move ClusteredDecal to cluster module
- Depends on #19957 (because of the imports shuffling around) (draft
until thats merged)
## Testing
- 3d_scene runs
Note: no breaking changes thanks to re-exports
# Objective
- Make bevy_light possible
## Solution
- Move non-light stuff out of light module (its a marker for whether a
material should cast shadows: thats a material property not a light
property)
## Testing
- 3d_scene runs
# Objective
- Make bevy_light possible
## Solution
- Move some stuff it needs out of somewhere it cant depend on. Plus it
makes sense, cubemap stuff goes next to cubemap stuff.
## Testing
- 3d_scene runs
Note: no breaking changes thanks to re-exports
# Objective
- Make bevy_light possible by making it possible to split out
clusterable into bevy_camera
## Solution
- Move some stuff so i can split it out cleanly.
## Testing
- 3d_scene runs
# Objective
- Make bevy_light possible by making it possible to split out
clusterable into bevy_camera
## Solution
- Move cubemap stuff next to cubemap stuff.
## Testing
- 3d_scene runs
Note: no breaking changes thanks to re-exports
# Objective
- Make bevy_light possible
## Solution
- Move some stuff it needs out of somewhere it cant depend on. Plus it
makes sense, spotlight stuff goes in spotlight file.
## Testing
- 3d_scene runs
Note: no breaking changes thanks to re-exports
# Objective
- Make bevy_light possible by making it possible to split out
clusterable into bevy_camera
## Solution
- Use a resource to store cluster settings instead of recalculating it
every time from the render adapter/device
## Testing
- 3d_scene runs
# Objective
- Make bevy_light possible
## Solution
- Move some stuff it needs out of somewhere it cant depend on. Plus it
makes sense, visibility stuff goes in visibility.
## Testing
- 3d_scene runs
Note: no breaking changes thanks to re-exports
# Objective
for `BufferUsages::STORAGE` on webgpu (and maybe other contexts), buffer
sizes must be a multiple of 4. the skin uniform buffer starts at 16384
then increases by 1.5x, which eventually hits a number which isn't
## Solution
`.next_multiple_of(4)`
A few versions ago, wgpu made it possible to set shader entry point to
`None`, which will select the correct entry point in file where only a
single entrypoint is specified. This makes it possible to implement
`Default` for pipeline descriptors. This PR does so and attempts to
`..default()` everything possible.
# Objective
- This unblocks some work I am doing for #19887.
## Solution
- Rename `RenderGraphApp` to `RenderGraphExt`.
- Implement `RenderGraphExt` for `World`.
- Change `SubApp` and `App` to call the `World` impl.
# Objective
- Progress towards #19024.
## Solution
- Remove `Handle::Weak`!
If users were relying on `Handle::Weak` for some purpose, they can
almost certainly replace it with raw `AssetId` instead. If they cannot,
they can make their own enum that holds either a Handle or an AssetId.
In either case, we don't need weak handles!
Sadly we still need Uuid handles since we rely on them for "default"
assets and "invalid" assets, as well as anywhere where a component wants
to impl default with a non-defaulted asset handle. One step at a time
though!
# Objective
- PrepassPipelineInternal used to exist to optimize compile time and
binary size when PrepassPipeline was generic over the material.
- After #19667, PrepassPipeline is no longer generic!
## Solution
- Flatten all the fields of `PrepassPipelineInternal` into
`PrepassPipeline`.
Previously, the specialize/queue systems were added per-material and the
plugin prepass/shadow enable flags controlled whether we added those
systems. Now, we make this a property of the material instance and check
for it when specializing. Fixes
https://github.com/bevyengine/bevy/issues/19850.
# Objective
- This plugin currently does nothing. That's because we add the plugin
to the `RenderApp`. Inside the plugin it then looks for the `RenderApp`
itself, but since it was added **to** the `RenderApp`, it will never
find the `RenderApp`.
## Solution
- Move the plugin into build, and more importantly, add it to the app
not the render_app.
# Objective
- The MaterialPlugin has some ugly code to initialize some data in the
render world
- #19887
## Solution
- Use the new RenderStartup schedule to use a system instead of using
the plugin `finish()`
## Testing
- Tested that the 3d_scene and shader_material example still work as
expected
# Objective
add support for light textures (also known as light cookies, light
functions, and light projectors)

## Solution
- add components:
```rs
/// Add to a [`PointLight`] to add a light texture effect.
/// A texture mask is applied to the light source to modulate its intensity,
/// simulating patterns like window shadows, gobo/cookie effects, or soft falloffs.
pub struct PointLightTexture {
/// The texture image. Only the R channel is read.
pub image: Handle<Image>,
/// The cubemap layout. The image should be a packed cubemap in one of the formats described by the [`CubemapLayout`] enum.
pub cubemap_layout: CubemapLayout,
}
/// Add to a [`SpotLight`] to add a light texture effect.
/// A texture mask is applied to the light source to modulate its intensity,
/// simulating patterns like window shadows, gobo/cookie effects, or soft falloffs.
pub struct SpotLightTexture {
/// The texture image. Only the R channel is read.
/// Note the border of the image should be entirely black to avoid leaking light.
pub image: Handle<Image>,
}
/// Add to a [`DirectionalLight`] to add a light texture effect.
/// A texture mask is applied to the light source to modulate its intensity,
/// simulating patterns like window shadows, gobo/cookie effects, or soft falloffs.
pub struct DirectionalLightTexture {
/// The texture image. Only the R channel is read.
pub image: Handle<Image>,
/// Whether to tile the image infinitely, or use only a single tile centered at the light's translation
pub tiled: bool,
}
```
- store images to the `RenderClusteredDecals` buffer
- read the image and modulate the lights
- add `light_textures` example to showcase the new features
## Testing
see light_textures example
# Objective
- MaterialProperties uses HashMap for some data that is generally going
to be really small. This is likely using more memory than necessary
## Solution
- Use a SmallVec instead
- I used the size a StandardMaterial would need for all the backing
arrays
## Testing
- Tested the 3d_scene to confirm it still works
## Notes
I'm not sure if it made a measurable difference since I'm not sure how
to measure this. It's a bit hard to create an artificial workflow where
this would be the main bottleneck. This is very in the realm of
microoptimization.
# Objective
Closes#18075
In order to enable a number of patterns for dynamic materials in the
engine, it's necessary to decouple the renderer from the `Material`
trait.
This opens the possibility for:
- Materials that aren't coupled to `AsBindGroup`.
- 2d using the underlying 3d bindless infrastructure.
- Dynamic materials that can change their layout at runtime.
- Materials that aren't even backed by a Rust struct at all.
## Solution
In short, remove all trait bounds from render world material systems and
resources. This means moving a bunch of stuff onto `MaterialProperties`
and engaging in some hacks to make specialization work. Rather than
storing the bind group data in `MaterialBindGroupAllocator`, right now
we're storing it in a closure on `MaterialProperties`. TBD if this has
bad performance characteristics.
## Benchmarks
- `many_cubes`:
`cargo run --example many_cubes --release --features=bevy/trace_tracy --
--vary-material-data-per-instance`:

- @DGriffin91's Caldera
`cargo run --release --features=bevy/trace_tracy -- --random-materials`

- @DGriffin91's Caldera with 20 unique material types (i.e.
`MaterialPlugin<M>`) and random materials per mesh
`cargo run --release --features=bevy/trace_tracy -- --random-materials`

### TODO
- We almost certainly lost some parallelization from removing the type
params that could be gained back from smarter iteration.
- Test all the things that could have broken.
- ~Fix meshlets~
## Showcase
See [the
example](https://github.com/bevyengine/bevy/pull/19667/files#diff-9d768cfe1c3aa81eff365d250d3cbe5a63e8df63e81dd85f64c3c3cd993f6d94)
for a custom material implemented without the use of the `Material`
trait and thus `AsBindGroup`.

---------
Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
Co-authored-by: IceSentry <c.giguere42@gmail.com>
# Objective
- i think const exprs werent supported in naga when these were written,
and we've just stuck with that since then. they're supported now so lets
use them
## Solution
- do that thang
## Testing
- transparency_3d, transmission, ssr, 3d_scene, couple others. they all
look fine
# Objective
Upgrade to `wgpu` version `25.0`.
Depends on https://github.com/bevyengine/naga_oil/pull/121
## Solution
### Problem
The biggest issue we face upgrading is the following requirement:
> To facilitate this change, there was an additional validation rule put
in place: if there is a binding array in a bind group, you may not use
dynamic offset buffers or uniform buffers in that bind group. This
requirement comes from vulkan rules on UpdateAfterBind descriptors.
This is a major difficulty for us, as there are a number of binding
arrays that are used in the view bind group. Note, this requirement does
not affect merely uniform buffors that use dynamic offset but the use of
*any* uniform in a bind group that also has a binding array.
### Attempted fixes
The easiest fix would be to change uniforms to be storage buffers
whenever binding arrays are in use:
```wgsl
#ifdef BINDING_ARRAYS_ARE_USED
@group(0) @binding(0) var<uniform> view: View;
@group(0) @binding(1) var<uniform> lights: types::Lights;
#else
@group(0) @binding(0) var<storage> view: array<View>;
@group(0) @binding(1) var<storage> lights: array<types::Lights>;
#endif
```
This requires passing the view index to the shader so that we know where
to index into the buffer:
```wgsl
struct PushConstants {
view_index: u32,
}
var<push_constant> push_constants: PushConstants;
```
Using push constants is no problem because binding arrays are only
usable on native anyway.
However, this greatly complicates the ability to access `view` in
shaders. For example:
```wgsl
#ifdef BINDING_ARRAYS_ARE_USED
mesh_view_bindings::view.view_from_world[0].z
#else
mesh_view_bindings::view[mesh_view_bindings::view_index].view_from_world[0].z
#endif
```
Using this approach would work but would have the effect of polluting
our shaders with ifdef spam basically *everywhere*.
Why not use a function? Unfortunately, the following is not valid wgsl
as it returns a binding directly from a function in the uniform path.
```wgsl
fn get_view() -> View {
#if BINDING_ARRAYS_ARE_USED
let view_index = push_constants.view_index;
let view = views[view_index];
#endif
return view;
}
```
This also poses problems for things like lights where we want to return
a ptr to the light data. Returning ptrs from wgsl functions isn't
allowed even if both bindings were buffers.
The next attempt was to simply use indexed buffers everywhere, in both
the binding array and non binding array path. This would be viable if
push constants were available everywhere to pass the view index, but
unfortunately they are not available on webgpu. This means either
passing the view index in a storage buffer (not ideal for such a small
amount of state) or using push constants sometimes and uniform buffers
only on webgpu. However, this kind of conditional layout infects
absolutely everything.
Even if we were to accept just using storage buffer for the view index,
there's also the additional problem that some dynamic offsets aren't
actually per-view but per-use of a setting on a camera, which would
require passing that uniform data on *every* camera regardless of
whether that rendering feature is being used, which is also gross.
As such, although it's gross, the simplest solution just to bump binding
arrays into `@group(1)` and all other bindings up one bind group. This
should still bring us under the device limit of 4 for most users.
### Next steps / looking towards the future
I'd like to avoid needing split our view bind group into multiple parts.
In the future, if `wgpu` were to add `@builtin(draw_index)`, we could
build a list of draw state in gpu processing and avoid the need for any
kind of state change at all (see
https://github.com/gfx-rs/wgpu/issues/6823). This would also provide
significantly more flexibility to handle things like offsets into other
arrays that may not be per-view.
### Testing
Tested a number of examples, there are probably more that are still
broken.
---------
Co-authored-by: François Mockers <mockersf@gmail.com>
Co-authored-by: Elabajaba <Elabajaba@users.noreply.github.com>
Updates the requirements on
[derive_more](https://github.com/JelteF/derive_more) to permit the
latest version.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/JelteF/derive_more/releases">derive_more's
releases</a>.</em></p>
<blockquote>
<h2>2.0.1</h2>
<p><a href="https://docs.rs/derive_more/2.0.1">API docs</a>
<a
href="https://github.com/JelteF/derive_more/blob/v2.0.1/CHANGELOG.md#201---2025-02-03">Changelog</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/JelteF/derive_more/blob/master/CHANGELOG.md">derive_more's
changelog</a>.</em></p>
<blockquote>
<h2>2.0.1 - 2025-02-03</h2>
<h3>Added</h3>
<ul>
<li>Add crate metadata for the Rust Playground. This makes sure that the
Rust
Playground will have all <code>derive_more</code> features available
once
<a
href="https://docs.rs/selectors/latest/selectors"><code>selectors</code></a>
crate updates its
<code>derive_more</code> version.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/445">#445</a>)</li>
</ul>
<h2>2.0.0 - 2025-02-03</h2>
<h3>Breaking changes</h3>
<ul>
<li><code>use derive_more::SomeTrait</code> now imports macro only.
Importing macro with
its trait along is possible now via <code>use
derive_more::with_trait::SomeTrait</code>.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/406">#406</a>)</li>
<li>Top-level <code>#[display("...")]</code> attribute on an
enum now has defaulting behavior
instead of replacing when no wrapping is possible (no
<code>_variant</code> placeholder).
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/395">#395</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Associated types of type parameters not being treated as generics in
<code>Debug</code>
and <code>Display</code> expansions.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/399">#399</a>)</li>
<li><code>unreachable_code</code> warnings on generated code when
<code>!</code> (never type) is used.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/404">#404</a>)</li>
<li>Ambiguous associated item error when deriving <code>TryFrom</code>,
<code>TryInto</code> or <code>FromStr</code>
with an associated item called <code>Error</code> or <code>Err</code>
respectively.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/410">#410</a>)</li>
<li>Top-level <code>#[display("...")]</code> attribute on an
enum being incorrectly treated
as transparent or wrapping.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/395">#395</a>)</li>
<li>Omitted raw identifiers in <code>Debug</code> and
<code>Display</code> expansions.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/431">#431</a>)</li>
<li>Incorrect rendering of raw identifiers as field names in
<code>Debug</code> expansions.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/431">#431</a>)</li>
<li>Top-level <code>#[display("...")]</code> attribute on an
enum not working transparently
for directly specified fields.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/438">#438</a>)</li>
<li>Incorrect dereferencing of unsized fields in <code>Debug</code> and
<code>Display</code> expansions.
(<a
href="https://redirect.github.com/JelteF/derive_more/pull/440">#440</a>)</li>
</ul>
<h2>0.99.19 - 2025-02-03</h2>
<ul>
<li>Add crate metadata for the Rust Playground.</li>
</ul>
<h2>1.0.0 - 2024-08-07</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a78d8ee41d"><code>a78d8ee</code></a>
chore: Release</li>
<li><a
href="2aeee4d1c0"><code>2aeee4d</code></a>
Update changelog (<a
href="https://redirect.github.com/JelteF/derive_more/issues/446">#446</a>)</li>
<li><a
href="5afbaa1d8e"><code>5afbaa1</code></a>
Add Rust Playground metadata (<a
href="https://redirect.github.com/JelteF/derive_more/issues/445">#445</a>)</li>
<li><a
href="d6c3315f12"><code>d6c3315</code></a>
Prepare 2.0.0 release (<a
href="https://redirect.github.com/JelteF/derive_more/issues/444">#444</a>)</li>
<li><a
href="c5e5e82c0a"><code>c5e5e82</code></a>
Fix unsized fields usage in <code>Display</code>/<code>Debug</code>
derives (<a
href="https://redirect.github.com/JelteF/derive_more/issues/440">#440</a>,
<a
href="https://redirect.github.com/JelteF/derive_more/issues/432">#432</a>)</li>
<li><a
href="d391493a3c"><code>d391493</code></a>
Fix field transparency for top-level shared attribute in
<code>Display</code> (<a
href="https://redirect.github.com/JelteF/derive_more/issues/438">#438</a>)</li>
<li><a
href="f14c7a759a"><code>f14c7a7</code></a>
Fix raw identifiers usage in <code>Display</code>/<code>Debug</code>
derives (<a
href="https://redirect.github.com/JelteF/derive_more/issues/434">#434</a>,
<a
href="https://redirect.github.com/JelteF/derive_more/issues/431">#431</a>)</li>
<li><a
href="7b23de3d53"><code>7b23de3</code></a>
Update <code>convert_case</code> crate from 0.6 to 0.7 version (<a
href="https://redirect.github.com/JelteF/derive_more/issues/436">#436</a>)</li>
<li><a
href="cc9957e9cd"><code>cc9957e</code></a>
Fix <code>compile_fail</code> tests and make Clippy happy for 1.84 Rust
(<a
href="https://redirect.github.com/JelteF/derive_more/issues/435">#435</a>)</li>
<li><a
href="17d61c3118"><code>17d61c3</code></a>
Fix transparency and behavior of shared formatting on enums (<a
href="https://redirect.github.com/JelteF/derive_more/issues/395">#395</a>,
<a
href="https://redirect.github.com/JelteF/derive_more/issues/377">#377</a>,
<a
href="https://redirect.github.com/JelteF/derive_more/issues/411">#411</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/JelteF/derive_more/compare/v1.0.0...v2.0.1">compare
view</a></li>
</ul>
</details>
<br />
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)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
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 <dependency name> 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)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
# Objective
#19410 added support for resizing images "in place" meaning that their
data was copied into the new texture allocation on the CPU. However,
there are some scenarios where an image may be created and populated
entirely on the GPU. Using this method would cause data to disappear, as
it wouldn't be copied into the new texture.
## Solution
When an image is resized in place, if it has no data in it's asset,
we'll opt into a new flag `copy_on_resize` which will issue a
`copy_texture_to_texture` command on the old allocation.
To support this, we require passing the old asset to all `RenderAsset`
implementations. This will be generally useful in the future for
reducing things like buffer re-allocations.
## Testing
Tested using the example in the issue.
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
- Remove a component impl footgun
- Make projection code slightly nicer, and remove the need to import the
projection trait when using the methods on `Projection`.
## Solution
- Do the things.
# Objective
- Related to #19024.
## Solution
- Remove the `FULLSCREEN_SHADER_HANDLE` `weak_handle` with a resource
holding the shader handle.
- This also changes us from using `load_internal_asset` to
`embedded_asset`/`load_embedded_asset`.
- All uses have been migrated to clone the `FullscreenShader` resource
and use its `to_vertex_state` method.
## Testing
- `anti_aliasing` example still works.
- `bloom_3d` example still works.
---------
Co-authored-by: charlotte 🌸 <charlotte.c.mcelwain@gmail.com>
# Objective
- Notice a word duplication typo
- Small quest to fix similar or nearby typos with my faithful companion
`\b(\w+)\s+\1\b`
## Solution
Fix em