Optional explicit compressed image format support (#19190)

# Objective

- Allow compressed image formats to be used with `ImagePlugin` and
`GltfPlugin` in cases where there is no `RenderDevice` resource. (For
example, when using a custom render backend)

## Solution

- Define a `CompressedImageFormatSupport` component that allows the user
to explicitly determine which formats are supported.

~~Not sure if this is the best solution. Alternatively, I considered
initializing CompressedImageFormatSupport from render device features
separately, it would need to run after the render device is initialized
but before `ImagePlugin` and `GltfPlugin` finish. Not sure where the
best place for that to happen would be.~~

Update: decided on going with @greeble-dev solution: defining the
`CompressedImageFormatSupport` resource in `bevy_image`, but letting
`bevy_render` register the resource value.
This commit is contained in:
Griffin 2025-05-26 11:00:33 -07:00 committed by GitHub
parent 5117e6fd49
commit d79efada3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 40 additions and 12 deletions

View File

@ -99,15 +99,15 @@ extern crate alloc;
use alloc::sync::Arc; use alloc::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use tracing::warn;
use bevy_platform::collections::HashMap; use bevy_platform::collections::HashMap;
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_asset::AssetApp; use bevy_asset::AssetApp;
use bevy_ecs::prelude::Resource; use bevy_ecs::prelude::Resource;
use bevy_image::{CompressedImageFormats, ImageSamplerDescriptor}; use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats, ImageSamplerDescriptor};
use bevy_mesh::MeshVertexAttribute; use bevy_mesh::MeshVertexAttribute;
use bevy_render::renderer::RenderDevice;
/// The glTF prelude. /// The glTF prelude.
/// ///
@ -205,10 +205,16 @@ impl Plugin for GltfPlugin {
} }
fn finish(&self, app: &mut App) { fn finish(&self, app: &mut App) {
let supported_compressed_formats = match app.world().get_resource::<RenderDevice>() { let supported_compressed_formats = if let Some(resource) =
Some(render_device) => CompressedImageFormats::from_features(render_device.features()), app.world().get_resource::<CompressedImageFormatSupport>()
None => CompressedImageFormats::NONE, {
resource.0
} else {
warn!("CompressedImageFormatSupport resource not found. It should either be initialized in finish() of \
RenderPlugin, or manually if not using the RenderPlugin or the WGPU backend.");
CompressedImageFormats::NONE
}; };
let default_sampler_resource = DefaultGltfImageSampler::new(&self.default_sampler); let default_sampler_resource = DefaultGltfImageSampler::new(&self.default_sampler);
let default_sampler = default_sampler_resource.get_internal(); let default_sampler = default_sampler_resource.get_internal();
app.insert_resource(default_sampler_resource); app.insert_resource(default_sampler_resource);

View File

@ -46,6 +46,7 @@ bevy_color = { path = "../bevy_color", version = "0.16.0-dev", features = [
"serialize", "serialize",
"wgpu-types", "wgpu-types",
] } ] }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" } bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }

View File

@ -11,6 +11,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_asset::{Asset, RenderAssetUsages}; use bevy_asset::{Asset, RenderAssetUsages};
use bevy_color::{Color, ColorToComponents, Gray, LinearRgba, Srgba, Xyza}; use bevy_color::{Color, ColorToComponents, Gray, LinearRgba, Srgba, Xyza};
use bevy_ecs::resource::Resource;
use bevy_math::{AspectRatio, UVec2, UVec3, Vec2}; use bevy_math::{AspectRatio, UVec2, UVec3, Vec2};
use core::hash::Hash; use core::hash::Hash;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -1648,6 +1649,12 @@ impl CompressedImageFormats {
} }
} }
/// For defining which compressed image formats are supported. This will be initialized from available device features
/// in `finish()` of the bevy `RenderPlugin`, but is left for the user to specify if not using the `RenderPlugin`, or
/// the WGPU backend.
#[derive(Resource)]
pub struct CompressedImageFormatSupport(pub CompressedImageFormats);
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View File

@ -79,6 +79,7 @@ pub mod _macro {
} }
use bevy_ecs::schedule::ScheduleBuildSettings; use bevy_ecs::schedule::ScheduleBuildSettings;
use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats};
use bevy_utils::prelude::default; use bevy_utils::prelude::default;
pub use extract_param::Extract; pub use extract_param::Extract;
@ -473,10 +474,15 @@ impl Plugin for RenderPlugin {
let RenderResources(device, queue, adapter_info, render_adapter, instance) = let RenderResources(device, queue, adapter_info, render_adapter, instance) =
future_render_resources.0.lock().unwrap().take().unwrap(); future_render_resources.0.lock().unwrap().take().unwrap();
let compressed_image_format_support = CompressedImageFormatSupport(
CompressedImageFormats::from_features(device.features()),
);
app.insert_resource(device.clone()) app.insert_resource(device.clone())
.insert_resource(queue.clone()) .insert_resource(queue.clone())
.insert_resource(adapter_info.clone()) .insert_resource(adapter_info.clone())
.insert_resource(render_adapter.clone()); .insert_resource(render_adapter.clone())
.insert_resource(compressed_image_format_support);
let render_app = app.sub_app_mut(RenderApp); let render_app = app.sub_app_mut(RenderApp);

View File

@ -8,7 +8,10 @@ pub use crate::render_resource::DefaultImageSampler;
use bevy_image::CompressedImageSaver; use bevy_image::CompressedImageSaver;
#[cfg(feature = "hdr")] #[cfg(feature = "hdr")]
use bevy_image::HdrTextureLoader; use bevy_image::HdrTextureLoader;
use bevy_image::{CompressedImageFormats, Image, ImageLoader, ImageSamplerDescriptor}; use bevy_image::{
CompressedImageFormatSupport, CompressedImageFormats, Image, ImageLoader,
ImageSamplerDescriptor,
};
pub use fallback_image::*; pub use fallback_image::*;
pub use gpu_image::*; pub use gpu_image::*;
pub use texture_attachment::*; pub use texture_attachment::*;
@ -20,6 +23,7 @@ use crate::{
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{weak_handle, AssetApp, Assets, Handle}; use bevy_asset::{weak_handle, AssetApp, Assets, Handle};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use tracing::warn;
/// A handle to a 1 x 1 transparent white image. /// A handle to a 1 x 1 transparent white image.
/// ///
@ -111,12 +115,16 @@ impl Plugin for ImagePlugin {
fn finish(&self, app: &mut App) { fn finish(&self, app: &mut App) {
if !ImageLoader::SUPPORTED_FORMATS.is_empty() { if !ImageLoader::SUPPORTED_FORMATS.is_empty() {
let supported_compressed_formats = match app.world().get_resource::<RenderDevice>() { let supported_compressed_formats = if let Some(resource) =
Some(render_device) => { app.world().get_resource::<CompressedImageFormatSupport>()
CompressedImageFormats::from_features(render_device.features()) {
} resource.0
None => CompressedImageFormats::NONE, } else {
warn!("CompressedImageFormatSupport resource not found. It should either be initialized in finish() of \
RenderPlugin, or manually if not using the RenderPlugin or the WGPU backend.");
CompressedImageFormats::NONE
}; };
app.register_asset_loader(ImageLoader::new(supported_compressed_formats)); app.register_asset_loader(ImageLoader::new(supported_compressed_formats));
} }