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::{BundleFile, BundleFileType, BundleFileVariant};
|
||||
|
||||
mod dds;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
struct TextureDefinition {
|
||||
common: TextureDefinitionPlatform,
|
||||
|
@ -53,6 +55,7 @@ struct TextureHeader {
|
|||
n_streamable_mipmaps: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
mip_info_size: u32,
|
||||
}
|
||||
|
||||
impl TextureHeader {
|
||||
|
@ -66,18 +69,19 @@ impl TextureHeader {
|
|||
let width = r.read_u32()?;
|
||||
let height = r.read_u32()?;
|
||||
|
||||
// Don't quite know yet what this is, only that it is related to mipmaps.
|
||||
// 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,
|
||||
// The engine calculates some offset and then moves 68 bytes at that offset to the beginning.
|
||||
// Hence the split between `68` and `60` in the length.
|
||||
r.seek(SeekFrom::Current(68 + 60))?;
|
||||
r.skip_u32(0)?;
|
||||
|
||||
// A section of 15 pairs of two u32
|
||||
r.seek(SeekFrom::Current(2 * 4 * 15))?;
|
||||
|
||||
let mip_info_size = r.read_u32()?;
|
||||
|
||||
Ok(Self {
|
||||
flags,
|
||||
n_streamable_mipmaps,
|
||||
width,
|
||||
height,
|
||||
mip_info_size,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -94,9 +98,12 @@ impl TextureHeader {
|
|||
w.write_u32(self.height)?;
|
||||
|
||||
// See `from_binary` about this unknown section.
|
||||
let buf = [0; 148];
|
||||
let buf = [0; (2 * 4 * 15) + 4];
|
||||
w.write_all(&buf)?;
|
||||
|
||||
// TODO: For now we write `0` here, until the mipmap section is figured out
|
||||
w.write_u32(0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -154,12 +161,10 @@ impl Texture {
|
|||
|
||||
let header = TextureHeader::from_binary(&mut r)?;
|
||||
|
||||
let meta_size = r.read_u32()?;
|
||||
|
||||
eyre::ensure!(
|
||||
meta_size == 0 || stream_r.is_some(),
|
||||
"Compression chunks and stream file don't match up. meta_size = {}, stream = {}",
|
||||
meta_size,
|
||||
header.mip_info_size == 0 || stream_r.is_some(),
|
||||
"Compression chunks and stream file don't match up. mip_info_size = {}, has_stream = {}",
|
||||
header.mip_info_size,
|
||||
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