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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/example-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
example_name=`basename $example .ron`
echo -n $example_name > last_example_run
echo "running $example_name - "`date`
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome,bevy_ui_debug"
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome,bevy_ui_debug,area_light_luts"
sleep 10
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
mkdir screenshots-$example_name
Expand Down Expand Up @@ -132,7 +132,7 @@ jobs:
example_name=`basename $example .ron`
echo -n $example_name > last_example_run
echo "running $example_name - "`date`
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example xvfb-run cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome,bevy_ui_debug"
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example xvfb-run cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome,bevy_ui_debug,area_light_luts"
sleep 10
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
mkdir screenshots-$example_name
Expand Down Expand Up @@ -202,7 +202,7 @@ jobs:
example_name=`basename $example .ron`
echo -n $example_name > last_example_run
echo "running $example_name - "`date`
time WGPU_BACKEND=dx12 TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "statically-linked-dxc,bevy_ci_testing,trace,trace_chrome,bevy_ui_debug"
time WGPU_BACKEND=dx12 TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "statically-linked-dxc,bevy_ci_testing,trace,trace_chrome,bevy_ui_debug,area_light_luts"
sleep 10
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
mkdir screenshots-$example_name
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Cargo.lock

# Bevy Assets
assets/**/*.meta
!assets/textures/GroundSand005/*.meta
crates/bevy_asset/imported_assets
imported_assets
.web-asset-cache
Expand Down
25 changes: 23 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,12 @@ trace = ["bevy_internal/trace", "dep:tracing"]
# Basis Universal compressed texture support
basis-universal = ["bevy_internal/basis-universal"]

# Enables compressed KTX2 UASTC texture output on the asset processor
# Texture compression asset processor (cross-platform, transcodes to any GPU format at load time)
compressed_image_saver_universal = [
"bevy_internal/compressed_image_saver_universal",
]

# Texture compression asset processor (BCn for desktop, ASTC for mobile via env var)
compressed_image_saver = ["bevy_internal/compressed_image_saver"]

# Enables system-level clipboard support.
Expand Down Expand Up @@ -574,6 +579,9 @@ bluenoise_texture = ["bevy_internal/bluenoise_texture"]
# Include a preintegrated BRDF Look Up Table for more accurate specular shading.
dfg_lut = ["bevy_internal/dfg_lut"]

# Include Look Up Tables that are required for area lights.
area_light_luts = ["bevy_internal/area_light_luts"]

# NVIDIA Deep Learning Super Sampling
dlss = ["bevy_internal/dlss"]

Expand Down Expand Up @@ -1190,7 +1198,7 @@ wasm = true
name = "rect_light"
path = "examples/3d/rect_light.rs"
doc-scrape-examples = true
required-features = ["free_camera"]
required-features = ["free_camera", "area_light_luts"]

[package.metadata.example.rect_light]
name = "Rectangular Area Light"
Expand Down Expand Up @@ -2080,6 +2088,18 @@ description = "Demonstrates loading a compressed asset"
category = "Assets"
wasm = false

[[example]]
name = "compressed_image_saver"
path = "examples/asset/compressed_image_saver.rs"
doc-scrape-examples = true
required-features = ["compressed_image_saver", "asset_processor", "jpeg"]

[package.metadata.example.compressed_image_saver]
name = "Compressed Image Saver"
description = "Demonstrates compressing textures and generating mipmaps using CompressedImageSaver"
category = "Assets"
wasm = false

[[example]]
name = "custom_asset"
path = "examples/asset/custom_asset.rs"
Expand Down Expand Up @@ -5192,6 +5212,7 @@ hidden = true
name = "testbed_3d"
path = "examples/testbed/3d.rs"
doc-scrape-examples = true
required-features = ["area_light_luts"]

[package.metadata.example.testbed_3d]
hidden = true
Expand Down
20 changes: 20 additions & 0 deletions _release-content/migration-guides/compressed_image_saver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: "`CompressedImageSaver` improvements"
pull_requests: [23567]
---

The `compressed_image_saver` Cargo feature has been reworked. The old behavior (Basis Universal UASTC compression) has been moved to a new feature called `compressed_image_saver_universal`, and the `compressed_image_saver` feature now uses the `ctt` library to compress textures into BCn (desktop) or ASTC (mobile) formats instead.

If you were using the `compressed_image_saver` feature and want to keep the previous Basis Universal behavior, rename the feature in your `Cargo.toml`:

```toml
# Before
bevy = { version = "0.18", features = ["compressed_image_saver"] }

# After (keeps old Basis Universal behavior)
bevy = { version = "0.19", features = ["compressed_image_saver_universal"] }
```

Alternatively, keep using `compressed_image_saver` to get the new BCn/ASTC compression backend. This produces higher-quality output and supports a wider range of input formats, but does not support all platforms in a single file like UASTC does. We recommend sticking to `compressed_image_saver_universal` when targeting the web.

`CompressedImageSaverError` has a new variant `CompressionFailed`. If you were matching exhaustively on this enum, add a branch for it.
2 changes: 2 additions & 0 deletions _release-content/release-notes/area_lights.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ The implementation uses [Linearly Transformed Cosines](https://eheitzresearch.wo

Rectangular lights currently don't cast shadows or have support for anisotropic materials.

You need to enable the `area_light_luts` cargo feature to use it.

Check out [the new example](https://github.com/bevyengine/bevy/tree/latest/examples/3d/rect_light.rs) to see them in action.
23 changes: 23 additions & 0 deletions _release-content/release-notes/compressed_image_saver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
title: CompressedImageSaver Improvements
authors: ["@JMS55", "@cwfitzgerald"]
pull_requests: [23567]
---

Bevy's `CompressedImageSaver` asset processor has been significantly upgraded with a new compression backend powered by the [`ctt`](https://github.com/cwfitzgerald/ctt) library.

The new `compressed_image_saver` feature compresses textures into BCn formats (for desktop GPUs) or ASTC formats (for mobile GPUs), producing higher-quality output than the previous Basis Universal approach. The compressor automatically selects the best output format based on the input texture's channel count and type — for example, single-channel textures get BC4, HDR textures get BC6H, and standard RGBA textures get BC7.

Try out the new `compressed_image_saver` example to see it in action.

## Automatic Mipmap Generation

No more manually generating mipmaps! The new backend automatically produces a full mip chain during compression. This means less aliasing when textures are viewed at a distance and better GPU cache utilization — all for free, just by running your textures through the asset processor.

## ASTC for Mobile

To target mobile GPUs, set the `BEVY_COMPRESSED_IMAGE_SAVER_ASTC` environment variable with your desired block size (e.g. `4x4`, `6x6`, `8x8`). Larger blocks give smaller files at the cost of quality. All 14 ASTC block sizes are supported.

## Basis Universal is Still Available

The previous Basis Universal compression behavior has been moved to the `compressed_image_saver_universal` feature. This remains the best choice for cross-platform distribution (including WebGPU), since UASTC can be transcoded at load time to whatever format the target GPU supports.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions assets/textures/GroundSand005/GroundSand005_COL_2K.jpg.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
(
meta_format_version: "1.0",
asset: Process(
processor: "LoadTransformAndSave<ImageLoader, IdentityAssetTransformer<Image>, CompressedImageSaver>",
settings: (
loader_settings: (
format: FromExtension,
is_srgb: true,
sampler: Default,
asset_usage: ("RENDER_WORLD"),
),
transformer_settings: (),
saver_settings: (),
),
),
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions assets/textures/GroundSand005/GroundSand005_DISP_2K.jpg.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
(
meta_format_version: "1.0",
asset: Process(
processor: "LoadTransformAndSave<ImageLoader, IdentityAssetTransformer<Image>, CompressedImageSaver>",
settings: (
loader_settings: (
format: FromExtension,
is_srgb: false,
sampler: Default,
asset_usage: ("RENDER_WORLD"),
texture_format: Some("r8unorm"),
),
transformer_settings: (),
saver_settings: (),
),
),
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions assets/textures/GroundSand005/GroundSand005_NRM_2K.jpg.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
(
meta_format_version: "1.0",
asset: Process(
processor: "LoadTransformAndSave<ImageLoader, IdentityAssetTransformer<Image>, CompressedImageSaver>",
settings: (
loader_settings: (
format: FromExtension,
is_srgb: false,
sampler: Default,
asset_usage: ("RENDER_WORLD"),
),
transformer_settings: (),
saver_settings: (),
),
),
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions assets/textures/GroundSand005/GroundSand005_ORM_2K.png.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
(
meta_format_version: "1.0",
asset: Process(
processor: "LoadTransformAndSave<ImageLoader, IdentityAssetTransformer<Image>, CompressedImageSaver>",
settings: (
loader_settings: (
format: FromExtension,
is_srgb: false,
sampler: Default,
asset_usage: ("RENDER_WORLD"),
),
transformer_settings: (),
saver_settings: (),
),
),
)
1 change: 1 addition & 0 deletions assets/textures/GroundSand005/source.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://www.poliigon.com/texture/rippled-wet-sand-texture/6997
80 changes: 78 additions & 2 deletions crates/bevy_asset/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
use alloc::sync::Arc;
use bevy_ecs::template::{FromTemplate, SpecializeFromTemplate, Template, TemplateContext};
use bevy_platform::{collections::Equivalent, sync::Mutex};
use bevy_reflect::{Reflect, TypePath};
use bevy_reflect::{enums::Enum, FromReflect, PartialReflect, Reflect, ReflectRef, TypePath};
use core::{
any::TypeId,
hash::{Hash, Hasher},
Expand Down Expand Up @@ -130,7 +130,7 @@ impl core::fmt::Debug for StrongHandle {
///
/// [`Handle::Strong`], via [`StrongHandle`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists).
#[derive(Reflect)]
#[reflect(Debug, Hash, PartialEq, Clone, Handle)]
#[reflect(Debug, Hash, PartialEq, Clone, Handle, from_reflect = false)]
pub enum Handle<A: Asset> {
/// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
/// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
Expand All @@ -140,6 +140,43 @@ pub enum Handle<A: Asset> {
Uuid(Uuid, #[reflect(ignore, clone)] PhantomData<fn() -> A>),
}

// `Handle` needs a custom `FromReflect` to do extra type checking - see the
// `strong_handle.type_id` check below.
// `Handle` needs a custom `FromReflect` to do extra type checking - see the
// `strong_handle.type_id` check below.
impl<A: Asset> FromReflect for Handle<A>
where
Handle<A>: Send + Sync,
A: TypePath,
{
fn from_reflect(reflect_value: &dyn PartialReflect) -> Option<Self> {
let ReflectRef::Enum(enum_value) = PartialReflect::reflect_ref(reflect_value) else {
return None;
};

match Enum::variant_name(enum_value) {
"Strong" => {
let strong_field = enum_value.field_at(0usize)?;
let strong_handle = Arc::<StrongHandle>::from_reflect(strong_field)?;

// This is necessary as otherwise you could construct Handle<A> via Handle<B>
if strong_handle.type_id != TypeId::of::<A>() {
return None;
}

Some(Handle::Strong(strong_handle))
}
"Uuid" => {
let uuid_field = enum_value.field_at(0usize)?;
let uuid = Uuid::from_reflect(uuid_field)?;

Some(Handle::Uuid(uuid, Default::default()))
}
_ => None,
}
}
}

impl<T: Asset> Clone for Handle<T> {
fn clone(&self) -> Self {
match self {
Expand Down Expand Up @@ -864,4 +901,43 @@ mod tests {
_ => panic!("Expected a strong handle"),
}
}

#[test]
fn handle_from_reflect_verifies_type_id() {
use crate::{AssetApp, Assets};
use bevy_reflect::FromReflect;

#[derive(Reflect, Asset)]
struct A;
#[derive(Reflect, Asset)]
struct B;

let mut app = create_app().0;
app.init_asset::<A>().init_asset::<B>();

let mut assets = app.world_mut().resource_mut::<Assets<A>>();
let handle_a = assets.add(A);

let dynamic_handle_a = handle_a.to_dynamic();
let reflected_handle_a = handle_a.as_partial_reflect();

let handle_b_from_reflect_dynamic: Option<Handle<B>> =
FromReflect::from_reflect(&*dynamic_handle_a);
let handle_b_from_reflect: Option<Handle<B>> =
FromReflect::from_reflect(reflected_handle_a);
let handle_a_from_reflect: Option<Handle<A>> =
FromReflect::from_reflect(reflected_handle_a);
assert!(
handle_b_from_reflect.is_none(),
"Handle<B> should not be constructible from reflected Handle<A>"
);
assert!(
handle_b_from_reflect_dynamic.is_none(),
"Handle<B> should not be constructible from dynamic Handle<A>"
);
assert!(
handle_a_from_reflect.is_some(),
"Handle<A> should be constructible from reflected Handle<A>"
);
}
}
20 changes: 17 additions & 3 deletions crates/bevy_ecs/src/entity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ use crate::{
storage::{SparseSetIndex, TableId, TableRow},
};
use alloc::vec::Vec;
use core::{fmt, hash::Hash, mem, num::NonZero, panic::Location};
use core::{fmt, hash::Hash, mem, num::NonZero, ops::Range, panic::Location};
use derive_more::derive::Display;
use log::warn;
use nonmax::NonMaxU32;
Expand Down Expand Up @@ -708,9 +708,17 @@ pub struct EntityAllocator {
}

impl EntityAllocator {
/// Creates a new `EntityAllocator` with a given range
pub fn new(range: Range<u32>) -> Self {
Self {
inner: remote_allocator::Allocator::new(range),
}
}

/// Restarts the allocator.
pub(crate) fn restart(&mut self) {
self.inner = remote_allocator::Allocator::new();
let range = self.inner.range().clone();
self.inner = remote_allocator::Allocator::new(range);
}

/// Builds a new remote allocator that hooks into this [`EntityAllocator`].
Expand Down Expand Up @@ -779,7 +787,13 @@ impl EntityAllocator {
/// More generally, manually spawning and [`despawn_no_free`](crate::world::World::despawn_no_free)ing entities allows you to skip Bevy's default entity allocator.
/// This is useful if you want to enforce properties about the [`EntityIndex`]s of a group of entities, make a custom allocator, etc.
pub fn alloc(&self) -> Entity {
self.inner.alloc()
self.inner.try_alloc().expect("out of entities")
}

/// Allocates some [`Entity`].
/// Returns `None` if no entities are available. This is a non-`panic`ing version of `alloc`.
pub fn try_alloc(&self) -> Option<Entity> {
self.inner.try_alloc()
}

/// A more efficient way of calling [`alloc`](Self::alloc) repeatedly `count` times.
Expand Down
Loading