WIP: Implement texture files #191

Draft
lucas wants to merge 14 commits from feat/textures into master
2 changed files with 220 additions and 12 deletions
Showing only changes of commit 30b9a93fa3 - Show all commits

View file

@ -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()
);

View 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)
}