Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix crash when loading textures from gltf. #7005

Closed
wants to merge 4 commits into from
Closed
Changes from all 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
148 changes: 21 additions & 127 deletions crates/bevy_render/src/texture/image_texture_conversion.rs
Original file line number Diff line number Diff line change
@@ -1,156 +1,50 @@
use crate::texture::{Image, TextureFormatPixelInfo};
use crate::texture::Image;
use anyhow::anyhow;
use image::{DynamicImage, ImageBuffer};
use image::{buffer::ConvertBuffer, DynamicImage, ImageBuffer, Luma, Rgba};
use wgpu::{Extent3d, TextureDimension, TextureFormat};

impl Image {
/// Converts a [`DynamicImage`] to an [`Image`].
pub fn from_dynamic(dyn_img: DynamicImage, is_srgb: bool) -> Image {
use bevy_core::cast_slice;
let width;
let height;
let width = dyn_img.width();
let height = dyn_img.height();

let data: Vec<u8>;
let format: TextureFormat;

match dyn_img {
DynamicImage::ImageLuma8(i) => {
let i = DynamicImage::ImageLuma8(i).into_rgba8();
width = i.width();
height = i.height();
format = if is_srgb {
TextureFormat::Rgba8UnormSrgb
} else {
TextureFormat::Rgba8Unorm
};

format = TextureFormat::R8Unorm;
data = i.into_raw();
}
DynamicImage::ImageLumaA8(i) => {
let i = DynamicImage::ImageLumaA8(i).into_rgba8();
width = i.width();
height = i.height();
format = if is_srgb {
TextureFormat::Rgba8UnormSrgb
} else {
TextureFormat::Rgba8Unorm
};

data = i.into_raw();
format = TextureFormat::R8Unorm;
data = ConvertBuffer::<ImageBuffer<Luma<u8>, Vec<u8>>>::convert(&i).into_raw();
}
DynamicImage::ImageRgb8(i) => {
let i = DynamicImage::ImageRgb8(i).into_rgba8();
width = i.width();
height = i.height();
format = if is_srgb {
TextureFormat::Rgba8UnormSrgb
if is_srgb {
format = TextureFormat::Rgba8UnormSrgb;
} else {
TextureFormat::Rgba8Unorm
};

data = i.into_raw();
format = TextureFormat::Rgba8Unorm;
}
data = ConvertBuffer::<ImageBuffer<Rgba<u8>, Vec<u8>>>::convert(&i).into_raw();
}
DynamicImage::ImageRgba8(i) => {
width = i.width();
height = i.height();
format = if is_srgb {
TextureFormat::Rgba8UnormSrgb
if is_srgb {
format = TextureFormat::Rgba8UnormSrgb;
} else {
TextureFormat::Rgba8Unorm
};

data = i.into_raw();
}
DynamicImage::ImageLuma16(i) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain why you removed support for the following image formats: Luma16, LumaA16, Rgb16, Rgba16, and 32F variants?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for not providing enough context and information at the first time.

I'm creating a 3D asset management tool based on bevy, it should be able to load and preview gltf 2.0 model at runtime, so the stability and compatibility comes to first priority.

I've tested the compatibility with all kinds of gltf, including all models from (https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0) and models I collected which will cause the crash issue #7005, and current code will provide maximum stability and compatibility, means all the textures could be loaded and rendered without any problem.

I think, we could build a solid ground without any crash at first, and extend the compatibility case-by-case in future, rather than support as much as possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about custom shaders? Bevy users might want to load any image formats for their own shaders. Even if it doesn't work with the default PBR shader

width = i.width();
height = i.height();
format = TextureFormat::R16Uint;

let raw_data = i.into_raw();

data = cast_slice(&raw_data).to_owned();
}
DynamicImage::ImageLumaA16(i) => {
width = i.width();
height = i.height();
format = TextureFormat::Rg16Uint;

let raw_data = i.into_raw();

data = cast_slice(&raw_data).to_owned();
}
DynamicImage::ImageRgb16(image) => {
width = image.width();
height = image.height();
format = TextureFormat::Rgba16Uint;

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];
let a = u16::max_value();

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;
}
DynamicImage::ImageRgba16(i) => {
width = i.width();
height = i.height();
format = TextureFormat::Rgba16Uint;

let raw_data = i.into_raw();

data = cast_slice(&raw_data).to_owned();
}
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];
let a = 1f32;

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());
format = TextureFormat::Rgba8Unorm;
}

data = local_data;
}
DynamicImage::ImageRgba32F(image) => {
width = image.width();
height = image.height();
format = TextureFormat::Rgba32Float;

let raw_data = image.into_raw();

data = cast_slice(&raw_data).to_owned();
data = i.into_raw();
}
// DynamicImage is now non exhaustive, catch future variants and convert them
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove this line? This is still true as far as I am aware.

_ => {
let image = dyn_img.into_rgba8();
width = image.width();
height = image.height();
format = TextureFormat::Rgba8UnormSrgb;
if is_srgb {
format = TextureFormat::Rgba8UnormSrgb;
} else {
format = TextureFormat::Rgba8Unorm;
}

data = image.into_raw();
}
Expand Down