WIP: Implement texture files #191
6 changed files with 645 additions and 107 deletions
42
Cargo.lock
generated
42
Cargo.lock
generated
|
@ -2308,6 +2308,17 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
|
@ -3066,6 +3077,12 @@ dependencies = [
|
|||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
version = "0.6.0"
|
||||
|
@ -3157,11 +3174,14 @@ dependencies = [
|
|||
"glob",
|
||||
"luajit2-sys",
|
||||
"nanorand",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"oodle",
|
||||
"path-slash",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_sjson",
|
||||
"strum",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
|
@ -3421,6 +3441,28 @@ version = "0.11.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
|
|
|
@ -38,6 +38,8 @@ luajit2-sys = { path = "lib/luajit2-sys" }
|
|||
minijinja = { version = "2.0.1", default-features = false }
|
||||
nanorand = "0.7.0"
|
||||
nexusmods = { path = "lib/nexusmods" }
|
||||
num-derive = "0.4.2"
|
||||
num-traits = "0.2.19"
|
||||
notify = "8.0.0"
|
||||
oodle = { path = "lib/oodle" }
|
||||
open = "5.0.1"
|
||||
|
@ -50,6 +52,7 @@ serde = { version = "1.0.152", features = ["derive", "rc"] }
|
|||
serde_sjson = { path = "lib/serde_sjson" }
|
||||
steamlocate = "2.0.0-beta.2"
|
||||
strip-ansi-escapes = "0.2.0"
|
||||
strum = { version = "0.26.3", features = ["derive", "strum_macros"] }
|
||||
time = { version = "0.3.20", features = ["serde", "serde-well-known", "local-offset", "formatting", "macros"] }
|
||||
tokio = { version = "1.23.0", features = ["rt-multi-thread", "fs", "process", "macros", "tracing", "io-util", "io-std"] }
|
||||
tokio-stream = { version = "0.1.12", features = ["fs", "io-util"] }
|
||||
|
|
|
@ -15,11 +15,14 @@ futures-util = { workspace = true }
|
|||
glob = { workspace = true }
|
||||
luajit2-sys = { workspace = true }
|
||||
nanorand = { workspace = true }
|
||||
num-derive = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
oodle = { workspace = true }
|
||||
path-slash = { workspace = true }
|
||||
pin-project-lite = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_sjson = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write as _};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use color_eyre::eyre::Context;
|
||||
use color_eyre::{eyre, SectionExt};
|
||||
use color_eyre::{Help, Result};
|
||||
use num_traits::ToPrimitive as _;
|
||||
use oodle::{OodleLZ_CheckCRC, OodleLZ_FuzzSafe};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::binary::sync::{ReadExt, WriteExt};
|
||||
use crate::bundle::file::UserFile;
|
||||
use crate::filetype::texture::dds::{DXGIFormat, ImageType};
|
||||
use crate::murmur::{HashGroup, IdString32, IdString64};
|
||||
use crate::{BundleFile, BundleFileType, BundleFileVariant};
|
||||
|
||||
|
@ -49,13 +51,20 @@ bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
struct TextureHeaderMipInfo {
|
||||
offset: usize,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TextureHeader {
|
||||
flags: TextureFlags,
|
||||
n_streamable_mipmaps: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
mip_info_size: u32,
|
||||
n_streamable_mipmaps: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
mip_infos: [TextureHeaderMipInfo; 16],
|
||||
meta_size: usize,
|
||||
}
|
||||
|
||||
impl TextureHeader {
|
||||
|
@ -65,23 +74,26 @@ impl TextureHeader {
|
|||
TextureFlags::from_bits(bits)
|
||||
.ok_or_else(|| eyre::eyre!("Unknown bits set in TextureFlags: {:032b}", bits))
|
||||
})?;
|
||||
let n_streamable_mipmaps = r.read_u32()?;
|
||||
let width = r.read_u32()?;
|
||||
let height = r.read_u32()?;
|
||||
let n_streamable_mipmaps = r.read_u32()? as usize;
|
||||
let width = r.read_u32()? as usize;
|
||||
let height = r.read_u32()? as usize;
|
||||
|
||||
r.skip_u32(0)?;
|
||||
let mut mip_infos = [TextureHeaderMipInfo::default(); 16];
|
||||
|
||||
// A section of 15 pairs of two u32
|
||||
r.seek(SeekFrom::Current(2 * 4 * 15))?;
|
||||
for info in mip_infos.iter_mut() {
|
||||
info.offset = r.read_u32()? as usize;
|
||||
info.size = r.read_u32()? as usize;
|
||||
}
|
||||
|
||||
let mip_info_size = r.read_u32()?;
|
||||
let meta_size = r.read_u32()? as usize;
|
||||
|
||||
Ok(Self {
|
||||
flags,
|
||||
n_streamable_mipmaps,
|
||||
width,
|
||||
height,
|
||||
mip_info_size,
|
||||
mip_infos,
|
||||
meta_size,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -93,15 +105,16 @@ impl TextureHeader {
|
|||
);
|
||||
|
||||
w.write_u32(self.flags.bits())?;
|
||||
w.write_u32(self.n_streamable_mipmaps)?;
|
||||
w.write_u32(self.width)?;
|
||||
w.write_u32(self.height)?;
|
||||
w.write_u32(self.n_streamable_mipmaps as u32)?;
|
||||
w.write_u32(self.width as u32)?;
|
||||
w.write_u32(self.height as u32)?;
|
||||
|
||||
// See `from_binary` about this unknown section.
|
||||
let buf = [0; (2 * 4 * 15) + 4];
|
||||
w.write_all(&buf)?;
|
||||
for info in self.mip_infos {
|
||||
w.write_u32(info.offset as u32)?;
|
||||
w.write_u32(info.size as u32)?;
|
||||
}
|
||||
|
||||
// TODO: For now we write `0` here, until the mipmap section is figured out
|
||||
// TODO: For now we write `0` here, until the meta section is figured out
|
||||
w.write_u32(0)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -117,6 +130,90 @@ struct Texture {
|
|||
}
|
||||
|
||||
impl Texture {
|
||||
#[tracing::instrument(skip(data, chunks))]
|
||||
fn decompress_stream_data(mut data: impl Read, chunks: impl AsRef<[usize]>) -> Result<Vec<u8>> {
|
||||
const RAW_SIZE: usize = 0x10000;
|
||||
|
||||
let chunks = chunks.as_ref();
|
||||
|
||||
let max_size = chunks.iter().max().copied().unwrap_or(RAW_SIZE);
|
||||
let mut read_buf = vec![0; max_size];
|
||||
|
||||
let mut stream_raw = Vec::with_capacity(chunks.iter().sum());
|
||||
let mut last = 0;
|
||||
|
||||
for offset_next in chunks {
|
||||
let size = offset_next - last;
|
||||
|
||||
let span = tracing::info_span!(
|
||||
"stream chunk",
|
||||
num_chunks = chunks.len(),
|
||||
chunk_size_comp = size,
|
||||
offset = last
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
let buf = &mut read_buf[0..size];
|
||||
data.read_exact(buf)
|
||||
.wrap_err("Failed to read chunk from stream file")?;
|
||||
|
||||
let raw = oodle::decompress(buf, RAW_SIZE, OodleLZ_FuzzSafe::No, OodleLZ_CheckCRC::No)
|
||||
.wrap_err("Failed to decompress stream chunk")?;
|
||||
eyre::ensure!(
|
||||
raw.len() == RAW_SIZE,
|
||||
"Invalid chunk length after decompression"
|
||||
);
|
||||
|
||||
stream_raw.extend_from_slice(&raw);
|
||||
|
||||
last = *offset_next;
|
||||
}
|
||||
Ok(stream_raw)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(data), fields(data_len = data.as_ref().len()))]
|
||||
fn reorder_stream_mipmap(
|
||||
data: impl AsRef<[u8]>,
|
||||
bits_per_block: usize,
|
||||
bytes_per_block: usize,
|
||||
block_size: usize,
|
||||
pitch: usize,
|
||||
) -> Result<Vec<u8>> {
|
||||
const CHUNK_SIZE: usize = 0x10000;
|
||||
let data = data.as_ref();
|
||||
|
||||
let mut out = Vec::with_capacity(data.len());
|
||||
let mut window = vec![0u8; pitch * 64];
|
||||
|
||||
let row_size = bits_per_block * block_size;
|
||||
tracing::Span::current().record("row_size", row_size);
|
||||
|
||||
eyre::ensure!(
|
||||
data.len() % CHUNK_SIZE == 0,
|
||||
"Stream data does not divide evenly into chunks"
|
||||
);
|
||||
|
||||
for (i, chunk) in data.chunks_exact(CHUNK_SIZE).enumerate() {
|
||||
let chunk_x = (i % bytes_per_block) * row_size;
|
||||
|
||||
let span = tracing::trace_span!("chunk", i, chunk_x = chunk_x);
|
||||
let _guard = span.enter();
|
||||
|
||||
if i > 0 && i % bytes_per_block == 0 {
|
||||
out.extend_from_slice(&window);
|
||||
}
|
||||
|
||||
for (j, row) in chunk.chunks_exact(row_size).enumerate() {
|
||||
let start = chunk_x + j * pitch;
|
||||
let end = start + row_size;
|
||||
tracing::trace!("{i}/{j} at {}:{}", start, end);
|
||||
window[start..end].copy_from_slice(row);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(ctx, r, stream_r))]
|
||||
fn from_binary(
|
||||
ctx: &crate::Context,
|
||||
|
@ -162,19 +259,19 @@ impl Texture {
|
|||
let header = TextureHeader::from_binary(&mut r)?;
|
||||
|
||||
eyre::ensure!(
|
||||
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,
|
||||
header.meta_size == 0 || stream_r.is_some(),
|
||||
"Compression chunks and stream file don't match up. meta_size = {}, has_stream = {}",
|
||||
header.meta_size,
|
||||
stream_r.is_some()
|
||||
);
|
||||
|
||||
let stream = if let Some(stream_r) = stream_r.as_mut() {
|
||||
// Number of compression chunks in the stream file
|
||||
let num_chunks = r.read_u32()?;
|
||||
let num_chunks = r.read_u32()? as usize;
|
||||
r.skip_u16(0)?;
|
||||
|
||||
{
|
||||
let num_chunks_1 = r.read_u16()? as u32;
|
||||
let num_chunks_1 = r.read_u16()? as usize;
|
||||
|
||||
eyre::ensure!(
|
||||
num_chunks == num_chunks_1,
|
||||
|
@ -184,37 +281,15 @@ impl Texture {
|
|||
);
|
||||
}
|
||||
|
||||
const RAW_SIZE: usize = 0x10000;
|
||||
let mut stream_raw = Vec::new();
|
||||
let mut last = 0;
|
||||
let mut chunks = Vec::with_capacity(num_chunks);
|
||||
|
||||
for i in 0..num_chunks {
|
||||
let offset_next = r.read_u32()? as usize;
|
||||
let size = offset_next - last;
|
||||
|
||||
let span = tracing::info_span!(
|
||||
"read stream chunk",
|
||||
num_chunks,
|
||||
i,
|
||||
chunk_size = size,
|
||||
offset = last
|
||||
);
|
||||
let _enter = span.enter();
|
||||
|
||||
let mut buf = vec![0; size];
|
||||
stream_r
|
||||
.read_exact(&mut buf)
|
||||
.wrap_err("Failed to read chunk from stream file")?;
|
||||
|
||||
let raw =
|
||||
oodle::decompress(&buf, RAW_SIZE, OodleLZ_FuzzSafe::No, OodleLZ_CheckCRC::No)
|
||||
.wrap_err("Failed to decompress stream chunk")?;
|
||||
|
||||
stream_raw.extend_from_slice(&raw);
|
||||
|
||||
last = offset_next;
|
||||
for _ in 0..num_chunks {
|
||||
chunks.push(r.read_u32()? as usize);
|
||||
}
|
||||
|
||||
let stream_raw = Self::decompress_stream_data(stream_r, chunks)
|
||||
.wrap_err("Failed to decompress stream data")?;
|
||||
|
||||
Some(stream_raw)
|
||||
} else {
|
||||
None
|
||||
|
@ -247,10 +322,6 @@ impl Texture {
|
|||
|
||||
self.header.to_binary(&mut w)?;
|
||||
|
||||
// More data not fully figured out, yet.
|
||||
let meta_size = 0;
|
||||
w.write_u32(meta_size)?;
|
||||
|
||||
w.write_u32(self.category.to_murmur32().into())?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -268,19 +339,9 @@ impl Texture {
|
|||
serde_sjson::to_string(&texture).wrap_err("Failed to serialize texture definition")
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn to_user_files(&self, name: String) -> Result<Vec<UserFile>> {
|
||||
let mut files = Vec::with_capacity(3);
|
||||
|
||||
// TODO: Don't clone.
|
||||
|
||||
if let Some(stream) = &self.stream {
|
||||
let stream_name = PathBuf::from(&name).with_extension("stream");
|
||||
files.push(UserFile::with_name(
|
||||
stream.clone(),
|
||||
stream_name.display().to_string(),
|
||||
));
|
||||
}
|
||||
let mut files = Vec::with_capacity(2);
|
||||
|
||||
{
|
||||
let data = self.to_sjson(name.clone())?.as_bytes().to_vec();
|
||||
|
@ -291,7 +352,119 @@ impl Texture {
|
|||
files.push(UserFile::with_name(data, name));
|
||||
}
|
||||
|
||||
// For debugging purposes, also extract the raw files
|
||||
if cfg!(debug_assertions) {
|
||||
if let Some(stream) = &self.stream {
|
||||
let stream_name = PathBuf::from(&name).with_extension("stream");
|
||||
files.push(UserFile::with_name(
|
||||
stream.clone(),
|
||||
stream_name.display().to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let name = PathBuf::from(&name)
|
||||
.with_extension("raw.dds")
|
||||
.display()
|
||||
.to_string();
|
||||
files.push(UserFile::with_name(self.data.clone(), name));
|
||||
}
|
||||
|
||||
{
|
||||
let mut data = Cursor::new(&self.data);
|
||||
let mut dds_header =
|
||||
dds::DDSHeader::from_binary(&mut data).wrap_err("Failed to read DDS header")?;
|
||||
|
||||
eyre::ensure!(
|
||||
dds_header.pixel_format.flags.contains(dds::DDPF::FOURCC)
|
||||
&& dds_header.pixel_format.four_cc == dds::FOURCC_DX10,
|
||||
"Only DX10 textures are currently supported."
|
||||
);
|
||||
|
||||
let dx10_header =
|
||||
dds::Dx10Header::from_binary(&mut data).wrap_err("Failed to read DX10 header")?;
|
||||
|
||||
match dx10_header.dxgi_format {
|
||||
DXGIFormat::BC1_UNORM
|
||||
| DXGIFormat::BC3_UNORM
|
||||
| DXGIFormat::BC4_UNORM
|
||||
| DXGIFormat::BC5_UNORM
|
||||
| DXGIFormat::BC6H_UF16
|
||||
| DXGIFormat::BC7_UNORM => {}
|
||||
_ => {
|
||||
eyre::bail!(
|
||||
"Unsupported DXGI format: {} (0x{:0X})",
|
||||
dx10_header.dxgi_format,
|
||||
dx10_header.dxgi_format.to_u32().unwrap_or_default()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let stingray_image_format =
|
||||
dds::stripped_format_from_header(&dds_header, &dx10_header)?;
|
||||
eyre::ensure!(
|
||||
stingray_image_format.image_type == ImageType::Image2D,
|
||||
"Unsupported image type: {}",
|
||||
stingray_image_format.image_type,
|
||||
);
|
||||
|
||||
let block_size = 4 * dds_header.pitch_or_linear_size / dds_header.width;
|
||||
let bits_per_block: usize = match block_size {
|
||||
8 => 128,
|
||||
16 => 64,
|
||||
block_size => eyre::bail!("Unsupported block size {}", block_size),
|
||||
};
|
||||
|
||||
let pitch = self.header.width / 4 * block_size;
|
||||
let bytes_per_block = self.header.width / bits_per_block / 4;
|
||||
|
||||
tracing::debug!(
|
||||
"block_size = {} | pitch = {} | bits_per_block = {} | bytes_per_block = {}",
|
||||
block_size,
|
||||
pitch,
|
||||
bits_per_block,
|
||||
bytes_per_block
|
||||
);
|
||||
|
||||
let mut out_data = Cursor::new(Vec::with_capacity(self.data.len()));
|
||||
|
||||
// Currently, we only extract the largest mipmap,
|
||||
// so we need to set the dimensions accordingly, and remove the
|
||||
// flag.
|
||||
dds_header.width = self.header.width;
|
||||
dds_header.height = self.header.height;
|
||||
dds_header.mipmap_count = 0;
|
||||
dds_header.flags &= !dds::DDSD::MIPMAPCOUNT;
|
||||
|
||||
dds_header
|
||||
.to_binary(&mut out_data)
|
||||
.wrap_err("Failed to write DDS header")?;
|
||||
|
||||
dx10_header
|
||||
.to_binary(&mut out_data)
|
||||
.wrap_err("Failed to write DX10 header")?;
|
||||
|
||||
if let Some(stream) = &self.stream {
|
||||
let data = Self::reorder_stream_mipmap(
|
||||
stream,
|
||||
bits_per_block,
|
||||
bytes_per_block,
|
||||
block_size,
|
||||
pitch,
|
||||
)
|
||||
.wrap_err("Failed to reorder stream chunks")?;
|
||||
|
||||
out_data
|
||||
.write_all(&data)
|
||||
.wrap_err("Failed to write streamed mipmap data")?;
|
||||
} else {
|
||||
out_data
|
||||
.write_all(data.remaining_slice())
|
||||
.wrap_err("Failed to write texture data")?;
|
||||
};
|
||||
|
||||
files.push(UserFile::with_name(out_data.into_inner(), name));
|
||||
}
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
}
|
||||
|
@ -388,8 +561,8 @@ pub async fn compile(
|
|||
|
||||
r.seek(SeekFrom::Current(5))?;
|
||||
|
||||
let width = r.read_u16()? as u32;
|
||||
let height = r.read_u16()? as u32;
|
||||
let width = r.read_u32()? as usize;
|
||||
let height = r.read_u32()? as usize;
|
||||
|
||||
(width, height)
|
||||
};
|
||||
|
@ -403,6 +576,8 @@ pub async fn compile(
|
|||
n_streamable_mipmaps: 0,
|
||||
width,
|
||||
height,
|
||||
mip_infos: [TextureHeaderMipInfo::default(); 16],
|
||||
meta_size: 0,
|
||||
},
|
||||
data: dds,
|
||||
stream: None,
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
use std::io::SeekFrom;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::Context as _;
|
||||
use color_eyre::eyre::{self, OptionExt as _};
|
||||
use color_eyre::Result;
|
||||
use num_derive::{FromPrimitive, ToPrimitive};
|
||||
use num_traits::{FromPrimitive as _, ToPrimitive as _};
|
||||
|
||||
use crate::binary::sync::{ReadExt, WriteExt};
|
||||
|
||||
const MAGIC_DDS: u32 = 0x20534444;
|
||||
pub const FOURCC_DX10: u32 = 0x30315844;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
@ -65,7 +75,27 @@ bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
fn flags_from_bits<T: bitflags::Flags>(bits: T::Bits) -> T
|
||||
where
|
||||
<T as bitflags::Flags>::Bits: std::fmt::Binary,
|
||||
{
|
||||
if let Some(flags) = T::from_bits(bits) {
|
||||
flags
|
||||
} else {
|
||||
let unknown = bits & !T::all().bits();
|
||||
|
||||
tracing::warn!(
|
||||
"Unknown bits found for '{}': known = {:0b}, unknown = {:0b}",
|
||||
std::any::type_name::<T>(),
|
||||
T::all().bits(),
|
||||
unknown
|
||||
);
|
||||
|
||||
T::from_bits_truncate(bits)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||
pub enum D3D10ResourceDimension {
|
||||
Unknown = 0,
|
||||
Buffer = 1,
|
||||
|
@ -74,46 +104,325 @@ pub enum D3D10ResourceDimension {
|
|||
Texture3D = 4,
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Clone, Copy, Debug, strum::Display, FromPrimitive, ToPrimitive)]
|
||||
pub enum DXGIFormat {
|
||||
UNKNOWN = 0,
|
||||
R32G32B32A32_TYPELESS = 1,
|
||||
R32G32B32A32_FLOAT = 2,
|
||||
R32G32B32A32_UINT = 3,
|
||||
R32G32B32A32_SINT = 4,
|
||||
R32G32B32_TYPELESS = 5,
|
||||
R32G32B32_FLOAT = 6,
|
||||
R32G32B32_UINT = 7,
|
||||
R32G32B32_SINT = 8,
|
||||
R16G16B16A16_TYPELESS = 9,
|
||||
R16G16B16A16_FLOAT = 10,
|
||||
R16G16B16A16_UNORM = 11,
|
||||
R16G16B16A16_UINT = 12,
|
||||
R16G16B16A16_SNORM = 13,
|
||||
R16G16B16A16_SINT = 14,
|
||||
R32G32_TYPELESS = 15,
|
||||
R32G32_FLOAT = 16,
|
||||
R32G32_UINT = 17,
|
||||
R32G32_SINT = 18,
|
||||
R32G8X24_TYPELESS = 19,
|
||||
D32_FLOAT_S8X24_UINT = 20,
|
||||
R32_FLOAT_X8X24_TYPELESS = 21,
|
||||
X32_TYPELESS_G8X24_UINT = 22,
|
||||
R10G10B10A2_TYPELESS = 23,
|
||||
R10G10B10A2_UNORM = 24,
|
||||
R10G10B10A2_UINT = 25,
|
||||
R11G11B10_FLOAT = 26,
|
||||
R8G8B8A8_TYPELESS = 27,
|
||||
R8G8B8A8_UNORM = 28,
|
||||
R8G8B8A8_UNORM_SRGB = 29,
|
||||
R8G8B8A8_UINT = 30,
|
||||
R8G8B8A8_SNORM = 31,
|
||||
R8G8B8A8_SINT = 32,
|
||||
R16G16_TYPELESS = 33,
|
||||
R16G16_FLOAT = 34,
|
||||
R16G16_UNORM = 35,
|
||||
R16G16_UINT = 36,
|
||||
R16G16_SNORM = 37,
|
||||
R16G16_SINT = 38,
|
||||
R32_TYPELESS = 39,
|
||||
D32_FLOAT = 40,
|
||||
R32_FLOAT = 41,
|
||||
R32_UINT = 42,
|
||||
R32_SINT = 43,
|
||||
R24G8_TYPELESS = 44,
|
||||
D24_UNORM_S8_UINT = 45,
|
||||
R24_UNORM_X8_TYPELESS = 46,
|
||||
X24_TYPELESS_G8_UINT = 47,
|
||||
R8G8_TYPELESS = 48,
|
||||
R8G8_UNORM = 49,
|
||||
R8G8_UINT = 50,
|
||||
R8G8_SNORM = 51,
|
||||
R8G8_SINT = 52,
|
||||
R16_TYPELESS = 53,
|
||||
R16_FLOAT = 54,
|
||||
D16_UNORM = 55,
|
||||
R16_UNORM = 56,
|
||||
R16_UINT = 57,
|
||||
R16_SNORM = 58,
|
||||
R16_SINT = 59,
|
||||
R8_TYPELESS = 60,
|
||||
R8_UNORM = 61,
|
||||
R8_UINT = 62,
|
||||
R8_SNORM = 63,
|
||||
R8_SINT = 64,
|
||||
A8_UNORM = 65,
|
||||
R1_UNORM = 66,
|
||||
R9G9B9E5_SHAREDEXP = 67,
|
||||
R8G8_B8G8_UNORM = 68,
|
||||
G8R8_G8B8_UNORM = 69,
|
||||
BC1_TYPELESS = 70,
|
||||
BC1_UNORM = 71,
|
||||
BC1_UNORM_SRGB = 72,
|
||||
BC2_TYPELESS = 73,
|
||||
BC2_UNORM = 74,
|
||||
BC2_UNORM_SRGB = 75,
|
||||
BC3_TYPELESS = 76,
|
||||
BC3_UNORM = 77,
|
||||
BC3_UNORM_SRGB = 78,
|
||||
BC4_TYPELESS = 79,
|
||||
BC4_UNORM = 80,
|
||||
BC4_SNORM = 81,
|
||||
BC5_TYPELESS = 82,
|
||||
BC5_UNORM = 83,
|
||||
BC5_SNORM = 84,
|
||||
B5G6R5_UNORM = 85,
|
||||
B5G5R5A1_UNORM = 86,
|
||||
B8G8R8A8_UNORM = 87,
|
||||
B8G8R8X8_UNORM = 88,
|
||||
R10G10B10_XR_BIAS_A2_UNORM = 89,
|
||||
B8G8R8A8_TYPELESS = 90,
|
||||
B8G8R8A8_UNORM_SRGB = 91,
|
||||
B8G8R8X8_TYPELESS = 92,
|
||||
B8G8R8X8_UNORM_SRGB = 93,
|
||||
BC6H_TYPELESS = 94,
|
||||
BC6H_UF16 = 95,
|
||||
BC6H_SF16 = 96,
|
||||
BC7_TYPELESS = 97,
|
||||
BC7_UNORM = 98,
|
||||
BC7_UNORM_SRGB = 99,
|
||||
AYUV = 100,
|
||||
Y410 = 101,
|
||||
Y416 = 102,
|
||||
NV12 = 103,
|
||||
P010 = 104,
|
||||
P016 = 105,
|
||||
OPAQUE = 106,
|
||||
YUY2 = 107,
|
||||
Y210 = 108,
|
||||
Y216 = 109,
|
||||
NV11 = 110,
|
||||
AI44 = 111,
|
||||
IA44 = 112,
|
||||
P8 = 113,
|
||||
A8P8 = 114,
|
||||
B4G4R4A4_UNORM = 115,
|
||||
P208 = 130,
|
||||
V208 = 131,
|
||||
V408 = 132,
|
||||
SAMPLER_FEEDBACK_MIN_MIP_OPAQUE,
|
||||
SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
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 dxgi_format: DXGIFormat,
|
||||
pub resource_dimension: D3D10ResourceDimension,
|
||||
pub misc_flag: DdsResourceMiscFlags,
|
||||
pub array_size: usize,
|
||||
pub misc_flags2: u32,
|
||||
}
|
||||
|
||||
impl Dx10Header {
|
||||
#[tracing::instrument(skip(r))]
|
||||
pub fn from_binary(mut r: impl ReadExt) -> Result<Self> {
|
||||
let dxgi_format = r
|
||||
.read_u32()
|
||||
.map(|val| DXGIFormat::from_u32(val).unwrap_or(DXGIFormat::UNKNOWN))?;
|
||||
let resource_dimension = r.read_u32().map(|val| {
|
||||
D3D10ResourceDimension::from_u32(val).unwrap_or(D3D10ResourceDimension::Unknown)
|
||||
})?;
|
||||
let misc_flag = r.read_u32().map(flags_from_bits)?;
|
||||
let array_size = r.read_u32()? as usize;
|
||||
let misc_flags2 = r.read_u32()?;
|
||||
|
||||
Ok(Self {
|
||||
dxgi_format,
|
||||
resource_dimension,
|
||||
misc_flag,
|
||||
array_size,
|
||||
misc_flags2,
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(w))]
|
||||
pub fn to_binary(&self, mut w: impl WriteExt) -> Result<()> {
|
||||
w.write_u32(
|
||||
self.dxgi_format
|
||||
.to_u32()
|
||||
.ok_or_eyre("DXGIFormat should fit in a u32")?,
|
||||
)?;
|
||||
w.write_u32(
|
||||
self.resource_dimension
|
||||
.to_u32()
|
||||
.ok_or_eyre("DXGIFormat should fit in a u32")?,
|
||||
)?;
|
||||
w.write_u32(self.misc_flag.bits())?;
|
||||
w.write_u32(self.array_size as u32)?;
|
||||
w.write_u32(self.misc_flags2)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
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 flags: DDPF,
|
||||
pub four_cc: u32,
|
||||
pub rgb_bit_count: u32,
|
||||
pub r_bit_mask: u32,
|
||||
pub g_bit_mask: u32,
|
||||
pub b_bit_mask: u32,
|
||||
pub a_bit_mask: u32,
|
||||
}
|
||||
|
||||
impl DDSPixelFormat {
|
||||
#[tracing::instrument(skip(r))]
|
||||
pub fn from_binary(mut r: impl ReadExt) -> Result<Self> {
|
||||
let size = r.read_u32()? as usize;
|
||||
eyre::ensure!(
|
||||
size == 32,
|
||||
"Invalid structure size. Got 0X{:0X}, expected 0x20",
|
||||
size
|
||||
);
|
||||
|
||||
let flags = r.read_u32().map(flags_from_bits)?;
|
||||
let four_cc = r.read_u32()?;
|
||||
let rgb_bit_count = r.read_u32()?;
|
||||
let r_bit_mask = r.read_u32()?;
|
||||
let g_bit_mask = r.read_u32()?;
|
||||
let b_bit_mask = r.read_u32()?;
|
||||
let a_bit_mask = r.read_u32()?;
|
||||
|
||||
Ok(Self {
|
||||
flags,
|
||||
four_cc,
|
||||
rgb_bit_count,
|
||||
r_bit_mask,
|
||||
g_bit_mask,
|
||||
b_bit_mask,
|
||||
a_bit_mask,
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(w))]
|
||||
pub fn to_binary(&self, mut w: impl WriteExt) -> Result<()> {
|
||||
// Structure size
|
||||
w.write_u32(32)?;
|
||||
|
||||
w.write_u32(self.flags.bits())?;
|
||||
w.write_u32(self.four_cc)?;
|
||||
w.write_u32(self.rgb_bit_count)?;
|
||||
w.write_u32(self.r_bit_mask)?;
|
||||
w.write_u32(self.g_bit_mask)?;
|
||||
w.write_u32(self.b_bit_mask)?;
|
||||
w.write_u32(self.a_bit_mask)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
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],
|
||||
pub flags: DDSD,
|
||||
pub height: usize,
|
||||
pub width: usize,
|
||||
pub pitch_or_linear_size: usize,
|
||||
pub depth: usize,
|
||||
pub mipmap_count: usize,
|
||||
pub pixel_format: DDSPixelFormat,
|
||||
pub caps: DDSCAPS,
|
||||
pub caps_2: DDSCAPS2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
impl DDSHeader {
|
||||
#[tracing::instrument(skip(r))]
|
||||
pub fn from_binary(mut r: impl ReadExt) -> Result<Self> {
|
||||
r.skip_u32(MAGIC_DDS).wrap_err("Invalid magic bytes")?;
|
||||
|
||||
let size = r.read_u32()?;
|
||||
eyre::ensure!(
|
||||
size == 124,
|
||||
"Invalid structure size. Got 0x{:0X}, expected 0x7C",
|
||||
size
|
||||
);
|
||||
|
||||
let flags = r.read_u32().map(flags_from_bits)?;
|
||||
let height = r.read_u32()? as usize;
|
||||
let width = r.read_u32()? as usize;
|
||||
let pitch_or_linear_size = r.read_u32()? as usize;
|
||||
let depth = r.read_u32()? as usize;
|
||||
let mipmap_count = r.read_u32()? as usize;
|
||||
|
||||
// Skip reserved bytes
|
||||
r.seek(SeekFrom::Current(11 * 4))?;
|
||||
|
||||
let pixel_format = DDSPixelFormat::from_binary(&mut r)?;
|
||||
let caps = r.read_u32().map(flags_from_bits)?;
|
||||
let caps_2 = r.read_u32().map(flags_from_bits)?;
|
||||
|
||||
// Skip unused and reserved bytes
|
||||
r.seek(SeekFrom::Current(3 * 4))?;
|
||||
|
||||
Ok(Self {
|
||||
flags,
|
||||
height,
|
||||
width,
|
||||
pitch_or_linear_size,
|
||||
depth,
|
||||
mipmap_count,
|
||||
pixel_format,
|
||||
caps,
|
||||
caps_2,
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(w))]
|
||||
pub fn to_binary(&self, mut w: impl WriteExt) -> Result<()> {
|
||||
w.write_u32(MAGIC_DDS)?;
|
||||
|
||||
// Structure size in bytes
|
||||
w.write_u32(124)?;
|
||||
w.write_u32(self.flags.bits())?;
|
||||
w.write_u32(self.height as u32)?;
|
||||
w.write_u32(self.width as u32)?;
|
||||
w.write_u32(self.pitch_or_linear_size as u32)?;
|
||||
w.write_u32(self.depth as u32)?;
|
||||
w.write_u32(self.mipmap_count as u32)?;
|
||||
|
||||
w.write_all(&[0u8; 11 * 4])?;
|
||||
|
||||
self.pixel_format.to_binary(&mut w)?;
|
||||
w.write_u32(self.caps.bits())?;
|
||||
w.write_u32(self.caps_2.bits())?;
|
||||
|
||||
w.write_all(&[0u8; 3 * 4])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Display)]
|
||||
pub enum ImageType {
|
||||
Image2D = 0,
|
||||
Image3D = 1,
|
||||
|
@ -125,12 +434,14 @@ pub enum ImageType {
|
|||
|
||||
/// A stripped version of `ImageType` that only contains just the data needed
|
||||
/// to read a DDS image stream.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct StrippedImageFormat {
|
||||
pub image_type: ImageType,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub layers: u32,
|
||||
pub mip_levels: u32,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub layers: usize,
|
||||
pub mip_levels: usize,
|
||||
}
|
||||
|
||||
// This is a stripped down version of the logic that the engine implements to fill
|
||||
|
@ -172,7 +483,10 @@ pub fn stripped_format_from_header(
|
|||
}
|
||||
|
||||
if dx10_header.resource_dimension == D3D10ResourceDimension::Texture2D {
|
||||
if dx10_header.misc_flag == DdsResourceMiscFlags::TextureCube {
|
||||
if dx10_header
|
||||
.misc_flag
|
||||
.contains(DdsResourceMiscFlags::TEXTURECUBE)
|
||||
{
|
||||
image_format.image_type = ImageType::ImageCube;
|
||||
if dx10_header.array_size > 1 {
|
||||
image_format.layers = dx10_header.array_size;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#![feature(cursor_remaining)]
|
||||
#![feature(test)]
|
||||
|
||||
mod binary;
|
||||
|
|
Loading…
Add table
Reference in a new issue