diff --git a/CREDITS.md b/CREDITS.md index d2ac81cd30..da4bccd579 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -14,4 +14,5 @@ ## Assets -* Generic RPG Pack (CC0 license) by [Bakudas](https://twitter.com/bakudas) and [Gabe Fern](https://twitter.com/_Gabrielfer) \ No newline at end of file +* Generic RPG Pack (CC0 license) by [Bakudas](https://twitter.com/bakudas) and [Gabe Fern](https://twitter.com/_Gabrielfer) +* Environment maps (`.hdr` files) from [HDRIHaven](https://hdrihaven.com) (CC0 license) \ No newline at end of file diff --git a/assets/textures/spiaggia_di_mondello_1k.hdr b/assets/textures/spiaggia_di_mondello_1k.hdr new file mode 100644 index 0000000000..694306a88f Binary files /dev/null and b/assets/textures/spiaggia_di_mondello_1k.hdr differ diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 6a8d401c9e..2d28a67e91 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -20,8 +20,7 @@ bevy_window = { path = "../bevy_window" } # rendering spirv-reflect = "0.2.3" glsl-to-spirv = { git = "https://github.com/cart/glsl-to-spirv" } -# TODO: move this to its own crate -png = "0.16.0" +image = { version = "0.23", default-features = false, features = ["png", "hdr"] } # misc log = { version = "0.4", features = ["release_max_level_info"] } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 0a0924a4bc..053bfe05eb 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -38,7 +38,7 @@ use render_graph::{ }; use renderer::{AssetRenderResourceBindings, RenderResourceBindings}; use std::ops::Range; -use texture::{PngTextureLoader, TextureResourceSystemState}; +use texture::{HdrTextureLoader, ImageTextureLoader, TextureResourceSystemState}; pub mod stage { /// Stage where render resources are set up @@ -75,7 +75,8 @@ impl AppPlugin for RenderPlugin { .add_asset::() .add_asset::() .add_asset::() - .add_asset_loader::() + .add_asset_loader::() + .add_asset_loader::() .register_component::() .register_component::() .register_component::() diff --git a/crates/bevy_render/src/texture/hdr_texture_loader.rs b/crates/bevy_render/src/texture/hdr_texture_loader.rs new file mode 100644 index 0000000000..160d74a79e --- /dev/null +++ b/crates/bevy_render/src/texture/hdr_texture_loader.rs @@ -0,0 +1,43 @@ +use super::{Texture, TextureFormat}; +use anyhow::Result; +use bevy_asset::AssetLoader; +use bevy_math::Vec2; +use std::path::Path; + +#[derive(Clone, Default)] +pub struct HdrTextureLoader; + +impl AssetLoader for HdrTextureLoader { + fn from_bytes(&self, _asset_path: &Path, bytes: Vec) -> Result { + let format = TextureFormat::Rgba32Float; + debug_assert_eq!( + format.pixel_size(), + 4 * 4 * 1, + "Format should have 32bit x 4 size" + ); + + let decoder = image::hdr::HdrDecoder::new(bytes.as_slice())?; + let info = decoder.metadata(); + let rgb_data = decoder.read_image_hdr()?; + let mut rgba_data = Vec::with_capacity(rgb_data.len() * format.pixel_size()); + + for rgb in rgb_data { + let alpha = 1.0f32; + + rgba_data.extend_from_slice(&rgb.0[0].to_ne_bytes()); + rgba_data.extend_from_slice(&rgb.0[1].to_ne_bytes()); + rgba_data.extend_from_slice(&rgb.0[2].to_ne_bytes()); + rgba_data.extend_from_slice(&alpha.to_ne_bytes()); + } + + Ok(Texture::new( + Vec2::new(info.width as f32, info.height as f32), + rgba_data, + format, + )) + } + fn extensions(&self) -> &[&str] { + static EXTENSIONS: &[&str] = &["hdr"]; + EXTENSIONS + } +} diff --git a/crates/bevy_render/src/texture/image_texture_loader.rs b/crates/bevy_render/src/texture/image_texture_loader.rs new file mode 100644 index 0000000000..0937a3741d --- /dev/null +++ b/crates/bevy_render/src/texture/image_texture_loader.rs @@ -0,0 +1,155 @@ +use super::{Texture, TextureFormat}; +use anyhow::Result; +use bevy_asset::AssetLoader; +use bevy_math::Vec2; +use std::path::Path; + +/// Loader for images that can be read by the `image` crate. +/// +/// Reads only PNG images for now. +#[derive(Clone, Default)] +pub struct ImageTextureLoader; + +impl AssetLoader for ImageTextureLoader { + fn from_bytes(&self, asset_path: &Path, bytes: Vec) -> Result { + use bevy_core::AsBytes; + + // Find the image type we expect. A file with the extension "png" should + // probably load as a PNG. + + let ext = asset_path.extension().unwrap().to_str().unwrap(); + + // NOTE: If more formats are added they can be added here. + let img_format = if ext.eq_ignore_ascii_case("png") { + image::ImageFormat::Png + } else { + panic!( + "Unexpected image format {:?} for file {}, this is an error in `bevy_render`.", + ext, + asset_path.display() + ) + }; + + // Load the image in the expected format. + // Some formats like PNG allow for R or RG textures too, so the texture + // format needs to be determined. For RGB textures an alpha channel + // needs to be added, so the image data needs to be converted in those + // cases. + + let dyn_img = image::load_from_memory_with_format(bytes.as_slice(), img_format)?; + + let width; + let height; + + let data: Vec; + let format: TextureFormat; + + match dyn_img { + image::DynamicImage::ImageLuma8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::R8Unorm; + + data = i.into_raw(); + } + image::DynamicImage::ImageLumaA8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rg8Unorm; + + data = i.into_raw(); + } + image::DynamicImage::ImageRgb8(i) => { + let i = image::DynamicImage::ImageRgb8(i).into_rgba(); + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba8UnormSrgb; + + data = i.into_raw(); + } + image::DynamicImage::ImageRgba8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba8UnormSrgb; + + data = i.into_raw(); + } + image::DynamicImage::ImageBgr8(i) => { + let i = image::DynamicImage::ImageBgr8(i).into_bgra(); + + width = i.width(); + height = i.height(); + format = TextureFormat::Bgra8UnormSrgb; + + data = i.into_raw(); + } + image::DynamicImage::ImageBgra8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Bgra8UnormSrgb; + + data = i.into_raw(); + } + image::DynamicImage::ImageLuma16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::R16Uint; + + let raw_data = i.into_raw(); + + data = raw_data.as_slice().as_bytes().to_owned(); + } + image::DynamicImage::ImageLumaA16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rg16Uint; + + let raw_data = i.into_raw(); + + data = raw_data.as_slice().as_bytes().to_owned(); + } + image::DynamicImage::ImageRgb16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba16Uint; + + let mut d = + Vec::with_capacity(width as usize * height as usize * format.pixel_size()); + + for pixel in i.into_raw().chunks_exact(3) { + // TODO unsafe_get in release builds? + let r = pixel[0]; + let g = pixel[1]; + let b = pixel[2]; + let a = u16::max_value(); + + d.extend_from_slice(&r.to_ne_bytes()); + d.extend_from_slice(&g.to_ne_bytes()); + d.extend_from_slice(&b.to_ne_bytes()); + d.extend_from_slice(&a.to_ne_bytes()); + } + + data = d; + } + image::DynamicImage::ImageRgba16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba16Uint; + + let raw_data = i.into_raw(); + + data = raw_data.as_slice().as_bytes().to_owned(); + } + } + + Ok(Texture::new( + Vec2::new(width as f32, height as f32), + data, + format, + )) + } + fn extensions(&self) -> &[&str] { + static EXTENSIONS: &[&str] = &["png"]; + EXTENSIONS + } +} diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index effbb49f16..e6ab2a793e 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -1,10 +1,12 @@ -mod png_texture_loader; +mod hdr_texture_loader; +mod image_texture_loader; mod sampler_descriptor; mod texture; mod texture_descriptor; mod texture_dimension; -pub use png_texture_loader::*; +pub use hdr_texture_loader::*; +pub use image_texture_loader::*; pub use sampler_descriptor::*; pub use texture::*; pub use texture_descriptor::*; diff --git a/crates/bevy_render/src/texture/png_texture_loader.rs b/crates/bevy_render/src/texture/png_texture_loader.rs deleted file mode 100644 index daeb9ec321..0000000000 --- a/crates/bevy_render/src/texture/png_texture_loader.rs +++ /dev/null @@ -1,26 +0,0 @@ -use super::{Texture, TextureFormat}; -use anyhow::Result; -use bevy_asset::AssetLoader; -use bevy_math::Vec2; -use std::path::Path; - -#[derive(Clone, Default)] -pub struct PngTextureLoader; - -impl AssetLoader for PngTextureLoader { - fn from_bytes(&self, _asset_path: &Path, bytes: Vec) -> Result { - let decoder = png::Decoder::new(bytes.as_slice()); - let (info, mut reader) = decoder.read_info()?; - let mut data = vec![0; info.buffer_size()]; - reader.next_frame(&mut data)?; - Ok(Texture::new( - Vec2::new(info.width as f32, info.height as f32), - data, - TextureFormat::Rgba8UnormSrgb, - )) - } - fn extensions(&self) -> &[&str] { - static EXTENSIONS: &[&str] = &["png"]; - EXTENSIONS - } -} diff --git a/crates/bevy_render/src/texture/texture.rs b/crates/bevy_render/src/texture/texture.rs index 546ceaf757..6a4ffbe2c1 100644 --- a/crates/bevy_render/src/texture/texture.rs +++ b/crates/bevy_render/src/texture/texture.rs @@ -67,7 +67,8 @@ impl Texture { self.size = size; let width = size.x() as usize; let height = size.y() as usize; - self.data.resize(width * height * self.format.pixel_size(), 0); + self.data + .resize(width * height * self.format.pixel_size(), 0); } pub fn texture_resource_system(