2024-10-04 20:16:47 +00:00
|
|
|
use crate::{Image, TextureFormatPixelInfo};
|
|
|
|
use bevy_asset::RenderAssetUsages;
|
2022-01-11 01:08:39 +00:00
|
|
|
use image::{DynamicImage, ImageBuffer};
|
Removed `anyhow` (#10003)
# Objective
- Fixes #8140
## Solution
- Added Explicit Error Typing for `AssetLoader` and `AssetSaver`, which
were the last instances of `anyhow` in use across Bevy.
---
## Changelog
- Added an associated type `Error` to `AssetLoader` and `AssetSaver` for
use with the `load` and `save` methods respectively.
- Changed `ErasedAssetLoader` and `ErasedAssetSaver` `load` and `save`
methods to use `Box<dyn Error + Send + Sync + 'static>` to allow for
arbitrary `Error` types from the non-erased trait variants. Note the
strict requirements match the pre-existing requirements around
`anyhow::Error`.
## Migration Guide
- `anyhow` is no longer exported by `bevy_asset`; Add it to your own
project (if required).
- `AssetLoader` and `AssetSaver` have an associated type `Error`; Define
an appropriate error type (e.g., using `thiserror`), or use a pre-made
error type (e.g., `anyhow::Error`). Note that using `anyhow::Error` is a
drop-in replacement.
- `AssetLoaderError` has been removed; Define a new error type, or use
an alternative (e.g., `anyhow::Error`)
- All the first-party `AssetLoader`'s and `AssetSaver`'s now return
relevant (and narrow) error types instead of a single ambiguous type;
Match over the specific error type, or encapsulate (`Box<dyn>`,
`thiserror`, `anyhow`, etc.)
## Notes
A simpler PR to resolve this issue would simply define a Bevy `Error`
type defined as `Box<dyn std::error::Error + Send + Sync + 'static>`,
but I think this type of error handling should be discouraged when
possible. Since only 2 traits required the use of `anyhow`, it isn't a
substantive body of work to solidify these error types, and remove
`anyhow` entirely. End users are still encouraged to use `anyhow` if
that is their preferred error handling style. Arguably, adding the
`Error` associated type gives more freedom to end-users to decide
whether they want more or less explicit error handling (`anyhow` vs
`thiserror`).
As an aside, I didn't perform any testing on Android or WASM. CI passed
locally, but there may be mistakes for those platforms I missed.
2023-10-06 07:20:13 +00:00
|
|
|
use thiserror::Error;
|
2021-12-14 03:58:23 +00:00
|
|
|
use wgpu::{Extent3d, TextureDimension, TextureFormat};
|
|
|
|
|
2022-09-03 17:47:38 +00:00
|
|
|
impl Image {
|
|
|
|
/// Converts a [`DynamicImage`] to an [`Image`].
|
2024-01-03 03:31:04 +00:00
|
|
|
pub fn from_dynamic(
|
|
|
|
dyn_img: DynamicImage,
|
|
|
|
is_srgb: bool,
|
RenderAssetPersistencePolicy → RenderAssetUsages (#11399)
# Objective
Right now, all assets in the main world get extracted and prepared in
the render world (if the asset's using the RenderAssetPlugin). This is
unfortunate for two cases:
1. **TextureAtlas** / **FontAtlas**: This one's huge. The individual
`Image` assets that make up the atlas are cloned and prepared
individually when there's no reason for them to be. The atlas textures
are built on the CPU in the main world. *There can be hundreds of images
that get prepared for rendering only not to be used.*
2. If one loads an Image and needs to transform it in a system before
rendering it, kind of like the [decompression
example](https://github.com/bevyengine/bevy/blob/main/examples/asset/asset_decompression.rs#L120),
there's a price paid for extracting & preparing the asset that's not
intended to be rendered yet.
------
* References #10520
* References #1782
## Solution
This changes the `RenderAssetPersistencePolicy` enum to bitflags. I felt
that the objective with the parameter is so similar in nature to wgpu's
[`TextureUsages`](https://docs.rs/wgpu/latest/wgpu/struct.TextureUsages.html)
and
[`BufferUsages`](https://docs.rs/wgpu/latest/wgpu/struct.BufferUsages.html),
that it may as well be just like that.
```rust
// This asset only needs to be in the main world. Don't extract and prepare it.
RenderAssetUsages::MAIN_WORLD
// Keep this asset in the main world and
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD
// This asset is only needed in the render world. Remove it from the asset server once extracted.
RenderAssetUsages::RENDER_WORLD
```
### Alternate Solution
I considered introducing a third field to `RenderAssetPersistencePolicy`
enum:
```rust
enum RenderAssetPersistencePolicy {
/// Keep the asset in the main world after extracting to the render world.
Keep,
/// Remove the asset from the main world after extracting to the render world.
Unload,
/// This doesn't need to be in the render world at all.
NoExtract, // <-----
}
```
Functional, but this seemed like shoehorning. Another option is renaming
the enum to something like:
```rust
enum RenderAssetExtractionPolicy {
/// Extract the asset and keep it in the main world.
Extract,
/// Remove the asset from the main world after extracting to the render world.
ExtractAndUnload,
/// This doesn't need to be in the render world at all.
NoExtract,
}
```
I think this last one could be a good option if the bitflags are too
clunky.
## Migration Guide
* `RenderAssetPersistencePolicy::Keep` → `RenderAssetUsage::MAIN_WORLD |
RenderAssetUsage::RENDER_WORLD` (or `RenderAssetUsage::default()`)
* `RenderAssetPersistencePolicy::Unload` →
`RenderAssetUsage::RENDER_WORLD`
* For types implementing the `RenderAsset` trait, change `fn
persistence_policy(&self) -> RenderAssetPersistencePolicy` to `fn
asset_usage(&self) -> RenderAssetUsages`.
* Change any references to `cpu_persistent_access`
(`RenderAssetPersistencePolicy`) to `asset_usage` (`RenderAssetUsage`).
This applies to `Image`, `Mesh`, and a few other types.
2024-01-30 13:22:10 +00:00
|
|
|
asset_usage: RenderAssetUsages,
|
2024-01-03 03:31:04 +00:00
|
|
|
) -> Image {
|
2024-03-07 02:30:15 +00:00
|
|
|
use bytemuck::cast_slice;
|
2022-09-03 17:47:38 +00:00
|
|
|
let width;
|
|
|
|
let height;
|
|
|
|
|
|
|
|
let data: Vec<u8>;
|
|
|
|
let format: TextureFormat;
|
|
|
|
|
|
|
|
match dyn_img {
|
2023-09-06 05:24:44 +00:00
|
|
|
DynamicImage::ImageLuma8(image) => {
|
|
|
|
let i = DynamicImage::ImageLuma8(image).into_rgba8();
|
2022-09-03 17:47:38 +00:00
|
|
|
width = i.width();
|
|
|
|
height = i.height();
|
|
|
|
format = if is_srgb {
|
|
|
|
TextureFormat::Rgba8UnormSrgb
|
|
|
|
} else {
|
|
|
|
TextureFormat::Rgba8Unorm
|
|
|
|
};
|
|
|
|
|
|
|
|
data = i.into_raw();
|
|
|
|
}
|
2023-09-06 05:24:44 +00:00
|
|
|
DynamicImage::ImageLumaA8(image) => {
|
|
|
|
let i = DynamicImage::ImageLumaA8(image).into_rgba8();
|
2022-09-03 17:47:38 +00:00
|
|
|
width = i.width();
|
|
|
|
height = i.height();
|
|
|
|
format = if is_srgb {
|
|
|
|
TextureFormat::Rgba8UnormSrgb
|
|
|
|
} else {
|
|
|
|
TextureFormat::Rgba8Unorm
|
|
|
|
};
|
|
|
|
|
|
|
|
data = i.into_raw();
|
|
|
|
}
|
2023-09-06 05:24:44 +00:00
|
|
|
DynamicImage::ImageRgb8(image) => {
|
|
|
|
let i = DynamicImage::ImageRgb8(image).into_rgba8();
|
2022-09-03 17:47:38 +00:00
|
|
|
width = i.width();
|
|
|
|
height = i.height();
|
|
|
|
format = if is_srgb {
|
|
|
|
TextureFormat::Rgba8UnormSrgb
|
|
|
|
} else {
|
|
|
|
TextureFormat::Rgba8Unorm
|
|
|
|
};
|
|
|
|
|
|
|
|
data = i.into_raw();
|
|
|
|
}
|
2023-09-06 05:24:44 +00:00
|
|
|
DynamicImage::ImageRgba8(image) => {
|
|
|
|
width = image.width();
|
|
|
|
height = image.height();
|
2022-09-03 17:47:38 +00:00
|
|
|
format = if is_srgb {
|
|
|
|
TextureFormat::Rgba8UnormSrgb
|
|
|
|
} else {
|
|
|
|
TextureFormat::Rgba8Unorm
|
|
|
|
};
|
|
|
|
|
2023-09-06 05:24:44 +00:00
|
|
|
data = image.into_raw();
|
2022-09-03 17:47:38 +00:00
|
|
|
}
|
2023-09-06 05:24:44 +00:00
|
|
|
DynamicImage::ImageLuma16(image) => {
|
|
|
|
width = image.width();
|
|
|
|
height = image.height();
|
2022-09-03 17:47:38 +00:00
|
|
|
format = TextureFormat::R16Uint;
|
2021-02-02 21:25:16 +00:00
|
|
|
|
2023-09-06 05:24:44 +00:00
|
|
|
let raw_data = image.into_raw();
|
2021-02-02 21:25:16 +00:00
|
|
|
|
2022-09-03 17:47:38 +00:00
|
|
|
data = cast_slice(&raw_data).to_owned();
|
|
|
|
}
|
2023-09-06 05:24:44 +00:00
|
|
|
DynamicImage::ImageLumaA16(image) => {
|
|
|
|
width = image.width();
|
|
|
|
height = image.height();
|
2022-09-03 17:47:38 +00:00
|
|
|
format = TextureFormat::Rg16Uint;
|
2021-02-02 21:25:16 +00:00
|
|
|
|
2023-09-06 05:24:44 +00:00
|
|
|
let raw_data = image.into_raw();
|
2021-02-02 21:25:16 +00:00
|
|
|
|
2022-09-03 17:47:38 +00:00
|
|
|
data = cast_slice(&raw_data).to_owned();
|
2021-06-08 02:26:51 +00:00
|
|
|
}
|
2022-09-03 17:47:38 +00:00
|
|
|
DynamicImage::ImageRgb16(image) => {
|
2023-09-06 05:24:44 +00:00
|
|
|
let i = DynamicImage::ImageRgb16(image).into_rgba16();
|
2022-09-03 17:47:38 +00:00
|
|
|
width = i.width();
|
|
|
|
height = i.height();
|
2023-09-06 05:24:44 +00:00
|
|
|
format = TextureFormat::Rgba16Unorm;
|
2021-02-02 21:25:16 +00:00
|
|
|
|
2022-09-03 17:47:38 +00:00
|
|
|
let raw_data = i.into_raw();
|
2021-06-08 02:26:51 +00:00
|
|
|
|
2022-09-03 17:47:38 +00:00
|
|
|
data = cast_slice(&raw_data).to_owned();
|
|
|
|
}
|
2023-09-06 05:24:44 +00:00
|
|
|
DynamicImage::ImageRgba16(image) => {
|
|
|
|
width = image.width();
|
|
|
|
height = image.height();
|
|
|
|
format = TextureFormat::Rgba16Unorm;
|
|
|
|
|
|
|
|
let raw_data = image.into_raw();
|
|
|
|
|
|
|
|
data = cast_slice(&raw_data).to_owned();
|
|
|
|
}
|
2022-09-03 17:47:38 +00:00
|
|
|
DynamicImage::ImageRgb32F(image) => {
|
|
|
|
width = image.width();
|
|
|
|
height = image.height();
|
|
|
|
format = TextureFormat::Rgba32Float;
|
|
|
|
|
|
|
|
let mut local_data =
|
|
|
|
Vec::with_capacity(width as usize * height as usize * format.pixel_size());
|
|
|
|
|
|
|
|
for pixel in image.into_raw().chunks_exact(3) {
|
|
|
|
// TODO: use the array_chunks method once stabilised
|
|
|
|
// https://github.com/rust-lang/rust/issues/74985
|
|
|
|
let r = pixel[0];
|
|
|
|
let g = pixel[1];
|
|
|
|
let b = pixel[2];
|
Fix alpha channel in RGB32F image texture format conversion (#6914)
# Objective
The following code:
```rs
use bevy::prelude::Image;
use image::{ DynamicImage, GenericImage, Rgba };
fn main() {
let mut dynamic_image = DynamicImage::new_rgb32f(1, 1);
dynamic_image.put_pixel(0, 0, Rgba([1, 1, 1, 1]));
let image = Image::from_dynamic(dynamic_image, false); // Panic!
println!("{image:?}");
}
```
Can cause an assertion failed:
```
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `16`,
right: `14`: Pixel data, size and format have to match', .../bevy_render-0.9.1/src/texture/image.rs:209:9
stack backtrace:
...
4: core::panicking::assert_failed<usize,usize>
at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:181
5: bevy_render::texture::image::Image::new
at .../bevy_render-0.9.1/src/texture/image.rs:209
6: bevy_render::texture::image::Image::from_dynamic
at .../bevy_render-0.9.1/src/texture/image_texture_conversion.rs:159
7: bevy_test::main
at ./src/main.rs:8
...
```
It seems to be cause by a copypasta in `crates/bevy_render/src/texture/image_texture_conversion.rs`. Let's fix it.
## Solution
```diff
// DynamicImage::ImageRgb32F(image) => {
- let a = u16::max_value();
+ let a = 1f32;
```
This will fix the conversion.
---
## Changelog
- Fixed the alpha channel of the `image::DynamicImage::ImageRgb32F` to `bevy_render::texture::Image` conversion in `bevy_render::texture::Image::from_dynamic()`.
2022-12-11 18:46:47 +00:00
|
|
|
let a = 1f32;
|
2022-09-03 17:47:38 +00:00
|
|
|
|
|
|
|
local_data.extend_from_slice(&r.to_ne_bytes());
|
|
|
|
local_data.extend_from_slice(&g.to_ne_bytes());
|
|
|
|
local_data.extend_from_slice(&b.to_ne_bytes());
|
|
|
|
local_data.extend_from_slice(&a.to_ne_bytes());
|
|
|
|
}
|
|
|
|
|
|
|
|
data = local_data;
|
2022-05-28 02:00:55 +00:00
|
|
|
}
|
2022-09-03 17:47:38 +00:00
|
|
|
DynamicImage::ImageRgba32F(image) => {
|
|
|
|
width = image.width();
|
|
|
|
height = image.height();
|
|
|
|
format = TextureFormat::Rgba32Float;
|
2022-05-28 02:00:55 +00:00
|
|
|
|
2022-09-03 17:47:38 +00:00
|
|
|
let raw_data = image.into_raw();
|
|
|
|
|
|
|
|
data = cast_slice(&raw_data).to_owned();
|
|
|
|
}
|
|
|
|
// DynamicImage is now non exhaustive, catch future variants and convert them
|
|
|
|
_ => {
|
|
|
|
let image = dyn_img.into_rgba8();
|
|
|
|
width = image.width();
|
|
|
|
height = image.height();
|
|
|
|
format = TextureFormat::Rgba8UnormSrgb;
|
|
|
|
|
|
|
|
data = image.into_raw();
|
|
|
|
}
|
2022-05-28 02:00:55 +00:00
|
|
|
}
|
|
|
|
|
2022-09-03 17:47:38 +00:00
|
|
|
Image::new(
|
|
|
|
Extent3d {
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
depth_or_array_layers: 1,
|
|
|
|
},
|
|
|
|
TextureDimension::D2,
|
|
|
|
data,
|
|
|
|
format,
|
RenderAssetPersistencePolicy → RenderAssetUsages (#11399)
# Objective
Right now, all assets in the main world get extracted and prepared in
the render world (if the asset's using the RenderAssetPlugin). This is
unfortunate for two cases:
1. **TextureAtlas** / **FontAtlas**: This one's huge. The individual
`Image` assets that make up the atlas are cloned and prepared
individually when there's no reason for them to be. The atlas textures
are built on the CPU in the main world. *There can be hundreds of images
that get prepared for rendering only not to be used.*
2. If one loads an Image and needs to transform it in a system before
rendering it, kind of like the [decompression
example](https://github.com/bevyengine/bevy/blob/main/examples/asset/asset_decompression.rs#L120),
there's a price paid for extracting & preparing the asset that's not
intended to be rendered yet.
------
* References #10520
* References #1782
## Solution
This changes the `RenderAssetPersistencePolicy` enum to bitflags. I felt
that the objective with the parameter is so similar in nature to wgpu's
[`TextureUsages`](https://docs.rs/wgpu/latest/wgpu/struct.TextureUsages.html)
and
[`BufferUsages`](https://docs.rs/wgpu/latest/wgpu/struct.BufferUsages.html),
that it may as well be just like that.
```rust
// This asset only needs to be in the main world. Don't extract and prepare it.
RenderAssetUsages::MAIN_WORLD
// Keep this asset in the main world and
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD
// This asset is only needed in the render world. Remove it from the asset server once extracted.
RenderAssetUsages::RENDER_WORLD
```
### Alternate Solution
I considered introducing a third field to `RenderAssetPersistencePolicy`
enum:
```rust
enum RenderAssetPersistencePolicy {
/// Keep the asset in the main world after extracting to the render world.
Keep,
/// Remove the asset from the main world after extracting to the render world.
Unload,
/// This doesn't need to be in the render world at all.
NoExtract, // <-----
}
```
Functional, but this seemed like shoehorning. Another option is renaming
the enum to something like:
```rust
enum RenderAssetExtractionPolicy {
/// Extract the asset and keep it in the main world.
Extract,
/// Remove the asset from the main world after extracting to the render world.
ExtractAndUnload,
/// This doesn't need to be in the render world at all.
NoExtract,
}
```
I think this last one could be a good option if the bitflags are too
clunky.
## Migration Guide
* `RenderAssetPersistencePolicy::Keep` → `RenderAssetUsage::MAIN_WORLD |
RenderAssetUsage::RENDER_WORLD` (or `RenderAssetUsage::default()`)
* `RenderAssetPersistencePolicy::Unload` →
`RenderAssetUsage::RENDER_WORLD`
* For types implementing the `RenderAsset` trait, change `fn
persistence_policy(&self) -> RenderAssetPersistencePolicy` to `fn
asset_usage(&self) -> RenderAssetUsages`.
* Change any references to `cpu_persistent_access`
(`RenderAssetPersistencePolicy`) to `asset_usage` (`RenderAssetUsage`).
This applies to `Image`, `Mesh`, and a few other types.
2024-01-30 13:22:10 +00:00
|
|
|
asset_usage,
|
2022-09-03 17:47:38 +00:00
|
|
|
)
|
|
|
|
}
|
2022-05-28 02:00:55 +00:00
|
|
|
|
2023-02-20 22:56:57 +00:00
|
|
|
/// Convert a [`Image`] to a [`DynamicImage`]. Useful for editing image
|
2022-09-03 17:47:38 +00:00
|
|
|
/// data. Not all [`TextureFormat`] are covered, therefore it will return an
|
|
|
|
/// error if the format is unsupported. Supported formats are:
|
|
|
|
/// - `TextureFormat::R8Unorm`
|
|
|
|
/// - `TextureFormat::Rg8Unorm`
|
|
|
|
/// - `TextureFormat::Rgba8UnormSrgb`
|
2023-04-19 21:28:42 +00:00
|
|
|
/// - `TextureFormat::Bgra8UnormSrgb`
|
2022-09-03 17:47:38 +00:00
|
|
|
///
|
|
|
|
/// To convert [`Image`] to a different format see: [`Image::convert`].
|
Removed `anyhow` (#10003)
# Objective
- Fixes #8140
## Solution
- Added Explicit Error Typing for `AssetLoader` and `AssetSaver`, which
were the last instances of `anyhow` in use across Bevy.
---
## Changelog
- Added an associated type `Error` to `AssetLoader` and `AssetSaver` for
use with the `load` and `save` methods respectively.
- Changed `ErasedAssetLoader` and `ErasedAssetSaver` `load` and `save`
methods to use `Box<dyn Error + Send + Sync + 'static>` to allow for
arbitrary `Error` types from the non-erased trait variants. Note the
strict requirements match the pre-existing requirements around
`anyhow::Error`.
## Migration Guide
- `anyhow` is no longer exported by `bevy_asset`; Add it to your own
project (if required).
- `AssetLoader` and `AssetSaver` have an associated type `Error`; Define
an appropriate error type (e.g., using `thiserror`), or use a pre-made
error type (e.g., `anyhow::Error`). Note that using `anyhow::Error` is a
drop-in replacement.
- `AssetLoaderError` has been removed; Define a new error type, or use
an alternative (e.g., `anyhow::Error`)
- All the first-party `AssetLoader`'s and `AssetSaver`'s now return
relevant (and narrow) error types instead of a single ambiguous type;
Match over the specific error type, or encapsulate (`Box<dyn>`,
`thiserror`, `anyhow`, etc.)
## Notes
A simpler PR to resolve this issue would simply define a Bevy `Error`
type defined as `Box<dyn std::error::Error + Send + Sync + 'static>`,
but I think this type of error handling should be discouraged when
possible. Since only 2 traits required the use of `anyhow`, it isn't a
substantive body of work to solidify these error types, and remove
`anyhow` entirely. End users are still encouraged to use `anyhow` if
that is their preferred error handling style. Arguably, adding the
`Error` associated type gives more freedom to end-users to decide
whether they want more or less explicit error handling (`anyhow` vs
`thiserror`).
As an aside, I didn't perform any testing on Android or WASM. CI passed
locally, but there may be mistakes for those platforms I missed.
2023-10-06 07:20:13 +00:00
|
|
|
pub fn try_into_dynamic(self) -> Result<DynamicImage, IntoDynamicImageError> {
|
2022-09-03 17:47:38 +00:00
|
|
|
match self.texture_descriptor.format {
|
2023-10-23 20:49:02 +00:00
|
|
|
TextureFormat::R8Unorm => ImageBuffer::from_raw(self.width(), self.height(), self.data)
|
|
|
|
.map(DynamicImage::ImageLuma8),
|
|
|
|
TextureFormat::Rg8Unorm => {
|
|
|
|
ImageBuffer::from_raw(self.width(), self.height(), self.data)
|
|
|
|
.map(DynamicImage::ImageLumaA8)
|
|
|
|
}
|
|
|
|
TextureFormat::Rgba8UnormSrgb => {
|
|
|
|
ImageBuffer::from_raw(self.width(), self.height(), self.data)
|
|
|
|
.map(DynamicImage::ImageRgba8)
|
|
|
|
}
|
2023-04-19 21:28:42 +00:00
|
|
|
// This format is commonly used as the format for the swapchain texture
|
|
|
|
// This conversion is added here to support screenshots
|
2023-10-23 20:49:02 +00:00
|
|
|
TextureFormat::Bgra8UnormSrgb | TextureFormat::Bgra8Unorm => {
|
|
|
|
ImageBuffer::from_raw(self.width(), self.height(), {
|
2023-04-19 21:28:42 +00:00
|
|
|
let mut data = self.data;
|
|
|
|
for bgra in data.chunks_exact_mut(4) {
|
|
|
|
bgra.swap(0, 2);
|
|
|
|
}
|
|
|
|
data
|
2023-10-23 20:49:02 +00:00
|
|
|
})
|
|
|
|
.map(DynamicImage::ImageRgba8)
|
|
|
|
}
|
2022-09-03 17:47:38 +00:00
|
|
|
// Throw and error if conversion isn't supported
|
Removed `anyhow` (#10003)
# Objective
- Fixes #8140
## Solution
- Added Explicit Error Typing for `AssetLoader` and `AssetSaver`, which
were the last instances of `anyhow` in use across Bevy.
---
## Changelog
- Added an associated type `Error` to `AssetLoader` and `AssetSaver` for
use with the `load` and `save` methods respectively.
- Changed `ErasedAssetLoader` and `ErasedAssetSaver` `load` and `save`
methods to use `Box<dyn Error + Send + Sync + 'static>` to allow for
arbitrary `Error` types from the non-erased trait variants. Note the
strict requirements match the pre-existing requirements around
`anyhow::Error`.
## Migration Guide
- `anyhow` is no longer exported by `bevy_asset`; Add it to your own
project (if required).
- `AssetLoader` and `AssetSaver` have an associated type `Error`; Define
an appropriate error type (e.g., using `thiserror`), or use a pre-made
error type (e.g., `anyhow::Error`). Note that using `anyhow::Error` is a
drop-in replacement.
- `AssetLoaderError` has been removed; Define a new error type, or use
an alternative (e.g., `anyhow::Error`)
- All the first-party `AssetLoader`'s and `AssetSaver`'s now return
relevant (and narrow) error types instead of a single ambiguous type;
Match over the specific error type, or encapsulate (`Box<dyn>`,
`thiserror`, `anyhow`, etc.)
## Notes
A simpler PR to resolve this issue would simply define a Bevy `Error`
type defined as `Box<dyn std::error::Error + Send + Sync + 'static>`,
but I think this type of error handling should be discouraged when
possible. Since only 2 traits required the use of `anyhow`, it isn't a
substantive body of work to solidify these error types, and remove
`anyhow` entirely. End users are still encouraged to use `anyhow` if
that is their preferred error handling style. Arguably, adding the
`Error` associated type gives more freedom to end-users to decide
whether they want more or less explicit error handling (`anyhow` vs
`thiserror`).
As an aside, I didn't perform any testing on Android or WASM. CI passed
locally, but there may be mistakes for those platforms I missed.
2023-10-06 07:20:13 +00:00
|
|
|
texture_format => return Err(IntoDynamicImageError::UnsupportedFormat(texture_format)),
|
2022-05-28 02:00:55 +00:00
|
|
|
}
|
Removed `anyhow` (#10003)
# Objective
- Fixes #8140
## Solution
- Added Explicit Error Typing for `AssetLoader` and `AssetSaver`, which
were the last instances of `anyhow` in use across Bevy.
---
## Changelog
- Added an associated type `Error` to `AssetLoader` and `AssetSaver` for
use with the `load` and `save` methods respectively.
- Changed `ErasedAssetLoader` and `ErasedAssetSaver` `load` and `save`
methods to use `Box<dyn Error + Send + Sync + 'static>` to allow for
arbitrary `Error` types from the non-erased trait variants. Note the
strict requirements match the pre-existing requirements around
`anyhow::Error`.
## Migration Guide
- `anyhow` is no longer exported by `bevy_asset`; Add it to your own
project (if required).
- `AssetLoader` and `AssetSaver` have an associated type `Error`; Define
an appropriate error type (e.g., using `thiserror`), or use a pre-made
error type (e.g., `anyhow::Error`). Note that using `anyhow::Error` is a
drop-in replacement.
- `AssetLoaderError` has been removed; Define a new error type, or use
an alternative (e.g., `anyhow::Error`)
- All the first-party `AssetLoader`'s and `AssetSaver`'s now return
relevant (and narrow) error types instead of a single ambiguous type;
Match over the specific error type, or encapsulate (`Box<dyn>`,
`thiserror`, `anyhow`, etc.)
## Notes
A simpler PR to resolve this issue would simply define a Bevy `Error`
type defined as `Box<dyn std::error::Error + Send + Sync + 'static>`,
but I think this type of error handling should be discouraged when
possible. Since only 2 traits required the use of `anyhow`, it isn't a
substantive body of work to solidify these error types, and remove
`anyhow` entirely. End users are still encouraged to use `anyhow` if
that is their preferred error handling style. Arguably, adding the
`Error` associated type gives more freedom to end-users to decide
whether they want more or less explicit error handling (`anyhow` vs
`thiserror`).
As an aside, I didn't perform any testing on Android or WASM. CI passed
locally, but there may be mistakes for those platforms I missed.
2023-10-06 07:20:13 +00:00
|
|
|
.ok_or(IntoDynamicImageError::UnknownConversionError(
|
|
|
|
self.texture_descriptor.format,
|
|
|
|
))
|
2021-02-02 21:25:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Removed `anyhow` (#10003)
# Objective
- Fixes #8140
## Solution
- Added Explicit Error Typing for `AssetLoader` and `AssetSaver`, which
were the last instances of `anyhow` in use across Bevy.
---
## Changelog
- Added an associated type `Error` to `AssetLoader` and `AssetSaver` for
use with the `load` and `save` methods respectively.
- Changed `ErasedAssetLoader` and `ErasedAssetSaver` `load` and `save`
methods to use `Box<dyn Error + Send + Sync + 'static>` to allow for
arbitrary `Error` types from the non-erased trait variants. Note the
strict requirements match the pre-existing requirements around
`anyhow::Error`.
## Migration Guide
- `anyhow` is no longer exported by `bevy_asset`; Add it to your own
project (if required).
- `AssetLoader` and `AssetSaver` have an associated type `Error`; Define
an appropriate error type (e.g., using `thiserror`), or use a pre-made
error type (e.g., `anyhow::Error`). Note that using `anyhow::Error` is a
drop-in replacement.
- `AssetLoaderError` has been removed; Define a new error type, or use
an alternative (e.g., `anyhow::Error`)
- All the first-party `AssetLoader`'s and `AssetSaver`'s now return
relevant (and narrow) error types instead of a single ambiguous type;
Match over the specific error type, or encapsulate (`Box<dyn>`,
`thiserror`, `anyhow`, etc.)
## Notes
A simpler PR to resolve this issue would simply define a Bevy `Error`
type defined as `Box<dyn std::error::Error + Send + Sync + 'static>`,
but I think this type of error handling should be discouraged when
possible. Since only 2 traits required the use of `anyhow`, it isn't a
substantive body of work to solidify these error types, and remove
`anyhow` entirely. End users are still encouraged to use `anyhow` if
that is their preferred error handling style. Arguably, adding the
`Error` associated type gives more freedom to end-users to decide
whether they want more or less explicit error handling (`anyhow` vs
`thiserror`).
As an aside, I didn't perform any testing on Android or WASM. CI passed
locally, but there may be mistakes for those platforms I missed.
2023-10-06 07:20:13 +00:00
|
|
|
/// Errors that occur while converting an [`Image`] into a [`DynamicImage`]
|
|
|
|
#[non_exhaustive]
|
|
|
|
#[derive(Error, Debug)]
|
|
|
|
pub enum IntoDynamicImageError {
|
|
|
|
/// Conversion into dynamic image not supported for source format.
|
|
|
|
#[error("Conversion into dynamic image not supported for {0:?}.")]
|
|
|
|
UnsupportedFormat(TextureFormat),
|
|
|
|
|
|
|
|
/// Encountered an unknown error during conversion.
|
|
|
|
#[error("Failed to convert into {0:?}.")]
|
|
|
|
UnknownConversionError(TextureFormat),
|
|
|
|
}
|
|
|
|
|
2022-09-03 17:47:38 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use image::{GenericImage, Rgba};
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn two_way_conversion() {
|
|
|
|
// Check to see if color is preserved through an rgba8 conversion and back.
|
|
|
|
let mut initial = DynamicImage::new_rgba8(1, 1);
|
|
|
|
initial.put_pixel(0, 0, Rgba::from([132, 3, 7, 200]));
|
|
|
|
|
RenderAssetPersistencePolicy → RenderAssetUsages (#11399)
# Objective
Right now, all assets in the main world get extracted and prepared in
the render world (if the asset's using the RenderAssetPlugin). This is
unfortunate for two cases:
1. **TextureAtlas** / **FontAtlas**: This one's huge. The individual
`Image` assets that make up the atlas are cloned and prepared
individually when there's no reason for them to be. The atlas textures
are built on the CPU in the main world. *There can be hundreds of images
that get prepared for rendering only not to be used.*
2. If one loads an Image and needs to transform it in a system before
rendering it, kind of like the [decompression
example](https://github.com/bevyengine/bevy/blob/main/examples/asset/asset_decompression.rs#L120),
there's a price paid for extracting & preparing the asset that's not
intended to be rendered yet.
------
* References #10520
* References #1782
## Solution
This changes the `RenderAssetPersistencePolicy` enum to bitflags. I felt
that the objective with the parameter is so similar in nature to wgpu's
[`TextureUsages`](https://docs.rs/wgpu/latest/wgpu/struct.TextureUsages.html)
and
[`BufferUsages`](https://docs.rs/wgpu/latest/wgpu/struct.BufferUsages.html),
that it may as well be just like that.
```rust
// This asset only needs to be in the main world. Don't extract and prepare it.
RenderAssetUsages::MAIN_WORLD
// Keep this asset in the main world and
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD
// This asset is only needed in the render world. Remove it from the asset server once extracted.
RenderAssetUsages::RENDER_WORLD
```
### Alternate Solution
I considered introducing a third field to `RenderAssetPersistencePolicy`
enum:
```rust
enum RenderAssetPersistencePolicy {
/// Keep the asset in the main world after extracting to the render world.
Keep,
/// Remove the asset from the main world after extracting to the render world.
Unload,
/// This doesn't need to be in the render world at all.
NoExtract, // <-----
}
```
Functional, but this seemed like shoehorning. Another option is renaming
the enum to something like:
```rust
enum RenderAssetExtractionPolicy {
/// Extract the asset and keep it in the main world.
Extract,
/// Remove the asset from the main world after extracting to the render world.
ExtractAndUnload,
/// This doesn't need to be in the render world at all.
NoExtract,
}
```
I think this last one could be a good option if the bitflags are too
clunky.
## Migration Guide
* `RenderAssetPersistencePolicy::Keep` → `RenderAssetUsage::MAIN_WORLD |
RenderAssetUsage::RENDER_WORLD` (or `RenderAssetUsage::default()`)
* `RenderAssetPersistencePolicy::Unload` →
`RenderAssetUsage::RENDER_WORLD`
* For types implementing the `RenderAsset` trait, change `fn
persistence_policy(&self) -> RenderAssetPersistencePolicy` to `fn
asset_usage(&self) -> RenderAssetUsages`.
* Change any references to `cpu_persistent_access`
(`RenderAssetPersistencePolicy`) to `asset_usage` (`RenderAssetUsage`).
This applies to `Image`, `Mesh`, and a few other types.
2024-01-30 13:22:10 +00:00
|
|
|
let image = Image::from_dynamic(initial.clone(), true, RenderAssetUsages::RENDER_WORLD);
|
2022-09-03 17:47:38 +00:00
|
|
|
|
|
|
|
// NOTE: Fails if `is_srbg = false` or the dynamic image is of the type rgb8.
|
|
|
|
assert_eq!(initial, image.try_into_dynamic().unwrap());
|
2021-02-02 21:25:16 +00:00
|
|
|
}
|
|
|
|
}
|