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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
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]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
|
@ -3066,6 +3077,12 @@ dependencies = [
|
||||||
"untrusted",
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustybuzz"
|
name = "rustybuzz"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -3157,11 +3174,14 @@ dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
"luajit2-sys",
|
"luajit2-sys",
|
||||||
"nanorand",
|
"nanorand",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
"oodle",
|
"oodle",
|
||||||
"path-slash",
|
"path-slash",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_sjson",
|
"serde_sjson",
|
||||||
|
"strum",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -3421,6 +3441,28 @@ version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
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]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
|
|
|
@ -38,6 +38,8 @@ luajit2-sys = { path = "lib/luajit2-sys" }
|
||||||
minijinja = { version = "2.0.1", default-features = false }
|
minijinja = { version = "2.0.1", default-features = false }
|
||||||
nanorand = "0.7.0"
|
nanorand = "0.7.0"
|
||||||
nexusmods = { path = "lib/nexusmods" }
|
nexusmods = { path = "lib/nexusmods" }
|
||||||
|
num-derive = "0.4.2"
|
||||||
|
num-traits = "0.2.19"
|
||||||
notify = "8.0.0"
|
notify = "8.0.0"
|
||||||
oodle = { path = "lib/oodle" }
|
oodle = { path = "lib/oodle" }
|
||||||
open = "5.0.1"
|
open = "5.0.1"
|
||||||
|
@ -50,6 +52,7 @@ serde = { version = "1.0.152", features = ["derive", "rc"] }
|
||||||
serde_sjson = { path = "lib/serde_sjson" }
|
serde_sjson = { path = "lib/serde_sjson" }
|
||||||
steamlocate = "2.0.0-beta.2"
|
steamlocate = "2.0.0-beta.2"
|
||||||
strip-ansi-escapes = "0.2.0"
|
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"] }
|
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 = { 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"] }
|
tokio-stream = { version = "0.1.12", features = ["fs", "io-util"] }
|
||||||
|
|
|
@ -15,11 +15,14 @@ futures-util = { workspace = true }
|
||||||
glob = { workspace = true }
|
glob = { workspace = true }
|
||||||
luajit2-sys = { workspace = true }
|
luajit2-sys = { workspace = true }
|
||||||
nanorand = { workspace = true }
|
nanorand = { workspace = true }
|
||||||
|
num-derive = { workspace = true }
|
||||||
|
num-traits = { workspace = true }
|
||||||
oodle = { workspace = true }
|
oodle = { workspace = true }
|
||||||
path-slash = { workspace = true }
|
path-slash = { workspace = true }
|
||||||
pin-project-lite = { workspace = true }
|
pin-project-lite = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_sjson = { workspace = true }
|
serde_sjson = { workspace = true }
|
||||||
|
strum = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tokio-stream = { workspace = true }
|
tokio-stream = { workspace = true }
|
||||||
tracing = { 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 std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use color_eyre::{eyre, SectionExt};
|
use color_eyre::{eyre, SectionExt};
|
||||||
use color_eyre::{Help, Result};
|
use color_eyre::{Help, Result};
|
||||||
|
use num_traits::ToPrimitive as _;
|
||||||
use oodle::{OodleLZ_CheckCRC, OodleLZ_FuzzSafe};
|
use oodle::{OodleLZ_CheckCRC, OodleLZ_FuzzSafe};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
use crate::binary::sync::{ReadExt, WriteExt};
|
use crate::binary::sync::{ReadExt, WriteExt};
|
||||||
use crate::bundle::file::UserFile;
|
use crate::bundle::file::UserFile;
|
||||||
|
use crate::filetype::texture::dds::{DXGIFormat, ImageType};
|
||||||
use crate::murmur::{HashGroup, IdString32, IdString64};
|
use crate::murmur::{HashGroup, IdString32, IdString64};
|
||||||
use crate::{BundleFile, BundleFileType, BundleFileVariant};
|
use crate::{BundleFile, BundleFileType, BundleFileVariant};
|
||||||
|
|
||||||
|
@ -49,13 +51,20 @@ bitflags! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
struct TextureHeaderMipInfo {
|
||||||
|
offset: usize,
|
||||||
|
size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct TextureHeader {
|
struct TextureHeader {
|
||||||
flags: TextureFlags,
|
flags: TextureFlags,
|
||||||
n_streamable_mipmaps: u32,
|
n_streamable_mipmaps: usize,
|
||||||
width: u32,
|
width: usize,
|
||||||
height: u32,
|
height: usize,
|
||||||
mip_info_size: u32,
|
mip_infos: [TextureHeaderMipInfo; 16],
|
||||||
|
meta_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextureHeader {
|
impl TextureHeader {
|
||||||
|
@ -65,23 +74,26 @@ impl TextureHeader {
|
||||||
TextureFlags::from_bits(bits)
|
TextureFlags::from_bits(bits)
|
||||||
.ok_or_else(|| eyre::eyre!("Unknown bits set in TextureFlags: {:032b}", bits))
|
.ok_or_else(|| eyre::eyre!("Unknown bits set in TextureFlags: {:032b}", bits))
|
||||||
})?;
|
})?;
|
||||||
let n_streamable_mipmaps = r.read_u32()?;
|
let n_streamable_mipmaps = r.read_u32()? as usize;
|
||||||
let width = r.read_u32()?;
|
let width = r.read_u32()? as usize;
|
||||||
let height = r.read_u32()?;
|
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
|
for info in mip_infos.iter_mut() {
|
||||||
r.seek(SeekFrom::Current(2 * 4 * 15))?;
|
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 {
|
Ok(Self {
|
||||||
flags,
|
flags,
|
||||||
n_streamable_mipmaps,
|
n_streamable_mipmaps,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
mip_info_size,
|
mip_infos,
|
||||||
|
meta_size,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,15 +105,16 @@ impl TextureHeader {
|
||||||
);
|
);
|
||||||
|
|
||||||
w.write_u32(self.flags.bits())?;
|
w.write_u32(self.flags.bits())?;
|
||||||
w.write_u32(self.n_streamable_mipmaps)?;
|
w.write_u32(self.n_streamable_mipmaps as u32)?;
|
||||||
w.write_u32(self.width)?;
|
w.write_u32(self.width as u32)?;
|
||||||
w.write_u32(self.height)?;
|
w.write_u32(self.height as u32)?;
|
||||||
|
|
||||||
// See `from_binary` about this unknown section.
|
for info in self.mip_infos {
|
||||||
let buf = [0; (2 * 4 * 15) + 4];
|
w.write_u32(info.offset as u32)?;
|
||||||
w.write_all(&buf)?;
|
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)?;
|
w.write_u32(0)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -117,6 +130,90 @@ struct Texture {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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))]
|
#[tracing::instrument(skip(ctx, r, stream_r))]
|
||||||
fn from_binary(
|
fn from_binary(
|
||||||
ctx: &crate::Context,
|
ctx: &crate::Context,
|
||||||
|
@ -162,19 +259,19 @@ impl Texture {
|
||||||
let header = TextureHeader::from_binary(&mut r)?;
|
let header = TextureHeader::from_binary(&mut r)?;
|
||||||
|
|
||||||
eyre::ensure!(
|
eyre::ensure!(
|
||||||
header.mip_info_size == 0 || stream_r.is_some(),
|
header.meta_size == 0 || stream_r.is_some(),
|
||||||
"Compression chunks and stream file don't match up. mip_info_size = {}, has_stream = {}",
|
"Compression chunks and stream file don't match up. meta_size = {}, has_stream = {}",
|
||||||
header.mip_info_size,
|
header.meta_size,
|
||||||
stream_r.is_some()
|
stream_r.is_some()
|
||||||
);
|
);
|
||||||
|
|
||||||
let stream = if let Some(stream_r) = stream_r.as_mut() {
|
let stream = if let Some(stream_r) = stream_r.as_mut() {
|
||||||
// Number of compression chunks in the stream file
|
// 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)?;
|
r.skip_u16(0)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let num_chunks_1 = r.read_u16()? as u32;
|
let num_chunks_1 = r.read_u16()? as usize;
|
||||||
|
|
||||||
eyre::ensure!(
|
eyre::ensure!(
|
||||||
num_chunks == num_chunks_1,
|
num_chunks == num_chunks_1,
|
||||||
|
@ -184,37 +281,15 @@ impl Texture {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const RAW_SIZE: usize = 0x10000;
|
let mut chunks = Vec::with_capacity(num_chunks);
|
||||||
let mut stream_raw = Vec::new();
|
|
||||||
let mut last = 0;
|
|
||||||
|
|
||||||
for i in 0..num_chunks {
|
for _ in 0..num_chunks {
|
||||||
let offset_next = r.read_u32()? as usize;
|
chunks.push(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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let stream_raw = Self::decompress_stream_data(stream_r, chunks)
|
||||||
|
.wrap_err("Failed to decompress stream data")?;
|
||||||
|
|
||||||
Some(stream_raw)
|
Some(stream_raw)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -247,10 +322,6 @@ impl Texture {
|
||||||
|
|
||||||
self.header.to_binary(&mut w)?;
|
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())?;
|
w.write_u32(self.category.to_murmur32().into())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -268,19 +339,9 @@ impl Texture {
|
||||||
serde_sjson::to_string(&texture).wrap_err("Failed to serialize texture definition")
|
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>> {
|
fn to_user_files(&self, name: String) -> Result<Vec<UserFile>> {
|
||||||
let mut files = Vec::with_capacity(3);
|
let mut files = Vec::with_capacity(2);
|
||||||
|
|
||||||
// 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 data = self.to_sjson(name.clone())?.as_bytes().to_vec();
|
let data = self.to_sjson(name.clone())?.as_bytes().to_vec();
|
||||||
|
@ -291,7 +352,119 @@ impl Texture {
|
||||||
files.push(UserFile::with_name(data, name));
|
files.push(UserFile::with_name(data, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
files.push(UserFile::with_name(self.data.clone(), 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)
|
Ok(files)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -388,8 +561,8 @@ pub async fn compile(
|
||||||
|
|
||||||
r.seek(SeekFrom::Current(5))?;
|
r.seek(SeekFrom::Current(5))?;
|
||||||
|
|
||||||
let width = r.read_u16()? as u32;
|
let width = r.read_u32()? as usize;
|
||||||
let height = r.read_u16()? as u32;
|
let height = r.read_u32()? as usize;
|
||||||
|
|
||||||
(width, height)
|
(width, height)
|
||||||
};
|
};
|
||||||
|
@ -403,6 +576,8 @@ pub async fn compile(
|
||||||
n_streamable_mipmaps: 0,
|
n_streamable_mipmaps: 0,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
mip_infos: [TextureHeaderMipInfo::default(); 16],
|
||||||
|
meta_size: 0,
|
||||||
},
|
},
|
||||||
data: dds,
|
data: dds,
|
||||||
stream: None,
|
stream: None,
|
||||||
|
|
|
@ -1,6 +1,16 @@
|
||||||
|
use std::io::SeekFrom;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
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 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! {
|
bitflags! {
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[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 {
|
pub enum D3D10ResourceDimension {
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
Buffer = 1,
|
Buffer = 1,
|
||||||
|
@ -74,46 +104,325 @@ pub enum D3D10ResourceDimension {
|
||||||
Texture3D = 4,
|
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 {
|
pub struct Dx10Header {
|
||||||
/// Resource data formats, including fully-typed and typeless formats.
|
/// Resource data formats, including fully-typed and typeless formats.
|
||||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
|
/// See https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
|
||||||
dxgi_format: u32,
|
pub dxgi_format: DXGIFormat,
|
||||||
resource_dimension: D3D10ResourceDimension,
|
pub resource_dimension: D3D10ResourceDimension,
|
||||||
misc_flag: DdsResourceMiscFlags,
|
pub misc_flag: DdsResourceMiscFlags,
|
||||||
array_size: u32,
|
pub array_size: usize,
|
||||||
misc_flags2: u32,
|
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 {
|
pub struct DDSPixelFormat {
|
||||||
/// Structure size. Must be `32`.
|
pub flags: DDPF,
|
||||||
size: u32,
|
pub four_cc: u32,
|
||||||
flags: DDPF,
|
pub rgb_bit_count: u32,
|
||||||
four_cc: u32,
|
pub r_bit_mask: u32,
|
||||||
rgb_bit_count: u32,
|
pub g_bit_mask: u32,
|
||||||
r_bit_mask: u32,
|
pub b_bit_mask: u32,
|
||||||
g_bit_mask: u32,
|
pub a_bit_mask: u32,
|
||||||
b_bit_mask: u32,
|
|
||||||
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 {
|
pub struct DDSHeader {
|
||||||
/// Size of this structure. Must be `124`.
|
|
||||||
size: u32,
|
|
||||||
/// Flags to indicate which members contain valid data.
|
/// Flags to indicate which members contain valid data.
|
||||||
flags: DDSD,
|
pub flags: DDSD,
|
||||||
height: u32,
|
pub height: usize,
|
||||||
width: u32,
|
pub width: usize,
|
||||||
pitch_or_linear_size: u32,
|
pub pitch_or_linear_size: usize,
|
||||||
depth: u32,
|
pub depth: usize,
|
||||||
mipmap_count: u32,
|
pub mipmap_count: usize,
|
||||||
reserved_1: [u8; 11],
|
pub pixel_format: DDSPixelFormat,
|
||||||
pixel_format: DDSPixelFormat,
|
pub caps: DDSCAPS,
|
||||||
caps: DDSCAPS,
|
pub caps_2: DDSCAPS2,
|
||||||
caps_2: DDSCAPS2,
|
|
||||||
reserved_2: [u8; 3],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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 {
|
pub enum ImageType {
|
||||||
Image2D = 0,
|
Image2D = 0,
|
||||||
Image3D = 1,
|
Image3D = 1,
|
||||||
|
@ -125,12 +434,14 @@ pub enum ImageType {
|
||||||
|
|
||||||
/// A stripped version of `ImageType` that only contains just the data needed
|
/// A stripped version of `ImageType` that only contains just the data needed
|
||||||
/// to read a DDS image stream.
|
/// to read a DDS image stream.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct StrippedImageFormat {
|
pub struct StrippedImageFormat {
|
||||||
pub image_type: ImageType,
|
pub image_type: ImageType,
|
||||||
pub width: u32,
|
pub width: usize,
|
||||||
pub height: u32,
|
pub height: usize,
|
||||||
pub layers: u32,
|
pub layers: usize,
|
||||||
pub mip_levels: u32,
|
pub mip_levels: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a stripped down version of the logic that the engine implements to fill
|
// 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.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;
|
image_format.image_type = ImageType::ImageCube;
|
||||||
if dx10_header.array_size > 1 {
|
if dx10_header.array_size > 1 {
|
||||||
image_format.layers = dx10_header.array_size;
|
image_format.layers = dx10_header.array_size;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![feature(cursor_remaining)]
|
||||||
#![feature(test)]
|
#![feature(test)]
|
||||||
|
|
||||||
mod binary;
|
mod binary;
|
||||||
|
|
Loading…
Add table
Reference in a new issue