WIP: Implement texture files #191
2 changed files with 220 additions and 12 deletions
|
@ -14,6 +14,8 @@ use crate::bundle::file::UserFile;
|
||||||
use crate::murmur::{HashGroup, IdString32, IdString64};
|
use crate::murmur::{HashGroup, IdString32, IdString64};
|
||||||
use crate::{BundleFile, BundleFileType, BundleFileVariant};
|
use crate::{BundleFile, BundleFileType, BundleFileVariant};
|
||||||
|
|
||||||
|
mod dds;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
struct TextureDefinition {
|
struct TextureDefinition {
|
||||||
common: TextureDefinitionPlatform,
|
common: TextureDefinitionPlatform,
|
||||||
|
@ -53,6 +55,7 @@ struct TextureHeader {
|
||||||
n_streamable_mipmaps: u32,
|
n_streamable_mipmaps: u32,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
mip_info_size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextureHeader {
|
impl TextureHeader {
|
||||||
|
@ -66,18 +69,19 @@ impl TextureHeader {
|
||||||
let width = r.read_u32()?;
|
let width = r.read_u32()?;
|
||||||
let height = r.read_u32()?;
|
let height = r.read_u32()?;
|
||||||
|
|
||||||
// Don't quite know yet what this is, only that it is related to mipmaps.
|
r.skip_u32(0)?;
|
||||||
// The reference to "streamable mipmaps" comes from VT2, so far.
|
|
||||||
// As such, it might be related to the stream file, but since all texture files have it,
|
// A section of 15 pairs of two u32
|
||||||
// The engine calculates some offset and then moves 68 bytes at that offset to the beginning.
|
r.seek(SeekFrom::Current(2 * 4 * 15))?;
|
||||||
// Hence the split between `68` and `60` in the length.
|
|
||||||
r.seek(SeekFrom::Current(68 + 60))?;
|
let mip_info_size = r.read_u32()?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
flags,
|
flags,
|
||||||
n_streamable_mipmaps,
|
n_streamable_mipmaps,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
mip_info_size,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,9 +98,12 @@ impl TextureHeader {
|
||||||
w.write_u32(self.height)?;
|
w.write_u32(self.height)?;
|
||||||
|
|
||||||
// See `from_binary` about this unknown section.
|
// See `from_binary` about this unknown section.
|
||||||
let buf = [0; 148];
|
let buf = [0; (2 * 4 * 15) + 4];
|
||||||
w.write_all(&buf)?;
|
w.write_all(&buf)?;
|
||||||
|
|
||||||
|
// TODO: For now we write `0` here, until the mipmap section is figured out
|
||||||
|
w.write_u32(0)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,12 +161,10 @@ impl Texture {
|
||||||
|
|
||||||
let header = TextureHeader::from_binary(&mut r)?;
|
let header = TextureHeader::from_binary(&mut r)?;
|
||||||
|
|
||||||
let meta_size = r.read_u32()?;
|
|
||||||
|
|
||||||
eyre::ensure!(
|
eyre::ensure!(
|
||||||
meta_size == 0 || stream_r.is_some(),
|
header.mip_info_size == 0 || stream_r.is_some(),
|
||||||
"Compression chunks and stream file don't match up. meta_size = {}, stream = {}",
|
"Compression chunks and stream file don't match up. mip_info_size = {}, has_stream = {}",
|
||||||
meta_size,
|
header.mip_info_size,
|
||||||
stream_r.is_some()
|
stream_r.is_some()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
203
lib/sdk/src/filetype/texture/dds.rs
Normal file
203
lib/sdk/src/filetype/texture/dds.rs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
use bitflags::bitflags;
|
||||||
|
use color_eyre::eyre;
|
||||||
|
use color_eyre::Result;
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct DDSD: u32 {
|
||||||
|
/// Required
|
||||||
|
const CAPS = 0x1;
|
||||||
|
/// Required
|
||||||
|
const HEIGHT = 0x2;
|
||||||
|
/// Required
|
||||||
|
const WIDTH = 0x4;
|
||||||
|
/// Pitch for an uncompressed texture
|
||||||
|
const PITCH = 0x8;
|
||||||
|
/// Required
|
||||||
|
const PIXELFORMAT = 0x1000;
|
||||||
|
/// Required in a mipmapped texture
|
||||||
|
const MIPMAPCOUNT = 0x20000;
|
||||||
|
/// Pitch for a compressed texture
|
||||||
|
const LINEARSIZE = 0x80000;
|
||||||
|
/// Required in a depth texture
|
||||||
|
const DEPTH = 0x800000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct DDSCAPS: u32 {
|
||||||
|
const COMPLEX = 0x8;
|
||||||
|
const MIPMAP = 0x400000;
|
||||||
|
const TEXTURE = 0x1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct DDSCAPS2: u32 {
|
||||||
|
const CUBEMAP = 0x200;
|
||||||
|
const CUBEMAP_POSITIVEX = 0x400;
|
||||||
|
const CUBEMAP_NEGATIVEX = 0x800;
|
||||||
|
const CUBEMAP_POSITIVEY = 0x1000;
|
||||||
|
const CUBEMAP_NEGATIVEY = 0x2000;
|
||||||
|
const CUBEMAP_POSITIVEZ = 0x4000;
|
||||||
|
const CUBEMAP_NEGATIVEZ = 0x8000;
|
||||||
|
const VOLUME = 0x200000;
|
||||||
|
|
||||||
|
const CUBEMAP_ALLFACES = Self::CUBEMAP_POSITIVEX.bits()
|
||||||
|
| Self::CUBEMAP_NEGATIVEX.bits()
|
||||||
|
| Self::CUBEMAP_POSITIVEY.bits()
|
||||||
|
| Self::CUBEMAP_NEGATIVEY.bits()
|
||||||
|
| Self::CUBEMAP_POSITIVEZ.bits()
|
||||||
|
| Self::CUBEMAP_NEGATIVEZ.bits();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct DDPF: u32 {
|
||||||
|
const ALPHAPIXELS = 0x1;
|
||||||
|
const ALPHA = 0x2;
|
||||||
|
const FOURCC = 0x4;
|
||||||
|
const RGB = 0x40;
|
||||||
|
const YUV = 0x200;
|
||||||
|
const LUMINANCE = 0x20000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct DdsResourceMiscFlags: u32 {
|
||||||
|
const TEXTURECUBE = 0x4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum D3D10ResourceDimension {
|
||||||
|
Unknown = 0,
|
||||||
|
Buffer = 1,
|
||||||
|
Texture1D = 2,
|
||||||
|
Texture2D = 3,
|
||||||
|
Texture3D = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Dx10Header {
|
||||||
|
/// Resource data formats, including fully-typed and typeless formats.
|
||||||
|
/// See https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
|
||||||
|
dxgi_format: u32,
|
||||||
|
resource_dimension: D3D10ResourceDimension,
|
||||||
|
misc_flag: DdsResourceMiscFlags,
|
||||||
|
array_size: u32,
|
||||||
|
misc_flags2: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DDSPixelFormat {
|
||||||
|
/// Structure size. Must be `32`.
|
||||||
|
size: u32,
|
||||||
|
flags: DDPF,
|
||||||
|
four_cc: u32,
|
||||||
|
rgb_bit_count: u32,
|
||||||
|
r_bit_mask: u32,
|
||||||
|
g_bit_mask: u32,
|
||||||
|
b_bit_mask: u32,
|
||||||
|
a_bit_mask: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DDSHeader {
|
||||||
|
/// Size of this structure. Must be `124`.
|
||||||
|
size: u32,
|
||||||
|
/// Flags to indicate which members contain valid data.
|
||||||
|
flags: DDSD,
|
||||||
|
height: u32,
|
||||||
|
width: u32,
|
||||||
|
pitch_or_linear_size: u32,
|
||||||
|
depth: u32,
|
||||||
|
mipmap_count: u32,
|
||||||
|
reserved_1: [u8; 11],
|
||||||
|
pixel_format: DDSPixelFormat,
|
||||||
|
caps: DDSCAPS,
|
||||||
|
caps_2: DDSCAPS2,
|
||||||
|
reserved_2: [u8; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ImageType {
|
||||||
|
Image2D = 0,
|
||||||
|
Image3D = 1,
|
||||||
|
ImageCube = 2,
|
||||||
|
Unknown = 3,
|
||||||
|
Image2dArray = 4,
|
||||||
|
ImagecubeArray = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A stripped version of `ImageType` that only contains just the data needed
|
||||||
|
/// to read a DDS image stream.
|
||||||
|
pub struct StrippedImageFormat {
|
||||||
|
pub image_type: ImageType,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub layers: u32,
|
||||||
|
pub mip_levels: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a stripped down version of the logic that the engine implements to fill
|
||||||
|
// `stingray::ImageFormat`. With the `type` field we need to distinguish between `IMAGE3D`
|
||||||
|
// and everything else, and we need the various dimensions filled to calculate the chunks.
|
||||||
|
pub fn stripped_format_from_header(
|
||||||
|
dds_header: &DDSHeader,
|
||||||
|
dx10_header: &Dx10Header,
|
||||||
|
) -> Result<StrippedImageFormat> {
|
||||||
|
let mut image_format = StrippedImageFormat {
|
||||||
|
image_type: ImageType::Unknown,
|
||||||
|
width: dds_header.width,
|
||||||
|
height: dds_header.height,
|
||||||
|
layers: 0,
|
||||||
|
mip_levels: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if dds_header.mipmap_count > 0 {
|
||||||
|
image_format.mip_levels = dds_header.mipmap_count;
|
||||||
|
} else {
|
||||||
|
image_format.mip_levels = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: These next two sections are conditional in the engine code,
|
||||||
|
// based on a lot of stuff in "fourcc" and other fields. But it might
|
||||||
|
// actually be fine to just do it like this, as this seems universal
|
||||||
|
// to DDS.
|
||||||
|
// Will have to check how it plays out with actual assets.
|
||||||
|
|
||||||
|
if dds_header.caps_2.contains(DDSCAPS2::CUBEMAP) {
|
||||||
|
image_format.image_type = ImageType::ImageCube;
|
||||||
|
image_format.layers = 6;
|
||||||
|
} else if dds_header.caps_2.contains(DDSCAPS2::VOLUME) {
|
||||||
|
image_format.image_type = ImageType::Image3D;
|
||||||
|
image_format.layers = dds_header.depth;
|
||||||
|
} else {
|
||||||
|
image_format.image_type = ImageType::Image2D;
|
||||||
|
image_format.layers = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if dx10_header.resource_dimension == D3D10ResourceDimension::Texture2D {
|
||||||
|
if dx10_header.misc_flag == DdsResourceMiscFlags::TextureCube {
|
||||||
|
image_format.image_type = ImageType::ImageCube;
|
||||||
|
if dx10_header.array_size > 1 {
|
||||||
|
image_format.layers = dx10_header.array_size;
|
||||||
|
} else {
|
||||||
|
image_format.layers = 6;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
image_format.image_type = ImageType::Image2D;
|
||||||
|
image_format.layers = dx10_header.array_size;
|
||||||
|
}
|
||||||
|
} else if dx10_header.resource_dimension == D3D10ResourceDimension::Texture3D {
|
||||||
|
image_format.image_type = ImageType::Image3D;
|
||||||
|
image_format.layers = dds_header.depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if dx10_header.array_size > 1 {
|
||||||
|
match image_format.image_type {
|
||||||
|
ImageType::Image2D => image_format.image_type = ImageType::Image2dArray,
|
||||||
|
ImageType::ImageCube => image_format.image_type = ImageType::ImagecubeArray,
|
||||||
|
ImageType::Image3D => {
|
||||||
|
eyre::bail!("3D-Arrays are not a supported image format")
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(image_format)
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue