#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(feature = "simd"), forbid(unsafe_code))]
#[macro_use]
extern crate bitflags;
mod error;
pub use error::*;
mod format;
pub use format::{D3DFormat, DataFormat, DxgiFormat, FourCC, PixelFormat, PixelFormatFlags};
mod header;
pub use header::{Caps, Caps2, Header, HeaderFlags};
mod header10;
pub use header10::{AlphaMode, D3D10ResourceDimension, Header10, MiscFlag};
mod surface;
pub use surface::{CubemapFace, SubresourceId, SurfaceView, SurfaceViewMut};
mod content;
pub use content::{DecodeContent, HdrDecodeContent, ImageRgba8, ImageRgbaF32};
#[cfg(feature = "decode")]
mod decode;
#[cfg(feature = "decode")]
pub use decode::reference;
#[cfg(feature = "encode")]
mod encode;
#[cfg(feature = "encode")]
pub use encode::{max_abs_diff, psnr_rgba8, EncodeLayout, EncodeQuality, Rdo};
mod upload;
pub use upload::{GpuFormat, UploadPath, UploadPlan};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::fmt;
use std::io::{Read, Write};
#[derive(Clone)]
pub struct DdsBase<D = Vec<u8>> {
pub header: Header,
pub header10: Option<Header10>,
pub data: D,
}
pub type Dds = DdsBase<Vec<u8>>;
pub type DdsView<'a> = DdsBase<&'a [u8]>;
#[derive(Debug, Clone)]
pub struct NewD3dParams {
pub height: u32,
pub width: u32,
pub depth: Option<u32>,
pub format: D3DFormat,
pub mipmap_levels: Option<u32>,
pub caps2: Option<Caps2>,
}
#[derive(Debug, Clone)]
pub struct NewDxgiParams {
pub height: u32,
pub width: u32,
pub depth: Option<u32>,
pub format: DxgiFormat,
pub mipmap_levels: Option<u32>,
pub array_layers: Option<u32>,
pub caps2: Option<Caps2>,
pub is_cubemap: bool,
pub resource_dimension: D3D10ResourceDimension,
pub alpha_mode: AlphaMode,
}
impl Dds {
const MAGIC: u32 = 0x20534444;
pub fn new_d3d(params: NewD3dParams) -> Result<Dds, Error> {
let size = match get_texture_size(
params.format.get_pitch(params.width),
None,
params.format.get_pitch_height(),
params.height,
params.depth,
) {
Some(s) => s,
None => return Err(Error::UnsupportedFormat),
};
let mml = params.mipmap_levels.unwrap_or(1);
let min_mipmap_size = match params.format.get_minimum_mipmap_size_in_bytes() {
Some(mms) => mms,
None => return Err(Error::UnsupportedFormat),
};
let array_stride = get_array_stride(size, min_mipmap_size, mml);
let data_size = array_stride;
Ok(Dds {
header: Header::new_d3d(
params.height,
params.width,
params.depth,
params.format,
params.mipmap_levels,
params.caps2,
)?,
header10: None,
data: vec![0; data_size as usize],
})
}
pub fn new_dxgi(params: NewDxgiParams) -> Result<Dds, Error> {
let arraysize = params.array_layers.unwrap_or(1);
let size = match get_texture_size(
params.format.get_pitch(params.width),
None,
params.format.get_pitch_height(),
params.height,
params.depth,
) {
Some(s) => s,
None => return Err(Error::UnsupportedFormat),
};
let mml = params.mipmap_levels.unwrap_or(1);
let min_mipmap_size = match params.format.get_minimum_mipmap_size_in_bytes() {
Some(mms) => mms,
None => return Err(Error::UnsupportedFormat),
};
let array_stride = get_array_stride(size, min_mipmap_size, mml);
let data_size = arraysize
.checked_mul(array_stride)
.ok_or(Error::OutOfBounds)?;
let arraysize = if params.is_cubemap {
arraysize / 6
} else {
arraysize
};
let header10 = Header10::new(
params.format,
params.is_cubemap,
params.resource_dimension,
arraysize,
params.alpha_mode,
);
Ok(Dds {
header: Header::new_dxgi(
params.height,
params.width,
params.depth,
params.format,
params.mipmap_levels,
params.array_layers,
params.caps2,
)?,
header10: Some(header10),
data: vec![0; data_size as usize],
})
}
pub fn read<R: Read>(r: R) -> Result<Dds, Error> {
Self::read_inner(r, None)
}
pub fn read_limited<R: Read>(r: R, max_data_len: usize) -> Result<Dds, Error> {
Self::read_inner(r, Some(max_data_len))
}
fn read_inner<R: Read>(mut r: R, max_data_len: Option<usize>) -> Result<Dds, Error> {
let (header, header10) = read_headers(&mut r)?;
let mut data: Vec<u8> = Vec::new();
read_payload(r, &mut data, max_data_len)?;
Ok(Dds {
header,
header10,
data,
})
}
}
impl<'a> DdsView<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<DdsView<'a>, Error> {
let mut cursor = bytes;
let magic = cursor.read_u32::<LittleEndian>()?;
if magic != Dds::MAGIC {
return Err(Error::BadMagicNumber);
}
let header = Header::read(&mut cursor)?;
let header10 = if header.spf.fourcc == Some(FourCC(<FourCC>::DX10)) {
Some(Header10::read(&mut cursor)?)
} else {
None
};
Ok(DdsBase {
header,
header10,
data: cursor,
})
}
pub fn read_into<R: Read>(r: R, buf: &'a mut Vec<u8>) -> Result<DdsView<'a>, Error> {
Self::read_into_inner(r, buf, None)
}
pub fn read_into_limited<R: Read>(
r: R,
buf: &'a mut Vec<u8>,
max_data_len: usize,
) -> Result<DdsView<'a>, Error> {
Self::read_into_inner(r, buf, Some(max_data_len))
}
fn read_into_inner<R: Read>(
mut r: R,
buf: &'a mut Vec<u8>,
max_data_len: Option<usize>,
) -> Result<DdsView<'a>, Error> {
let (header, header10) = read_headers(&mut r)?;
read_payload(r, buf, max_data_len)?;
Ok(DdsBase {
header,
header10,
data: &buf[..],
})
}
}
impl<D: AsRef<[u8]>> DdsBase<D> {
pub fn write<W: Write>(&self, w: &mut W) -> Result<(), Error> {
w.write_u32::<LittleEndian>(Dds::MAGIC)?;
self.header.write(w)?;
if let Some(ref header10) = self.header10 {
header10.write(w)?;
}
w.write_all(self.data.as_ref())?;
Ok(())
}
pub fn get_d3d_format(&self) -> Option<D3DFormat> {
D3DFormat::try_from_pixel_format(&self.header.spf)
}
pub fn get_dxgi_format(&self) -> Option<DxgiFormat> {
if let Some(ref h10) = self.header10 {
Some(h10.dxgi_format)
} else {
DxgiFormat::try_from_pixel_format(&self.header.spf)
}
}
pub(crate) fn format_of(&self) -> Option<crate::format::FormatOf> {
use crate::format::FormatOf;
if let Some(dxgi) = self.get_dxgi_format() {
return Some(FormatOf::Dxgi(dxgi));
}
if let Some(d3d) = self.get_d3d_format() {
return Some(FormatOf::D3d(d3d));
}
None
}
pub fn get_format(&self) -> Option<Box<dyn DataFormat>> {
if let Some(dxgi) = self.get_dxgi_format() {
Some(Box::new(dxgi))
} else if let Some(d3d) = self.get_d3d_format() {
Some(Box::new(d3d))
} else {
None
}
}
pub fn get_width(&self) -> u32 {
self.header.width
}
pub fn get_height(&self) -> u32 {
self.header.height
}
pub fn get_depth(&self) -> u32 {
self.header.depth.unwrap_or(1)
}
pub fn get_bits_per_pixel(&self) -> Option<u32> {
if let Some(format) = self.format_of() {
if let Some(bpp) = format.get_bits_per_pixel() {
return Some(bpp as u32);
}
}
if let Some(bpp) = self.header.spf.rgb_bit_count {
return Some(bpp);
}
None
}
pub fn get_pitch(&self) -> Option<u32> {
if let Some(format) = self.format_of() {
if let Some(pitch) = format.get_pitch(self.header.width) {
return Some(pitch);
}
}
if let Some(pitch) = self.header.pitch {
return Some(pitch);
}
if let Some(bpp) = self.get_bits_per_pixel() {
return bpp
.checked_mul(self.get_width())
.and_then(|n| n.checked_add(7))
.map(|n| n / 8);
}
None
}
pub fn get_pitch_height(&self) -> u32 {
if let Some(format) = self.format_of() {
format.get_pitch_height()
} else {
1
}
}
pub fn get_main_texture_size(&self) -> Option<u32> {
get_texture_size(
self.get_pitch(),
self.header.linear_size,
self.get_pitch_height(),
self.header.height,
self.header.depth,
)
}
pub fn get_array_stride(&self) -> Result<u32, Error> {
let size = match self.get_main_texture_size() {
Some(s) => s,
None => return Err(Error::UnsupportedFormat),
};
let mml = self.get_num_mipmap_levels();
let min_mipmap_size = self.get_min_mipmap_size_in_bytes();
Ok(get_array_stride(size, min_mipmap_size, mml))
}
pub fn get_num_array_layers(&self) -> u32 {
if let Some(ref h10) = self.header10 {
h10.array_size
} else if self.header.caps2.contains(Caps2::CUBEMAP) {
6
} else {
1 }
}
pub fn get_num_mipmap_levels(&self) -> u32 {
if let Some(mmc) = self.header.mip_map_count {
mmc
} else {
1 }
}
pub fn get_min_mipmap_size_in_bytes(&self) -> u32 {
if let Some(format) = self.format_of() {
if let Some(min) = format.get_minimum_mipmap_size_in_bytes() {
return min;
}
}
if let Some(bpp) = self.get_bits_per_pixel() {
bpp.saturating_add(7) / 8
} else {
1
}
}
pub fn get_data(&self, array_layer: u32) -> Result<&[u8], Error> {
let (offset, size) = self.get_offset_and_size(array_layer)?;
let offset = offset as usize;
let size = size as usize;
let end = offset.checked_add(size).ok_or(Error::OutOfBounds)?;
self.data.as_ref().get(offset..end).ok_or(Error::OutOfBounds)
}
fn get_offset_and_size(&self, array_layer: u32) -> Result<(u32, u32), Error> {
if array_layer >= self.get_num_array_layers() {
return Err(Error::OutOfBounds);
}
let array_stride = self.get_array_stride()?;
let offset = array_layer
.checked_mul(array_stride)
.ok_or(Error::OutOfBounds)?;
Ok((offset, array_stride))
}
}
impl<D: AsRef<[u8]> + AsMut<[u8]>> DdsBase<D> {
pub fn get_mut_data(&mut self, array_layer: u32) -> Result<&mut [u8], Error> {
let (offset, size) = self.get_offset_and_size(array_layer)?;
let offset = offset as usize;
let size = size as usize;
let end = offset.checked_add(size).ok_or(Error::OutOfBounds)?;
self.data
.as_mut()
.get_mut(offset..end)
.ok_or(Error::OutOfBounds)
}
}
fn read_headers<R: Read>(r: &mut R) -> Result<(Header, Option<Header10>), Error> {
let magic = r.read_u32::<LittleEndian>()?;
if magic != Dds::MAGIC {
return Err(Error::BadMagicNumber);
}
let header = Header::read(&mut *r)?;
let header10 = if header.spf.fourcc == Some(FourCC(<FourCC>::DX10)) {
Some(Header10::read(&mut *r)?)
} else {
None
};
Ok((header, header10))
}
fn read_payload<R: Read>(r: R, data: &mut Vec<u8>, max_data_len: Option<usize>) -> Result<(), Error> {
data.clear();
match max_data_len {
None => {
let mut r = r;
r.read_to_end(data)?;
}
Some(limit) => {
let mut capped = r.take(limit as u64 + 1);
capped.read_to_end(data)?;
if data.len() > limit {
return Err(Error::SizeLimitExceeded {
limit,
at_least: data.len(),
});
}
}
}
Ok(())
}
fn get_texture_size(
pitch: Option<u32>,
linear_size: Option<u32>,
pitch_height: u32,
height: u32,
depth: Option<u32>,
) -> Option<u32> {
let depth = depth.unwrap_or(1);
if let Some(ls) = linear_size {
return Some(ls);
}
let pitch = pitch?;
if pitch_height == 0 {
return None;
}
let row_height = height.checked_add(pitch_height - 1)? / pitch_height;
pitch.checked_mul(row_height)?.checked_mul(depth)
}
fn get_array_stride(texture_size: u32, min_mipmap_size: u32, mipmap_levels: u32) -> u32 {
let mut stride: u32 = 0;
let mut current_mipsize: u32 = texture_size;
let mut level: u32 = 0;
while level < mipmap_levels {
stride = stride.saturating_add(current_mipsize);
level += 1;
current_mipsize /= 4;
if current_mipsize <= min_mipmap_size {
let remaining = mipmap_levels - level;
return stride.saturating_add(remaining.saturating_mul(min_mipmap_size));
}
}
stride
}
impl<D: AsRef<[u8]>> fmt::Debug for DdsBase<D> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "Dds:")?;
if let Some(d3dformat) = self.get_d3d_format() {
writeln!(f, " Format: {:?}", d3dformat)?;
} else if let Some(dxgiformat) = self.get_dxgi_format() {
writeln!(f, " Format: {:?}", dxgiformat)?;
} else if let Some(ref fourcc) = self.header.spf.fourcc {
writeln!(f, " Format: FOURCC={:?} (Unknown)", fourcc)?;
} else {
writeln!(f, " Format UNSPECIFIED")?;
}
write!(f, "{:?}", self.header)?;
if let Some(ref h10) = self.header10 {
write!(f, "{:?}", h10)?;
}
writeln!(f, " (data elided)")?;
Ok(())
}
}