1
Fork 0
mirror of https://github.com/redstrate/Physis.git synced 2025-04-20 03:37:47 +00:00

Vendor what we need from texture2ddecoder

This recently tripped up cargo-deny because it depends on paste. I looked at it
and the parts that we need (a few BCn decoding functions) has only one place
where it's used and can easily be replaced.
This commit is contained in:
Joshua Goins 2025-03-10 17:15:14 -04:00
parent 24ace3370e
commit 3da2aa39c1
10 changed files with 283 additions and 4 deletions

View file

@ -47,7 +47,7 @@ game_install = []
# enables support for extracting visual data, such as models, textures, materials, etc. # enables support for extracting visual data, such as models, textures, materials, etc.
# this enables a bunch of dependencies! # this enables a bunch of dependencies!
# tip: can be safely turned off for launchers and other tools that simply need to extract the bare minimum of data # tip: can be safely turned off for launchers and other tools that simply need to extract the bare minimum of data
visual_data = ["dep:half", "dep:bitflags", "dep:texture2ddecoder"] visual_data = ["dep:half", "dep:bitflags"]
# testing only features # testing only features
retail_game_testing = [] retail_game_testing = []
@ -69,5 +69,5 @@ half = { version = "2", optional = true }
# cannot upgrade to 2.0.0, breaking changes that aren't recoverable: https://github.com/bitflags/bitflags/issues/314 # cannot upgrade to 2.0.0, breaking changes that aren't recoverable: https://github.com/bitflags/bitflags/issues/314
bitflags = { version = "1.3", optional = true } bitflags = { version = "1.3", optional = true }
# needed for dxt/bc decompression # used in bcn decoding
texture2ddecoder = { version = "0.1", optional = true } quote = "1.0"

View file

@ -145,6 +145,7 @@ Feel free to submit patches to help fix bugs or add functionality. Filing issues
- [binrw team](https://binrw.rs) for an awesome Rust library! - [binrw team](https://binrw.rs) for an awesome Rust library!
- [sha1-smol](https://github.com/mitsuhiko/sha1-smol) for a dependency-free SHA1 implementation - [sha1-smol](https://github.com/mitsuhiko/sha1-smol) for a dependency-free SHA1 implementation
* [FFXIVTools](https://github.com/dlunch/FFXIVTools) for it's Havok parsing implementation * [FFXIVTools](https://github.com/dlunch/FFXIVTools) for it's Havok parsing implementation
* [texture2ddecoder](https://github.com/UniversalGameExtraction/texture2ddecoder/) for it's BCn texture decoding functions.
And everyone else who writes FFXIV tools! And everyone else who writes FFXIV tools!

50
src/bcn/bc1.rs Normal file
View file

@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2023 Rudolf Kolbe
// SPDX-License-Identifier: MIT
use super::color::{color, rgb565_le};
#[inline]
pub fn decode_bc1_block(data: &[u8], outbuf: &mut [u32]) {
let q0 = u16::from_le_bytes([data[0], data[1]]);
let q1 = u16::from_le_bytes([data[2], data[3]]);
let (r0, g0, b0) = rgb565_le(q0);
let (r1, g1, b1) = rgb565_le(q1);
let mut c: [u32; 4] = [color(r0, g0, b0, 255), color(r1, g1, b1, 255), 0, 0];
// C insanity.....
let r0 = r0 as u16;
let g0 = g0 as u16;
let b0 = b0 as u16;
let r1 = r1 as u16;
let g1 = g1 as u16;
let b1 = b1 as u16;
if q0 > q1 {
c[2] = color(
((r0 * 2 + r1) / 3) as u8,
((g0 * 2 + g1) / 3) as u8,
((b0 * 2 + b1) / 3) as u8,
255,
);
c[3] = color(
((r0 + r1 * 2) / 3) as u8,
((g0 + g1 * 2) / 3) as u8,
((b0 + b1 * 2) / 3) as u8,
255,
);
} else {
c[2] = color(
((r0 + r1) / 2) as u8,
((g0 + g1) / 2) as u8,
((b0 + b1) / 2) as u8,
255,
);
c[3] = color(0, 0, 0, 255);
}
let mut d: usize = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
(0..16).for_each(|i| {
outbuf[i] = c[d & 3];
d >>= 2;
});
}

40
src/bcn/bc3.rs Normal file
View file

@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2023 Rudolf Kolbe
// SPDX-License-Identifier: MIT
use super::bc1::decode_bc1_block;
#[inline]
pub fn decode_bc3_alpha(data: &[u8], outbuf: &mut [u32], channel: usize) {
// use u16 to avoid overflow and replicate equivalent behavior to C++ code
let mut a: [u16; 8] = [data[0] as u16, data[1] as u16, 0, 0, 0, 0, 0, 0];
if a[0] > a[1] {
a[2] = (a[0] * 6 + a[1]) / 7;
a[3] = (a[0] * 5 + a[1] * 2) / 7;
a[4] = (a[0] * 4 + a[1] * 3) / 7;
a[5] = (a[0] * 3 + a[1] * 4) / 7;
a[6] = (a[0] * 2 + a[1] * 5) / 7;
a[7] = (a[0] + a[1] * 6) / 7;
} else {
a[2] = (a[0] * 4 + a[1]) / 5;
a[3] = (a[0] * 3 + a[1] * 2) / 5;
a[4] = (a[0] * 2 + a[1] * 3) / 5;
a[5] = (a[0] + a[1] * 4) / 5;
a[6] = 0;
a[7] = 255;
}
let mut d: usize = (u64::from_le_bytes(data[..8].try_into().unwrap()) >> 16) as usize;
let channel_shift = channel * 8;
let channel_mask = 0xFFFFFFFF ^ (0xFF << channel_shift);
outbuf.iter_mut().for_each(|p| {
*p = (*p & channel_mask) | (a[d & 7] as u32) << channel_shift;
d >>= 3;
});
}
#[inline]
pub fn decode_bc3_block(data: &[u8], outbuf: &mut [u32]) {
decode_bc1_block(&data[8..], outbuf);
decode_bc3_alpha(data, outbuf, 3);
}

10
src/bcn/bc5.rs Normal file
View file

@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2023 Rudolf Kolbe
// SPDX-License-Identifier: MIT
use super::bc3::decode_bc3_alpha;
#[inline]
pub fn decode_bc5_block(data: &[u8], outbuf: &mut [u32]) {
decode_bc3_alpha(data, outbuf, 2);
decode_bc3_alpha(&data[8..], outbuf, 1);
}

110
src/bcn/color.rs Normal file
View file

@ -0,0 +1,110 @@
// SPDX-FileCopyrightText: 2023 Rudolf Kolbe
// SPDX-License-Identifier: MIT
#![allow(clippy::too_many_arguments)]
pub static TRANSPARENT_MASK: u32 = {
#[cfg(target_endian = "little")]
{
0x00ffffff
}
#[cfg(target_endian = "big")]
{
0xffffff00
}
};
pub static TRANSPARENT_SHIFT: u32 = {
#[cfg(target_endian = "little")]
{
24
}
#[cfg(target_endian = "big")]
{
0
}
};
#[inline]
pub const fn color(r: u8, g: u8, b: u8, a: u8) -> u32 {
u32::from_le_bytes([b, g, r, a])
}
// #[cfg(target_endian = "little")]
// #[inline]
// pub fn alpha_mask(a: u8) -> u32 {
// TRANSPARENT_MASK | (a as u32) << 24
// }
// #[cfg(target_endian = "big")]
// #[inline]
// pub fn alpha_mask(a: u8) -> u32 {
// TRANSPARENT_MASK | a as u32
// }
// #[cfg(target_endian = "little")]
#[inline]
pub const fn rgb565_le(d: u16) -> (u8, u8, u8) {
(
(d >> 8 & 0xf8) as u8 | (d >> 13) as u8,
(d >> 3 & 0xfc) as u8 | (d >> 9 & 3) as u8,
(d << 3) as u8 | (d >> 2 & 7) as u8,
)
}
// #[cfg(target_endian = "big")]
// #[inline]
// pub fn rgb565_le(d: u16) -> (u8, u8, u8) {
// (
// (d & 0xf8) as u8 | (d >> 5 & 7) as u8,
// (d << 5 & 0xe0) as u8 | (d >> 11 & 0x1c) as u8 | (d >> 1 & 3) as u8,
// (d >> 5 & 0xf8) as u8 | (d >> 10 & 0x7) as u8,
// )
// }
// #[cfg(target_endian = "little")]
// #[inline]
// pub fn rgb565_be(d: u16) -> (u8, u8, u8) {
// (
// (d & 0xf8) as u8 | (d >> 5 & 7) as u8,
// (d << 5 & 0xe0) as u8 | (d >> 11 & 0x1c) as u8 | (d >> 1 & 3) as u8,
// (d >> 5 & 0xf8) as u8 | (d >> 10 & 0x7) as u8,
// )
// }
// #[cfg(target_endian = "big")]
// #[inline]
// pub fn rgb565_be(d: u16) -> (u8, u8, u8) {
// (
// (d >> 8 & 0xf8) as u8 | (d >> 13) as u8,
// (d >> 3 & 0xfc) as u8 | (d >> 9 & 3) as u8,
// (d << 3) as u8 | (d >> 2 & 7) as u8,
// )
// }
#[inline]
pub fn copy_block_buffer(
bx: usize,
by: usize,
w: usize,
h: usize,
bw: usize,
bh: usize,
buffer: &[u32],
image: &mut [u32],
) {
let x: usize = bw * bx;
let copy_width: usize = if bw * (bx + 1) > w { w - bw * bx } else { bw };
let y_0 = by * bh;
let copy_height: usize = if bh * (by + 1) > h { h - y_0 } else { bh };
let mut buffer_offset = 0;
for y in y_0..y_0 + copy_height {
let image_offset = y * w + x;
image[image_offset..image_offset + copy_width]
.copy_from_slice(&buffer[buffer_offset..buffer_offset + copy_width]);
buffer_offset += bw;
}
}

46
src/bcn/macros.rs Normal file
View file

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2023 Rudolf Kolbe
// SPDX-License-Identifier: MIT
// macro to generate generic block decoder functions
macro_rules! block_decoder{
($name: ident, $block_width: expr, $block_height: expr, $raw_block_size: expr, $block_decode_func: expr) => {
//#[doc = "Decodes a " $name " encoded texture into an image"]
pub fn $name(data: &[u8], width: usize, height: usize, image: &mut [u32]) -> Result<(), &'static str> {
const BLOCK_WIDTH: usize = $block_width;
const BLOCK_HEIGHT: usize = $block_height;
const BLOCK_SIZE: usize = BLOCK_WIDTH * BLOCK_HEIGHT;
let num_blocks_x: usize = (width + BLOCK_WIDTH - 1) / BLOCK_WIDTH;
let num_blocks_y: usize = (height + BLOCK_WIDTH - 1) / BLOCK_HEIGHT;
let mut buffer: [u32; BLOCK_SIZE] = [crate::bcn::color::color(0,0,0,255); BLOCK_SIZE];
if data.len() < num_blocks_x * num_blocks_y * $raw_block_size {
return Err("Not enough data to decode image!");
}
if image.len() < width * height {
return Err("Image buffer is too small!");
}
let mut data_offset = 0;
(0..num_blocks_y).for_each(|by| {
(0..num_blocks_x).for_each(|bx| {
$block_decode_func(&data[data_offset..], &mut buffer);
crate::bcn::color::copy_block_buffer(
bx,
by,
width,
height,
BLOCK_WIDTH,
BLOCK_HEIGHT,
&buffer,
image,
);
data_offset += $raw_block_size;
});
});
Ok(())
}
};
}
pub(crate) use block_decoder;

18
src/bcn/mod.rs Normal file
View file

@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2023 Rudolf Kolbe
// SPDX-License-Identifier: MIT
extern crate alloc;
mod bc1;
mod bc3;
mod bc5;
mod color;
mod macros;
pub use bc1::decode_bc1_block;
pub use bc3::decode_bc3_block;
pub use bc5::decode_bc5_block;
macros::block_decoder!(decode_bc1, 4, 4, 8, decode_bc1_block);
macros::block_decoder!(decode_bc3, 4, 4, 16, decode_bc3_block);
macros::block_decoder!(decode_bc5, 4, 4, 16, decode_bc5_block);

View file

@ -171,3 +171,5 @@ pub mod patchlist;
/// Reading SQDB files /// Reading SQDB files
pub mod sqdb; pub mod sqdb;
mod bcn;

View file

@ -5,11 +5,13 @@
use std::io::{Cursor, Read, Seek, SeekFrom}; use std::io::{Cursor, Read, Seek, SeekFrom};
use crate::bcn::decode_bc1;
use crate::bcn::decode_bc3;
use crate::bcn::decode_bc5;
use crate::ByteSpan; use crate::ByteSpan;
use binrw::BinRead; use binrw::BinRead;
use binrw::binrw; use binrw::binrw;
use bitflags::bitflags; use bitflags::bitflags;
use texture2ddecoder::{decode_bc1, decode_bc3, decode_bc5};
// Attributes and Format are adapted from Lumina (https://github.com/NotAdam/Lumina/blob/master/src/Lumina/Data/Files/TexFile.cs) // Attributes and Format are adapted from Lumina (https://github.com/NotAdam/Lumina/blob/master/src/Lumina/Data/Files/TexFile.cs)
bitflags! { bitflags! {