use ddsfile::{Caps2, D3DFormat};
use fast_image_resize as fr;
use fast_image_resize::ResizeOptions;
use fxhash::FxHasher;
use fyrox_core::visitor::error::VisitError;
use fyrox_core::visitor::pod::PodVecView;
use fyrox_core::{
algebra::{Vector2, Vector3},
futures::io::Error,
io::FileError,
num_traits::Bounded,
reflect::prelude::*,
sparse::AtomicIndex,
uuid,
uuid::Uuid,
uuid_provider,
visitor::{Visit, VisitResult, Visitor},
TypeUuidProvider,
};
use fyrox_resource::{
embedded_data_source, io::ResourceIo, manager::BuiltInResource, options::ImportOptions,
untyped::ResourceKind, Resource, ResourceData,
};
use image::{ColorType, DynamicImage, ImageError, ImageFormat, Pixel};
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
use std::{
fmt::{Debug, Display, Formatter},
hash::{Hash, Hasher},
io::Cursor,
ops::{Deref, DerefMut, Shr},
path::Path,
sync::Arc,
};
use strum_macros::{AsRefStr, EnumString, VariantNames};
pub mod loader;
#[derive(Copy, Clone, Debug, Reflect, AsRefStr, EnumString, VariantNames, TypeUuidProvider)]
#[type_uuid(id = "542eb785-875b-43ce-b73a-a25024535f48")]
pub enum TextureKind {
Line {
length: u32,
},
Rectangle {
width: u32,
height: u32,
},
Cube {
size: u32,
},
Volume {
width: u32,
height: u32,
depth: u32,
},
}
impl TextureKind {
#[inline]
pub fn line_length(&self) -> Option<u32> {
if let Self::Line { length } = self {
Some(*length)
} else {
None
}
}
#[inline]
pub fn rectangle_size(&self) -> Option<Vector2<u32>> {
if let Self::Rectangle { width, height } = self {
Some(Vector2::new(*width, *height))
} else {
None
}
}
#[inline]
pub fn cube_size(&self) -> Option<u32> {
if let Self::Cube { size } = self {
Some(*size)
} else {
None
}
}
#[inline]
pub fn volume_size(&self) -> Option<Vector3<u32>> {
if let Self::Volume {
width,
height,
depth,
} = self
{
Some(Vector3::new(*width, *height, *depth))
} else {
None
}
}
}
impl Default for TextureKind {
fn default() -> Self {
Self::Rectangle {
width: 0,
height: 0,
}
}
}
impl Visit for TextureKind {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut region = visitor.enter_region(name)?;
let mut id = match self {
TextureKind::Line { .. } => 0,
TextureKind::Rectangle { .. } => 1,
TextureKind::Cube { .. } => 2,
TextureKind::Volume { .. } => 3,
};
id.visit("Id", &mut region)?;
if region.is_reading() {
*self = match id {
0 => TextureKind::Line { length: 0 },
1 => TextureKind::Rectangle {
width: 0,
height: 0,
},
2 => TextureKind::Cube { size: 0 },
3 => TextureKind::Volume {
width: 0,
height: 0,
depth: 0,
},
_ => {
return VisitResult::Err(VisitError::User(format!(
"Invalid texture kind {id}!"
)))
}
};
}
match self {
TextureKind::Line { length } => {
length.visit("Length", &mut region)?;
}
TextureKind::Rectangle { width, height } => {
width.visit("Width", &mut region)?;
height.visit("Height", &mut region)?;
}
TextureKind::Cube { size } => {
size.visit("Width", &mut region)?;
}
TextureKind::Volume {
width,
height,
depth,
} => {
width.visit("Width", &mut region)?;
height.visit("Height", &mut region)?;
depth.visit("Depth", &mut region)?;
}
}
Ok(())
}
}
#[derive(Default, Clone, Reflect)]
pub struct TextureBytes(Vec<u8>);
impl Visit for TextureBytes {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
self.0.visit(name, visitor)
}
}
impl Debug for TextureBytes {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Texture has {} bytes", self.0.len())
}
}
impl From<Vec<u8>> for TextureBytes {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
impl Deref for TextureBytes {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for TextureBytes {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Debug, Clone, Reflect)]
pub struct Texture {
kind: TextureKind,
bytes: TextureBytes,
pixel_kind: TexturePixelKind,
minification_filter: TextureMinificationFilter,
magnification_filter: TextureMagnificationFilter,
s_wrap_mode: TextureWrapMode,
t_wrap_mode: TextureWrapMode,
r_wrap_mode: TextureWrapMode,
base_level: usize,
max_level: usize,
min_lod: f32,
max_lod: f32,
lod_bias: f32,
mip_count: u32,
anisotropy: f32,
modifications_counter: u64,
sampler_properties_modifications: u64,
is_render_target: bool,
#[doc(hidden)]
#[reflect(hidden)]
pub cache_index: Arc<AtomicIndex>,
}
impl TypeUuidProvider for Texture {
fn type_uuid() -> Uuid {
uuid!("02c23a44-55fa-411a-bc39-eb7a5eadf15c")
}
}
impl ResourceData for Texture {
fn type_uuid(&self) -> Uuid {
<Self as TypeUuidProvider>::type_uuid()
}
fn save(&mut self, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
let color_type = match self.pixel_kind {
TexturePixelKind::R8 => ColorType::L8,
TexturePixelKind::Luminance8 => ColorType::L8,
TexturePixelKind::RGB8 | TexturePixelKind::SRGB8 => ColorType::Rgb8,
TexturePixelKind::RGBA8 | TexturePixelKind::SRGBA8 => ColorType::Rgba8,
TexturePixelKind::RG8 => ColorType::La8,
TexturePixelKind::LuminanceAlpha8 => ColorType::La8,
TexturePixelKind::R16 => ColorType::L16,
TexturePixelKind::Luminance16 => ColorType::L16,
TexturePixelKind::RG16 => ColorType::La16,
TexturePixelKind::LuminanceAlpha16 => ColorType::La16,
TexturePixelKind::RGB16 => ColorType::Rgb16,
TexturePixelKind::RGBA16 => ColorType::Rgba16,
TexturePixelKind::RGB32F => ColorType::Rgb32F,
TexturePixelKind::RGBA32F => ColorType::Rgba32F,
TexturePixelKind::DXT1RGB
| TexturePixelKind::DXT1RGBA
| TexturePixelKind::DXT3RGBA
| TexturePixelKind::DXT5RGBA
| TexturePixelKind::R8RGTC
| TexturePixelKind::RG8RGTC
| TexturePixelKind::BGR8
| TexturePixelKind::BGRA8
| TexturePixelKind::RGB16F
| TexturePixelKind::R32F
| TexturePixelKind::R16F => return Err(Box::new(TextureError::UnsupportedFormat)),
};
if let TextureKind::Rectangle { width, height } = self.kind {
Ok(image::save_buffer(
path,
self.bytes.as_ref(),
width,
height,
color_type,
)?)
} else {
Err(Box::new(TextureError::UnsupportedFormat))
}
}
fn can_be_saved(&self) -> bool {
true
}
fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
Some(Box::new(self.clone()))
}
}
impl Visit for Texture {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut region = visitor.enter_region(name)?;
let mut kind = self.pixel_kind.id();
kind.visit("KindId", &mut region)?;
if region.is_reading() {
self.pixel_kind = TexturePixelKind::new(kind)?;
}
self.minification_filter
.visit("MinificationFilter", &mut region)?;
self.magnification_filter
.visit("MagnificationFilter", &mut region)?;
self.anisotropy.visit("Anisotropy", &mut region)?;
self.s_wrap_mode.visit("SWrapMode", &mut region)?;
self.t_wrap_mode.visit("TWrapMode", &mut region)?;
self.t_wrap_mode.visit("RWrapMode", &mut region)?;
self.mip_count.visit("MipCount", &mut region)?;
self.kind.visit("Kind", &mut region)?;
let mut bytes_view = PodVecView::from_pod_vec(&mut self.bytes);
bytes_view.visit("Data", &mut region)?;
self.base_level.visit("BaseLevel", &mut region)?;
self.max_level.visit("MaxLevel", &mut region)?;
self.min_lod.visit("MinLod", &mut region)?;
self.max_lod.visit("MaxLod", &mut region)?;
self.lod_bias.visit("LodBias", &mut region)?;
Ok(())
}
}
impl Default for Texture {
fn default() -> Self {
Self {
kind: TextureKind::Rectangle {
width: 0,
height: 0,
},
bytes: Default::default(),
pixel_kind: TexturePixelKind::RGBA8,
minification_filter: TextureMinificationFilter::LinearMipMapLinear,
magnification_filter: TextureMagnificationFilter::Linear,
s_wrap_mode: TextureWrapMode::Repeat,
t_wrap_mode: TextureWrapMode::Repeat,
r_wrap_mode: TextureWrapMode::Repeat,
base_level: 0,
max_level: default_max_level(),
min_lod: default_min_lod(),
max_lod: default_max_lod(),
lod_bias: 0.0,
mip_count: 1,
anisotropy: 16.0,
modifications_counter: 0,
sampler_properties_modifications: 1,
is_render_target: false,
cache_index: Default::default(),
}
}
}
#[derive(
Default, Copy, Clone, Deserialize, Serialize, Debug, Reflect, AsRefStr, EnumString, VariantNames,
)]
pub enum MipFilter {
Nearest,
#[default]
Bilinear,
Hamming,
CatmullRom,
Lanczos,
}
uuid_provider!(MipFilter = "8fa17c0e-6889-4540-b396-97db4dc952aa");
impl MipFilter {
fn into_filter_type(self) -> fr::FilterType {
match self {
MipFilter::Nearest => fr::FilterType::Box,
MipFilter::Bilinear => fr::FilterType::Bilinear,
MipFilter::CatmullRom => fr::FilterType::CatmullRom,
MipFilter::Hamming => fr::FilterType::Hamming,
MipFilter::Lanczos => fr::FilterType::Lanczos3,
}
}
}
#[derive(Clone, Deserialize, Serialize, Debug, Reflect)]
pub struct TextureImportOptions {
#[serde(default)]
pub(crate) minification_filter: TextureMinificationFilter,
#[serde(default)]
pub(crate) magnification_filter: TextureMagnificationFilter,
#[serde(default)]
pub(crate) s_wrap_mode: TextureWrapMode,
#[serde(default)]
pub(crate) t_wrap_mode: TextureWrapMode,
#[serde(default)]
pub(crate) r_wrap_mode: TextureWrapMode,
#[serde(default)]
pub(crate) anisotropy: f32,
#[serde(default)]
pub(crate) compression: CompressionOptions,
#[serde(default)]
pub(crate) mip_filter: MipFilter,
#[serde(default)]
pub(crate) flip_green_channel: bool,
#[serde(default)]
pub(crate) base_level: usize,
#[serde(default = "default_max_level")]
pub(crate) max_level: usize,
#[serde(default = "default_min_lod")]
pub(crate) min_lod: f32,
#[serde(default = "default_max_lod")]
pub(crate) max_lod: f32,
#[serde(default)]
pub(crate) lod_bias: f32,
}
fn default_max_level() -> usize {
1000
}
fn default_min_lod() -> f32 {
-1000.0
}
fn default_max_lod() -> f32 {
1000.0
}
impl Default for TextureImportOptions {
fn default() -> Self {
Self {
minification_filter: TextureMinificationFilter::LinearMipMapLinear,
magnification_filter: TextureMagnificationFilter::Linear,
s_wrap_mode: TextureWrapMode::Repeat,
t_wrap_mode: TextureWrapMode::Repeat,
r_wrap_mode: TextureWrapMode::Repeat,
anisotropy: 16.0,
compression: CompressionOptions::default(),
mip_filter: Default::default(),
flip_green_channel: false,
base_level: 0,
max_level: default_max_level(),
min_lod: default_min_lod(),
max_lod: default_max_lod(),
lod_bias: 0.0,
}
}
}
impl ImportOptions for TextureImportOptions {}
impl TextureImportOptions {
pub fn with_minification_filter(
mut self,
minification_filter: TextureMinificationFilter,
) -> Self {
self.minification_filter = minification_filter;
self
}
pub fn set_minification_filter(&mut self, minification_filter: TextureMinificationFilter) {
self.minification_filter = minification_filter;
}
pub fn with_magnification_filter(
mut self,
magnification_filter: TextureMagnificationFilter,
) -> Self {
self.magnification_filter = magnification_filter;
self
}
pub fn set_magnification_filter(&mut self, magnification_filter: TextureMagnificationFilter) {
self.magnification_filter = magnification_filter;
}
pub fn with_s_wrap_mode(mut self, s_wrap_mode: TextureWrapMode) -> Self {
self.s_wrap_mode = s_wrap_mode;
self
}
pub fn set_s_wrap_mode(&mut self, s_wrap_mode: TextureWrapMode) {
self.s_wrap_mode = s_wrap_mode;
}
pub fn with_t_wrap_mode(mut self, t_wrap_mode: TextureWrapMode) -> Self {
self.t_wrap_mode = t_wrap_mode;
self
}
pub fn set_t_wrap_mode(&mut self, t_wrap_mode: TextureWrapMode) {
self.t_wrap_mode = t_wrap_mode;
}
pub fn with_anisotropy(mut self, anisotropy: f32) -> Self {
self.anisotropy = anisotropy.min(1.0);
self
}
pub fn set_anisotropy(&mut self, anisotropy: f32) {
self.anisotropy = anisotropy.min(1.0);
}
pub fn with_compression(mut self, compression: CompressionOptions) -> Self {
self.compression = compression;
self
}
pub fn set_compression(&mut self, compression: CompressionOptions) {
self.compression = compression;
}
pub fn with_base_level(mut self, base_level: usize) -> Self {
self.base_level = base_level;
self
}
pub fn set_base_level(&mut self, base_level: usize) {
self.base_level = base_level;
}
pub fn with_max_level(mut self, max_level: usize) -> Self {
self.max_level = max_level;
self
}
pub fn set_max_level(&mut self, max_level: usize) {
self.max_level = max_level;
}
pub fn with_min_lod(mut self, min_lod: f32) -> Self {
self.min_lod = min_lod;
self
}
pub fn set_min_lod(&mut self, min_lod: f32) {
self.min_lod = min_lod;
}
pub fn with_max_lod(mut self, max_lod: f32) -> Self {
self.max_lod = max_lod;
self
}
pub fn set_max_lod(&mut self, max_lod: f32) {
self.max_lod = max_lod;
}
pub fn with_lod_bias(mut self, lod_bias: f32) -> Self {
self.lod_bias = lod_bias;
self
}
pub fn set_lod_bias(&mut self, lod_bias: f32) {
self.lod_bias = lod_bias;
}
}
pub static PLACEHOLDER: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
BuiltInResource::new(
"Default Texture",
embedded_data_source!("default.png"),
|data| {
TextureResource::load_from_memory(
uuid!("58b0e112-a21a-481f-b305-a2dc5a8bea1f"),
ResourceKind::External,
data,
Default::default(),
)
.unwrap()
},
)
});
pub static PURE_COLOR: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
BuiltInResource::new(
"Pure Color Texture",
embedded_data_source!("pure_color.png"),
|data| {
TextureResource::load_from_memory(
uuid!("9709eef2-305c-44da-91e5-6f293d74408a"),
ResourceKind::External,
data,
Default::default(),
)
.unwrap()
},
)
});
pub type TextureResource = Resource<Texture>;
pub trait TextureResourceExtension: Sized {
fn new_render_target(width: u32, height: u32) -> Self;
fn new_cube_render_target(resolution: u32) -> Self;
fn new_render_target_with_format(width: u32, height: u32, pixel_kind: TexturePixelKind)
-> Self;
fn load_from_memory(
resource_uuid: Uuid,
kind: ResourceKind,
data: &[u8],
import_options: TextureImportOptions,
) -> Result<Self, TextureError>;
fn from_bytes(
resource_uuid: Uuid,
kind: TextureKind,
pixel_kind: TexturePixelKind,
bytes: Vec<u8>,
resource_kind: ResourceKind,
) -> Option<Self>;
fn deep_clone(&self) -> Self;
}
impl TextureResourceExtension for TextureResource {
fn new_render_target(width: u32, height: u32) -> Self {
Self::new_render_target_with_format(width, height, TexturePixelKind::RGBA8)
}
fn new_cube_render_target(size: u32) -> Self {
Resource::new_ok(
Default::default(),
Default::default(),
Texture {
kind: TextureKind::Cube { size },
bytes: Default::default(),
pixel_kind: TexturePixelKind::RGBA8,
minification_filter: TextureMinificationFilter::Linear,
magnification_filter: TextureMagnificationFilter::Linear,
s_wrap_mode: TextureWrapMode::Repeat,
t_wrap_mode: TextureWrapMode::Repeat,
r_wrap_mode: TextureWrapMode::Repeat,
base_level: 0,
max_level: 1000,
min_lod: -1000.0,
max_lod: 1000.0,
lod_bias: 0.0,
mip_count: 1,
anisotropy: 1.0,
modifications_counter: 0,
sampler_properties_modifications: 1,
is_render_target: true,
cache_index: Default::default(),
},
)
}
fn new_render_target_with_format(
width: u32,
height: u32,
pixel_kind: TexturePixelKind,
) -> Self {
Resource::new_ok(
Default::default(),
Default::default(),
Texture {
kind: TextureKind::Rectangle { width, height },
bytes: Default::default(),
pixel_kind,
minification_filter: TextureMinificationFilter::Linear,
magnification_filter: TextureMagnificationFilter::Linear,
s_wrap_mode: TextureWrapMode::Repeat,
t_wrap_mode: TextureWrapMode::Repeat,
r_wrap_mode: TextureWrapMode::Repeat,
base_level: 0,
max_level: 1000,
min_lod: -1000.0,
max_lod: 1000.0,
lod_bias: 0.0,
mip_count: 1,
anisotropy: 1.0,
modifications_counter: 0,
sampler_properties_modifications: 1,
is_render_target: true,
cache_index: Default::default(),
},
)
}
fn load_from_memory(
resource_uuid: Uuid,
kind: ResourceKind,
data: &[u8],
import_options: TextureImportOptions,
) -> Result<Self, TextureError> {
Ok(Resource::new_ok(
resource_uuid,
kind,
Texture::load_from_memory(data, import_options)?,
))
}
fn from_bytes(
resource_uuid: Uuid,
kind: TextureKind,
pixel_kind: TexturePixelKind,
bytes: Vec<u8>,
resource_kind: ResourceKind,
) -> Option<Self> {
Some(Resource::new_ok(
resource_uuid,
resource_kind,
Texture::from_bytes(kind, pixel_kind, bytes)?,
))
}
fn deep_clone(&self) -> Self {
let kind = self.header().kind;
let data = self.data_ref().clone();
Resource::new_ok(Uuid::new_v4(), kind, data)
}
}
#[derive(
Copy,
Clone,
Debug,
Hash,
PartialOrd,
PartialEq,
Deserialize,
Serialize,
Reflect,
VariantNames,
EnumString,
AsRefStr,
Visit,
Eq,
)]
#[repr(u32)]
#[derive(Default)]
pub enum TextureMagnificationFilter {
Nearest = 0,
#[default]
Linear = 1,
}
uuid_provider!(TextureMagnificationFilter = "824f5b6c-8957-42db-9ebc-ef2a5dece5ab");
#[derive(
Copy,
Clone,
Debug,
Hash,
PartialOrd,
PartialEq,
Deserialize,
Serialize,
Reflect,
VariantNames,
EnumString,
AsRefStr,
Visit,
Eq,
)]
#[repr(u32)]
#[derive(Default)]
pub enum TextureMinificationFilter {
Nearest = 0,
NearestMipMapNearest = 1,
NearestMipMapLinear = 2,
Linear = 3,
LinearMipMapNearest = 4,
#[default]
LinearMipMapLinear = 5,
}
uuid_provider!(TextureMinificationFilter = "0ec9e072-6d0a-47b2-a9c2-498cac4de22b");
impl TextureMinificationFilter {
pub fn is_using_mip_mapping(self) -> bool {
match self {
TextureMinificationFilter::Nearest | TextureMinificationFilter::Linear => false,
TextureMinificationFilter::NearestMipMapNearest
| TextureMinificationFilter::LinearMipMapLinear
| TextureMinificationFilter::NearestMipMapLinear
| TextureMinificationFilter::LinearMipMapNearest => true,
}
}
}
#[derive(
Copy,
Clone,
Debug,
Hash,
PartialOrd,
PartialEq,
Deserialize,
Serialize,
Reflect,
VariantNames,
EnumString,
AsRefStr,
Visit,
Eq,
)]
#[repr(u32)]
#[derive(Default)]
pub enum TextureWrapMode {
#[default]
Repeat = 0,
ClampToEdge = 1,
ClampToBorder = 2,
MirroredRepeat = 3,
MirrorClampToEdge = 4,
}
uuid_provider!(TextureWrapMode = "e360d139-4374-4323-a66d-d192809d9d87");
#[derive(
Copy,
Clone,
PartialEq,
Eq,
Debug,
Reflect,
Visit,
AsRefStr,
EnumString,
VariantNames,
TypeUuidProvider,
)]
#[type_uuid(id = "dcca9b9c-dd1e-412c-922f-074703d35781")]
#[repr(u32)]
pub enum TexturePixelKind {
R8 = 0,
RGB8 = 1,
RGBA8 = 2,
RG8 = 3,
R16 = 4,
RG16 = 5,
BGR8 = 6,
BGRA8 = 7,
RGB16 = 8,
RGBA16 = 9,
DXT1RGB = 10,
DXT1RGBA = 11,
DXT3RGBA = 12,
DXT5RGBA = 13,
R8RGTC = 14,
RG8RGTC = 15,
RGB32F = 16,
RGBA32F = 17,
Luminance8 = 18,
LuminanceAlpha8 = 19,
Luminance16 = 20,
LuminanceAlpha16 = 21,
RGB16F = 22,
R32F = 23,
R16F = 24,
SRGBA8 = 25,
SRGB8 = 26,
}
impl TexturePixelKind {
fn new(id: u32) -> Result<Self, String> {
match id {
0 => Ok(Self::R8),
1 => Ok(Self::RGB8),
2 => Ok(Self::RGBA8),
3 => Ok(Self::RG8),
4 => Ok(Self::R16),
5 => Ok(Self::RG16),
6 => Ok(Self::BGR8),
7 => Ok(Self::BGRA8),
8 => Ok(Self::RGB16),
9 => Ok(Self::RGBA16),
10 => Ok(Self::DXT1RGB),
11 => Ok(Self::DXT1RGBA),
12 => Ok(Self::DXT3RGBA),
13 => Ok(Self::DXT5RGBA),
14 => Ok(Self::R8RGTC),
15 => Ok(Self::RG8RGTC),
16 => Ok(Self::RGB32F),
17 => Ok(Self::RGBA32F),
18 => Ok(Self::Luminance8),
19 => Ok(Self::LuminanceAlpha8),
20 => Ok(Self::Luminance16),
21 => Ok(Self::LuminanceAlpha16),
22 => Ok(Self::RGB16F),
23 => Ok(Self::R32F),
24 => Ok(Self::R16F),
25 => Ok(Self::SRGBA8),
26 => Ok(Self::SRGB8),
_ => Err(format!("Invalid texture kind {id}!")),
}
}
fn id(self) -> u32 {
self as u32
}
pub fn size_in_bytes(&self) -> Option<usize> {
match self {
Self::R8 | Self::Luminance8 => Some(1),
Self::RGB8 | Self::SRGB8 | Self::BGR8 => Some(3),
Self::RGBA8
| Self::SRGBA8
| Self::RG16
| Self::BGRA8
| Self::LuminanceAlpha16
| Self::R32F => Some(4),
Self::RG8 | Self::R16 | Self::LuminanceAlpha8 | Self::Luminance16 | Self::R16F => {
Some(2)
}
Self::RGB16 | Self::RGB16F => Some(6),
Self::RGBA16 => Some(8),
Self::RGB32F => Some(12),
Self::RGBA32F => Some(16),
Self::DXT1RGB
| Self::DXT1RGBA
| Self::DXT3RGBA
| Self::DXT5RGBA
| Self::R8RGTC
| Self::RG8RGTC => None,
}
}
}
#[derive(Debug)]
pub enum TextureError {
UnsupportedFormat,
Io(std::io::Error),
Image(image::ImageError),
FileLoadError(FileError),
}
impl Display for TextureError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
TextureError::UnsupportedFormat => {
write!(f, "Unsupported format!")
}
TextureError::Io(v) => {
write!(f, "An i/o error has occurred: {v}")
}
TextureError::Image(v) => {
write!(f, "Image loading error {v}")
}
TextureError::FileLoadError(v) => {
write!(f, "A file load error has occurred {v:?}")
}
}
}
}
impl std::error::Error for TextureError {}
impl From<FileError> for TextureError {
fn from(v: FileError) -> Self {
Self::FileLoadError(v)
}
}
impl From<image::ImageError> for TextureError {
fn from(v: ImageError) -> Self {
Self::Image(v)
}
}
impl From<std::io::Error> for TextureError {
fn from(v: Error) -> Self {
Self::Io(v)
}
}
fn ceil_div_4(x: u32) -> u32 {
x.div_ceil(4)
}
#[derive(
Copy,
Clone,
Deserialize,
Serialize,
PartialEq,
Eq,
Debug,
Reflect,
VariantNames,
EnumString,
AsRefStr,
)]
#[repr(u32)]
#[derive(Default)]
pub enum CompressionOptions {
#[default]
NoCompression = 0,
Speed = 1,
Quality = 2,
}
uuid_provider!(CompressionOptions = "fbdcc081-d0b8-4b62-9925-2de6c013fbf5");
fn transmute_slice<T>(bytes: &[u8]) -> &'_ [T] {
unsafe {
std::slice::from_raw_parts(
bytes.as_ptr() as *const T,
bytes.len() / std::mem::size_of::<T>(),
)
}
}
fn transmute_slice_mut<T>(bytes: &mut [u8]) -> &'_ mut [T] {
unsafe {
std::slice::from_raw_parts_mut(
bytes.as_ptr() as *mut T,
bytes.len() / std::mem::size_of::<T>(),
)
}
}
fn compress_bc1<T: tbc::color::ColorRgba8>(bytes: &[u8], width: usize, height: usize) -> Vec<u8> {
tbc::encode_image_bc1_conv_u8::<T>(transmute_slice::<T>(bytes), width, height)
}
fn compress_bc3<T: tbc::color::ColorRgba8>(bytes: &[u8], width: usize, height: usize) -> Vec<u8> {
tbc::encode_image_bc3_conv_u8::<T>(transmute_slice::<T>(bytes), width, height)
}
fn compress_r8_bc4<T: tbc::color::ColorRed8>(bytes: &[u8], width: usize, height: usize) -> Vec<u8> {
tbc::encode_image_bc4_r8_conv_u8::<T>(transmute_slice::<T>(bytes), width, height)
}
fn compress_rg8_bc4<T: tbc::color::ColorRedGreen8>(
bytes: &[u8],
width: usize,
height: usize,
) -> Vec<u8> {
tbc::encode_image_bc4_rg8_conv_u8::<T>(transmute_slice::<T>(bytes), width, height)
}
fn data_hash(data: &[u8]) -> u64 {
let mut hasher = FxHasher::default();
data.hash(&mut hasher);
hasher.finish()
}
fn try_compress(
pixel_kind: TexturePixelKind,
bytes: &[u8],
w: usize,
h: usize,
compression: CompressionOptions,
) -> Option<(Vec<u8>, TexturePixelKind)> {
match (pixel_kind, compression) {
(TexturePixelKind::RGB8, CompressionOptions::Speed) => Some((
compress_bc1::<tbc::color::Rgb8>(bytes, w, h),
TexturePixelKind::DXT1RGB,
)),
(TexturePixelKind::RGB8, CompressionOptions::Quality) => Some((
compress_bc3::<tbc::color::Rgb8>(bytes, w, h),
TexturePixelKind::DXT5RGBA,
)),
(TexturePixelKind::RGBA8, CompressionOptions::Speed) => Some((
compress_bc1::<tbc::color::Rgba8>(bytes, w, h),
TexturePixelKind::DXT1RGBA,
)),
(TexturePixelKind::RGBA8, CompressionOptions::Quality) => Some((
compress_bc3::<tbc::color::Rgba8>(bytes, w, h),
TexturePixelKind::DXT5RGBA,
)),
(TexturePixelKind::R8, CompressionOptions::Speed)
| (TexturePixelKind::R8, CompressionOptions::Quality)
| (TexturePixelKind::Luminance8, CompressionOptions::Speed)
| (TexturePixelKind::Luminance8, CompressionOptions::Quality) => Some((
compress_r8_bc4::<tbc::color::Red8>(bytes, w, h),
TexturePixelKind::R8RGTC,
)),
(TexturePixelKind::RG8, CompressionOptions::Speed)
| (TexturePixelKind::RG8, CompressionOptions::Quality)
| (TexturePixelKind::LuminanceAlpha8, CompressionOptions::Speed)
| (TexturePixelKind::LuminanceAlpha8, CompressionOptions::Quality) => Some((
compress_rg8_bc4::<tbc::color::RedGreen8>(bytes, w, h),
TexturePixelKind::RG8RGTC,
)),
_ => None,
}
}
fn bytes_in_mip_level(kind: TextureKind, pixel_kind: TexturePixelKind, mip: usize) -> u32 {
let pixel_count = match kind {
TextureKind::Line { length } => length.shr(mip),
TextureKind::Rectangle { width, height } => width.shr(mip) * height.shr(mip),
TextureKind::Cube { size } => 6 * size.shr(mip).pow(2),
TextureKind::Volume {
width,
height,
depth,
} => width.shr(mip) * height.shr(mip) * depth.shr(mip),
};
match pixel_kind {
TexturePixelKind::R8 | TexturePixelKind::Luminance8 => pixel_count,
TexturePixelKind::R16
| TexturePixelKind::LuminanceAlpha8
| TexturePixelKind::Luminance16
| TexturePixelKind::RG8
| TexturePixelKind::R16F => 2 * pixel_count,
TexturePixelKind::RGB8 | TexturePixelKind::SRGB8 | TexturePixelKind::BGR8 => {
3 * pixel_count
}
TexturePixelKind::RGBA8
| TexturePixelKind::SRGBA8
| TexturePixelKind::BGRA8
| TexturePixelKind::RG16
| TexturePixelKind::LuminanceAlpha16
| TexturePixelKind::R32F => 4 * pixel_count,
TexturePixelKind::RGB16 | TexturePixelKind::RGB16F => 6 * pixel_count,
TexturePixelKind::RGBA16 => 8 * pixel_count,
TexturePixelKind::RGB32F => 12 * pixel_count,
TexturePixelKind::RGBA32F => 16 * pixel_count,
TexturePixelKind::DXT1RGB
| TexturePixelKind::DXT1RGBA
| TexturePixelKind::DXT3RGBA
| TexturePixelKind::DXT5RGBA
| TexturePixelKind::R8RGTC
| TexturePixelKind::RG8RGTC => {
let block_size = match pixel_kind {
TexturePixelKind::DXT1RGB
| TexturePixelKind::DXT1RGBA
| TexturePixelKind::R8RGTC => 8,
TexturePixelKind::DXT3RGBA
| TexturePixelKind::DXT5RGBA
| TexturePixelKind::RG8RGTC => 16,
_ => unreachable!(),
};
match kind {
TextureKind::Line { length } => ceil_div_4(length) * block_size,
TextureKind::Rectangle { width, height } => {
ceil_div_4(width) * ceil_div_4(height) * block_size
}
TextureKind::Cube { size } => 6 * ceil_div_4(size).pow(2) * block_size,
TextureKind::Volume {
width,
height,
depth,
} => ceil_div_4(width) * ceil_div_4(height) * ceil_div_4(depth) * block_size,
}
}
}
}
fn mip_byte_offset(kind: TextureKind, pixel_kind: TexturePixelKind, mut mip: usize) -> usize {
let mut offset = 0;
loop {
offset += bytes_in_mip_level(kind, pixel_kind, mip) as usize;
mip = mip.saturating_sub(1);
if mip == 0 {
break;
}
}
offset
}
fn convert_pixel_type_enum(pixel_kind: TexturePixelKind) -> fr::PixelType {
match pixel_kind {
TexturePixelKind::R8 | TexturePixelKind::Luminance8 => fr::PixelType::U8,
TexturePixelKind::RGB8 | TexturePixelKind::BGR8 => fr::PixelType::U8x3,
TexturePixelKind::RGBA8 | TexturePixelKind::BGRA8 => fr::PixelType::U8x4,
TexturePixelKind::RG8 | TexturePixelKind::LuminanceAlpha8 => fr::PixelType::U8x2,
TexturePixelKind::R16 | TexturePixelKind::Luminance16 => fr::PixelType::U16,
TexturePixelKind::RG16 | TexturePixelKind::LuminanceAlpha16 => fr::PixelType::U16x2,
TexturePixelKind::RGB16 => fr::PixelType::U16x3,
TexturePixelKind::RGBA16 => fr::PixelType::U16x4,
TexturePixelKind::R32F => fr::PixelType::F32,
_ => unreachable!(),
}
}
fn flip_green_channel<'a, P>(pixels: impl Iterator<Item = &'a mut P>)
where
P: Pixel + 'a,
{
for pixel in pixels {
let green = &mut pixel.channels_mut()[1];
let inverted = P::Subpixel::max_value() - *green;
*green = inverted;
}
}
impl Texture {
pub fn load_from_memory(
data: &[u8],
import_options: TextureImportOptions,
) -> Result<Self, TextureError> {
if let Ok(dds) = ddsfile::Dds::read(&mut Cursor::new(data)) {
let d3dformat = dds
.get_d3d_format()
.ok_or(TextureError::UnsupportedFormat)?;
let mip_count = dds.get_num_mipmap_levels();
let mut bytes = dds.data;
let pixel_kind = match d3dformat {
D3DFormat::DXT1 => TexturePixelKind::DXT1RGBA,
D3DFormat::DXT3 => TexturePixelKind::DXT3RGBA,
D3DFormat::DXT5 => TexturePixelKind::DXT5RGBA,
D3DFormat::L8 | D3DFormat::A8 => TexturePixelKind::R8,
D3DFormat::L16 => TexturePixelKind::R16,
D3DFormat::R8G8B8 => TexturePixelKind::RGB8,
D3DFormat::A8L8 => TexturePixelKind::RG8,
D3DFormat::A8R8G8B8 => {
TexturePixelKind::RGBA8
}
D3DFormat::G16R16 => {
assert_eq!(bytes.len() % 4, 0);
for chunk in bytes.chunks_exact_mut(4) {
let gh = chunk[0];
let gl = chunk[1];
let rh = chunk[2];
let rl = chunk[3];
chunk[0] = rh;
chunk[1] = rl;
chunk[2] = gh;
chunk[3] = gl;
}
TexturePixelKind::RG16
}
_ => return Err(TextureError::UnsupportedFormat),
};
Ok(Self {
pixel_kind,
modifications_counter: 0,
minification_filter: import_options.minification_filter,
magnification_filter: import_options.magnification_filter,
s_wrap_mode: import_options.s_wrap_mode,
t_wrap_mode: import_options.t_wrap_mode,
r_wrap_mode: import_options.r_wrap_mode,
base_level: import_options.base_level,
max_level: import_options.max_level,
min_lod: import_options.min_lod,
max_lod: import_options.max_lod,
anisotropy: import_options.anisotropy,
mip_count,
bytes: bytes.into(),
kind: if dds.header.caps2 & Caps2::CUBEMAP == Caps2::CUBEMAP {
TextureKind::Cube {
size: dds.header.width,
}
} else if dds.header.caps2 & Caps2::VOLUME == Caps2::VOLUME {
TextureKind::Volume {
width: dds.header.width,
height: dds.header.height,
depth: dds.header.depth.unwrap(),
}
} else {
TextureKind::Rectangle {
width: dds.header.width,
height: dds.header.height,
}
},
is_render_target: false,
cache_index: Default::default(),
lod_bias: import_options.lod_bias,
sampler_properties_modifications: 1,
})
} else {
let mut dyn_img = image::load_from_memory(data)
.or_else(|_| image::load_from_memory_with_format(data, ImageFormat::Tga))?;
let width = dyn_img.width();
let height = dyn_img.height();
if import_options.flip_green_channel {
match dyn_img {
DynamicImage::ImageRgb8(ref mut img) => flip_green_channel(img.pixels_mut()),
DynamicImage::ImageRgba8(ref mut img) => flip_green_channel(img.pixels_mut()),
DynamicImage::ImageRgb16(ref mut img) => flip_green_channel(img.pixels_mut()),
DynamicImage::ImageRgba16(ref mut img) => flip_green_channel(img.pixels_mut()),
DynamicImage::ImageRgb32F(ref mut img) => flip_green_channel(img.pixels_mut()),
DynamicImage::ImageRgba32F(ref mut img) => flip_green_channel(img.pixels_mut()),
_ => (),
}
}
let src_pixel_kind = match dyn_img {
DynamicImage::ImageLuma8(_) => TexturePixelKind::Luminance8,
DynamicImage::ImageLumaA8(_) => TexturePixelKind::LuminanceAlpha8,
DynamicImage::ImageRgb8(_) => TexturePixelKind::RGB8,
DynamicImage::ImageRgba8(_) => TexturePixelKind::RGBA8,
DynamicImage::ImageLuma16(_) => TexturePixelKind::Luminance16,
DynamicImage::ImageLumaA16(_) => TexturePixelKind::LuminanceAlpha16,
DynamicImage::ImageRgb16(_) => TexturePixelKind::RGB16,
DynamicImage::ImageRgba16(_) => TexturePixelKind::RGBA16,
DynamicImage::ImageRgb32F(_) => TexturePixelKind::RGB32F,
DynamicImage::ImageRgba32F(_) => TexturePixelKind::RGBA32F,
_ => return Err(TextureError::UnsupportedFormat),
};
let mut final_pixel_kind = src_pixel_kind;
let mut mip_count = 0;
let mut bytes = Vec::with_capacity(
width as usize * height as usize * src_pixel_kind.size_in_bytes().unwrap_or(4),
);
if import_options.minification_filter.is_using_mip_mapping() {
let src_pixel_type = convert_pixel_type_enum(src_pixel_kind);
let mut level_width = width;
let mut level_height = height;
let mut current_level = fr::images::Image::from_vec_u8(
level_width,
level_height,
dyn_img.as_bytes().to_vec(),
src_pixel_type,
)
.map_err(|_| TextureError::UnsupportedFormat)?;
while level_width != 0 && level_height != 0 {
if mip_count != 0 {
let mut dst_img =
fr::images::Image::new(level_width, level_height, src_pixel_type);
let mut resizer = fr::Resizer::new();
resizer
.resize(
¤t_level,
&mut dst_img,
Some(&ResizeOptions {
algorithm: fr::ResizeAlg::Convolution(
import_options.mip_filter.into_filter_type(),
),
cropping: Default::default(),
mul_div_alpha: true,
}),
)
.expect("Pixel types must match!");
current_level = dst_img;
}
mip_count += 1;
if import_options.compression == CompressionOptions::NoCompression {
bytes.extend_from_slice(current_level.buffer())
} else if let Some((compressed_data, new_pixel_kind)) = try_compress(
src_pixel_kind,
current_level.buffer(),
level_width as usize,
level_height as usize,
import_options.compression,
) {
final_pixel_kind = new_pixel_kind;
bytes.extend_from_slice(&compressed_data);
} else {
bytes.extend_from_slice(current_level.buffer())
}
level_width = level_width.checked_shr(1).unwrap_or_default();
level_height = level_height.checked_shr(1).unwrap_or_default();
}
} else {
mip_count = 1;
if import_options.compression == CompressionOptions::NoCompression {
bytes.extend_from_slice(dyn_img.as_bytes());
} else if let Some((compressed_data, new_pixel_kind)) = try_compress(
src_pixel_kind,
dyn_img.as_bytes(),
width as usize,
height as usize,
import_options.compression,
) {
final_pixel_kind = new_pixel_kind;
bytes.extend_from_slice(&compressed_data);
} else {
bytes.extend_from_slice(dyn_img.as_bytes())
}
}
Ok(Self {
pixel_kind: final_pixel_kind,
kind: TextureKind::Rectangle { width, height },
modifications_counter: 0,
bytes: bytes.into(),
mip_count,
minification_filter: import_options.minification_filter,
magnification_filter: import_options.magnification_filter,
s_wrap_mode: import_options.s_wrap_mode,
t_wrap_mode: import_options.t_wrap_mode,
r_wrap_mode: import_options.r_wrap_mode,
base_level: import_options.base_level,
max_level: import_options.max_level,
min_lod: import_options.min_lod,
max_lod: import_options.max_lod,
anisotropy: import_options.anisotropy,
is_render_target: false,
cache_index: Default::default(),
lod_bias: import_options.lod_bias,
sampler_properties_modifications: 1,
})
}
}
pub(crate) async fn load_from_file<P: AsRef<Path>>(
path: P,
io: &dyn ResourceIo,
import_options: TextureImportOptions,
) -> Result<Self, TextureError> {
let data = io.load_file(path.as_ref()).await?;
Self::load_from_memory(&data, import_options)
}
pub fn from_bytes(
kind: TextureKind,
pixel_kind: TexturePixelKind,
bytes: Vec<u8>,
) -> Option<Self> {
if bytes_in_mip_level(kind, pixel_kind, 0) != bytes.len() as u32 {
None
} else {
Some(Self {
kind,
modifications_counter: 0,
bytes: bytes.into(),
pixel_kind,
..Default::default()
})
}
}
#[inline]
pub fn set_minification_filter(&mut self, filter: TextureMinificationFilter) {
self.minification_filter = filter;
self.sampler_properties_modifications += 1;
}
#[inline]
pub fn minification_filter(&self) -> TextureMinificationFilter {
self.minification_filter
}
#[inline]
pub fn set_magnification_filter(&mut self, filter: TextureMagnificationFilter) {
self.magnification_filter = filter;
self.sampler_properties_modifications += 1;
}
#[inline]
pub fn magnification_filter(&self) -> TextureMagnificationFilter {
self.magnification_filter
}
#[inline]
pub fn set_s_wrap_mode(&mut self, s_wrap_mode: TextureWrapMode) {
self.s_wrap_mode = s_wrap_mode;
self.sampler_properties_modifications += 1;
}
#[inline]
pub fn s_wrap_mode(&self) -> TextureWrapMode {
self.s_wrap_mode
}
#[inline]
pub fn set_t_wrap_mode(&mut self, t_wrap_mode: TextureWrapMode) {
self.t_wrap_mode = t_wrap_mode;
self.sampler_properties_modifications += 1;
}
#[inline]
pub fn t_wrap_mode(&self) -> TextureWrapMode {
self.t_wrap_mode
}
#[inline]
pub fn set_r_wrap_mode(&mut self, r_wrap_mode: TextureWrapMode) {
self.r_wrap_mode = r_wrap_mode;
self.sampler_properties_modifications += 1;
}
pub fn sampler_modifications_count(&self) -> u64 {
self.sampler_properties_modifications
}
#[inline]
pub fn r_wrap_mode(&self) -> TextureWrapMode {
self.r_wrap_mode
}
#[inline]
pub fn mip_count(&self) -> u32 {
self.mip_count
}
#[inline]
pub fn kind(&self) -> TextureKind {
self.kind
}
#[inline]
pub fn modifications_count(&self) -> u64 {
self.modifications_counter
}
#[inline]
pub fn calculate_data_hash(&self) -> u64 {
data_hash(&self.bytes.0)
}
#[inline]
pub fn pixel_kind(&self) -> TexturePixelKind {
self.pixel_kind
}
#[inline]
pub fn base_level(&self) -> usize {
self.base_level
}
#[inline]
pub fn set_base_level(&mut self, base_level: usize) {
self.base_level = base_level;
}
#[inline]
pub fn max_level(&self) -> usize {
self.max_level
}
#[inline]
pub fn set_max_level(&mut self, max_level: usize) {
self.max_level = max_level;
}
#[inline]
pub fn min_lod(&self) -> f32 {
self.min_lod
}
#[inline]
pub fn set_min_lod(&mut self, min_lod: f32) {
self.sampler_properties_modifications += 1;
self.min_lod = min_lod;
}
#[inline]
pub fn max_lod(&self) -> f32 {
self.max_lod
}
#[inline]
pub fn set_max_lod(&mut self, max_lod: f32) {
self.sampler_properties_modifications += 1;
self.max_lod = max_lod;
}
#[inline]
pub fn lod_bias(&self) -> f32 {
self.lod_bias
}
#[inline]
pub fn set_lod_bias(&mut self, lod_bias: f32) {
self.sampler_properties_modifications += 1;
self.lod_bias = lod_bias;
}
pub fn data(&self) -> &[u8] {
&self.bytes
}
pub fn data_of_type<T: Sized>(&self) -> Option<&[T]> {
if let Some(pixel_size) = self.pixel_kind.size_in_bytes() {
if pixel_size == std::mem::size_of::<T>() {
return Some(transmute_slice(&self.bytes));
}
}
None
}
#[inline]
pub fn mip_level_data(&self, mip: usize) -> &[u8] {
let mip_begin = mip
.checked_sub(1)
.map(|prev| mip_byte_offset(self.kind, self.pixel_kind, prev))
.unwrap_or_default();
let mip_end = mip_byte_offset(self.kind, self.pixel_kind, mip);
&self.bytes[mip_begin..mip_end]
}
pub fn mip_level_data_of_type<T: Sized>(&self, mip: usize) -> Option<&[T]> {
if let Some(pixel_size) = self.pixel_kind.size_in_bytes() {
if pixel_size == std::mem::size_of::<T>() {
return Some(transmute_slice(self.mip_level_data(mip)));
}
}
None
}
#[inline]
pub fn is_render_target(&self) -> bool {
self.is_render_target
}
#[inline]
pub fn set_anisotropy_level(&mut self, anisotropy: f32) {
self.sampler_properties_modifications += 1;
self.anisotropy = anisotropy.max(1.0);
}
#[inline]
pub fn anisotropy_level(&self) -> f32 {
self.anisotropy
}
#[inline]
pub fn modify(&mut self) -> TextureDataRefMut<'_> {
TextureDataRefMut { texture: self }
}
}
pub struct TextureDataRefMut<'a> {
texture: &'a mut Texture,
}
impl Drop for TextureDataRefMut<'_> {
fn drop(&mut self) {
self.texture.modifications_counter += 1;
}
}
impl Deref for TextureDataRefMut<'_> {
type Target = Texture;
fn deref(&self) -> &Self::Target {
self.texture
}
}
impl DerefMut for TextureDataRefMut<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.texture
}
}
impl TextureDataRefMut<'_> {
pub fn data_mut(&mut self) -> &mut [u8] {
&mut self.texture.bytes
}
pub fn data_mut_of_type<T: Sized>(&mut self) -> Option<&mut [T]> {
if let Some(pixel_size) = self.texture.pixel_kind.size_in_bytes() {
if pixel_size == std::mem::size_of::<T>() {
return Some(transmute_slice_mut(&mut self.texture.bytes));
}
}
None
}
}