Update RangeFit & ColourWeights for GCN/CMPR

This commit is contained in:
Luke Street
2021-08-30 17:25:42 -04:00
parent 69675cf7e2
commit b59431d33b
7 changed files with 126 additions and 96 deletions
+3 -3
View File
@@ -20,7 +20,7 @@
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
use core::{mem, u8};
use core::mem;
use crate::Format;
use crate::math::{f32_to_i32_clamped, Vec3};
@@ -110,7 +110,7 @@ pub fn write4(start: &Vec3, end: &Vec3, indices: &[u8; 16], block: &mut [u8], fo
/// Convert a little endian 565-packed colour to 8bpc RGBA
fn unpack_565(packed: &[u8], format: Format) -> [u8; 4] {
assert!(packed.len() == 2);
assert_eq!(packed.len(), 2);
// get components
let mut tmp = [0u8; 2];
tmp.copy_from_slice(&packed[0..2]);
@@ -133,7 +133,7 @@ fn unpack_565(packed: &[u8], format: Format) -> [u8; 4] {
/// Decompress a BC1/2/3 block to 4x4 RGBA pixels
pub fn decompress(bytes: &[u8], format: Format) -> [[u8; 4]; 16] {
assert!(bytes.len() == 8);
assert_eq!(bytes.len(), 8);
let is_gcn = format == Format::Bc1Gcn;
let is_bc1 = format == Format::Bc1 || is_gcn;
+41 -41
View File
@@ -151,17 +151,17 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> {
fn compress3(&mut self) {
let count = self.colourset.count();
let two = Vec4::new(2.0, 2.0, 2.0, 2.0);
let one = Vec4::new(1.0, 1.0, 1.0, 1.0);
let half_half2 = Vec4::new(0.5, 0.5, 0.5, 0.25);
let zero = Vec4::new(0.0, 0.0, 0.0, 0.0);
let half = Vec4::new(0.5, 0.5, 0.5, 0.5);
let grid = Vec4::new(31.0, 63.0, 31.0, 0.0);
let gridrcp = Vec4::new(1.0 / 31.0, 1.0 / 63.0, 1.0 / 31.0, 0.0);
const TWO: Vec4 = Vec4::new(2.0, 2.0, 2.0, 2.0);
const ONE: Vec4 = Vec4::new(1.0, 1.0, 1.0, 1.0);
const HALF_HALF2: Vec4 = Vec4::new(0.5, 0.5, 0.5, 0.25);
const ZERO: Vec4 = Vec4::new(0.0, 0.0, 0.0, 0.0);
const HALF: Vec4 = Vec4::new(0.5, 0.5, 0.5, 0.5);
const GRID: Vec4 = Vec4::new(31.0, 63.0, 31.0, 0.0);
const GRID_RCP: Vec4 = Vec4::new(1.0 / 31.0, 1.0 / 63.0, 1.0 / 31.0, 0.0);
// check all possible clusters and iterate on the total order
let mut best_start = zero;
let mut best_end = zero;
let mut best_start = ZERO;
let mut best_end = ZERO;
let mut best_error = self.best_error;
let mut best_indices = [0u8; 16];
let mut best_iteration = 0;
@@ -178,11 +178,11 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> {
}
// first cluster [0,i) is at the start
let mut part0 = zero;
let mut part0 = ZERO;
for i in 0..count {
// second cluster [i,j) is halfway along
let mut part1 =
if i == 0 { self.points_weights[0] } else { zero };
if i == 0 { self.points_weights[0] } else { ZERO };
let jmin = if i == 0 { 1 } else { i };
for j in jmin..=count {
@@ -190,13 +190,13 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> {
let part2 = self.xsum_wsum - part1 - part0;
// compute least squares term directly
let alphax_sum = part1 * half_half2 + part0;
let alphax_sum = part1 * HALF_HALF2 + part0;
let alpha2_sum = alphax_sum.splat_w();
let betax_sum = part1 * half_half2 + part2;
let betax_sum = part1 * HALF_HALF2 + part2;
let beta2_sum = betax_sum.splat_w();
let alphabeta_sum = (part1 * half_half2).splat_w();
let alphabeta_sum = (part1 * HALF_HALF2).splat_w();
// compute the least-squares optimal points
let factor =
@@ -205,16 +205,16 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> {
let b = ((betax_sum * alpha2_sum) - alphax_sum * alphabeta_sum) * factor;
// clamp to the grid
let a = one.min(zero.max(a));
let b = one.min(zero.max(b));
let a = (grid * a + half).truncate() * gridrcp;
let b = (grid * b + half).truncate() * gridrcp;
let a = ONE.min(ZERO.max(a));
let b = ONE.min(ZERO.max(b));
let a = (GRID * a + HALF).truncate() * GRID_RCP;
let b = (GRID * b + HALF).truncate() * GRID_RCP;
// compute the error (we skip the constant xxsum)
let e1 = (a * a) * alpha2_sum + (b * b * beta2_sum);
let e2 = (a * b * alphabeta_sum) - a * alphax_sum;
let e3 = e2 - b * betax_sum;
let e4 = two * e3 + e1;
let e4 = TWO * e3 + e1;
// apply the channel weights to the error term
let e5 = e4 * self.weights;
@@ -276,19 +276,19 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> {
fn compress4(&mut self) {
let count = self.colourset.count();
let two = Vec4::new(2.0, 2.0, 2.0, 2.0);
let one = Vec4::new(1.0, 1.0, 1.0, 1.0);
let onethird_onethird2 = Vec4::new(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 9.0);
let twothirds_twothirds2 = Vec4::new(2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0, 4.0 / 9.0);
let twoninths = Vec4::new(2.0 / 9.0, 2.0 / 9.0, 2.0 / 9.0, 2.0 / 9.0);
let zero = Vec4::new(0.0, 0.0, 0.0, 0.0);
let half = Vec4::new(0.5, 0.5, 0.5, 0.5);
let grid = Vec4::new(31.0, 63.0, 31.0, 0.0);
let gridrcp = Vec4::new(1.0 / 31.0, 1.0 / 63.0, 1.0 / 31.0, 0.0);
const TWO: Vec4 = Vec4::new(2.0, 2.0, 2.0, 2.0);
const ONE: Vec4 = Vec4::new(1.0, 1.0, 1.0, 1.0);
const ONETHIRD_ONETHIRD2: Vec4 = Vec4::new(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 9.0);
const TWOTHIRDS_TWOTHIRDS2: Vec4 = Vec4::new(2.0 / 3.0, 2.0 / 3.0, 2.0 / 3.0, 4.0 / 9.0);
const TWONINTHS: Vec4 = Vec4::new(2.0 / 9.0, 2.0 / 9.0, 2.0 / 9.0, 2.0 / 9.0);
const ZERO: Vec4 = Vec4::new(0.0, 0.0, 0.0, 0.0);
const HALF: Vec4 = Vec4::new(0.5, 0.5, 0.5, 0.5);
const GRID: Vec4 = Vec4::new(31.0, 63.0, 31.0, 0.0);
const GRID_RCP: Vec4 = Vec4::new(1.0 / 31.0, 1.0 / 63.0, 1.0 / 31.0, 0.0);
// check all possible clusters and iterate on the total order
let mut best_start = zero;
let mut best_end = zero;
let mut best_start = ZERO;
let mut best_end = ZERO;
let mut best_error = self.best_error;
let mut best_indices = [0u8; 16];
let mut best_iteration = 0;
@@ -306,17 +306,17 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> {
}
// first cluster [0,i) is at the start
let mut part0 = zero;
let mut part0 = ZERO;
for i in 0..count {
// second cluster [i,j) is one third along
let mut part1 = zero;
let mut part1 = ZERO;
for j in i..=count {
// third cluster [j, k) is two thirds along
let mut part2 = if j == 0 {
self.points_weights[0]
} else {
zero
ZERO
};
let kmin = if j == 0 { 1 } else { j };
@@ -326,14 +326,14 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> {
// compute least squares terms directly
let alphax_sum =
part2 * onethird_onethird2 + (part1 * twothirds_twothirds2 + part0);
part2 * ONETHIRD_ONETHIRD2 + (part1 * TWOTHIRDS_TWOTHIRDS2 + part0);
let alpha2_sum = alphax_sum.splat_w();
let betax_sum =
part1 * onethird_onethird2 + (part2 * twothirds_twothirds2 + part3);
part1 * ONETHIRD_ONETHIRD2 + (part2 * TWOTHIRDS_TWOTHIRDS2 + part3);
let beta2_sum = betax_sum.splat_w();
let alphabeta_sum = twoninths * (part1 + part2).splat_w();
let alphabeta_sum = TWONINTHS * (part1 + part2).splat_w();
// compute the least-squares optimal points
let factor =
@@ -342,16 +342,16 @@ impl<'a> ColourFitImpl<'a> for ClusterFit<'a> {
let b = ((betax_sum * alpha2_sum) - alphax_sum * alphabeta_sum) * factor;
// clamp to the grid
let a = one.min(zero.max(a));
let b = one.min(zero.max(b));
let a = (grid * a + half).truncate() * gridrcp;
let b = (grid * b + half).truncate() * gridrcp;
let a = ONE.min(ZERO.max(a));
let b = ONE.min(ZERO.max(b));
let a = (GRID * a + HALF).truncate() * GRID_RCP;
let b = (GRID * b + HALF).truncate() * GRID_RCP;
// compute the error (we skip the constant xxsum)
let e1 = (a * a) * alpha2_sum + (b * b * beta2_sum);
let e2 = (a * b * alphabeta_sum) - a * alphax_sum;
let e3 = e2 - b * betax_sum;
let e4 = two * e3 + e1;
let e4 = TWO * e3 + e1;
// apply the channel weights to the error term
let e5 = e4 * self.weights;
+5 -5
View File
@@ -20,12 +20,10 @@
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
use core::f32;
use crate::{ColourWeights, Format};
use crate::colourblock;
use crate::colourset::ColourSet;
use crate::math::{Sym3x3, Vec3};
use crate::{ColourWeights, Format};
use super::ColourFitImpl;
@@ -174,11 +172,13 @@ impl<'a> ColourFitImpl<'a> for RangeFit<'a> {
fn compress4(&mut self) {
// create a codebook
let one_third = if self.format == Format::Bc1Gcn { 3.0 / 8.0 } else { 1.0 / 3.0 };
let two_thirds = if self.format == Format::Bc1Gcn { 5.0 / 8.0 } else { 2.0 / 3.0 };
let codes = [
self.start,
self.end,
self.start * (2.0 / 3.0) + self.end * (1.0 / 3.0),
self.start * (1.0 / 3.0) + self.end * (2.0 / 3.0),
self.start * two_thirds + self.end * one_third,
self.start * one_third + self.end * two_thirds,
];
if self.compression_helper(&codes) {
-2
View File
@@ -20,8 +20,6 @@
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
use core::u32;
use crate::colourblock;
use crate::colourset::ColourSet;
use crate::math::{f32_to_i32_clamped, Vec3};
+63 -31
View File
@@ -25,17 +25,18 @@
#![no_std]
#[cfg(feature="rayon")]
use rayon::prelude::*;
use crate::colourfit::{ClusterFit, ColourFit, RangeFit, SingleColourFit};
use crate::colourset::ColourSet;
mod alpha;
mod colourblock;
mod colourfit;
mod colourset;
mod math;
use crate::colourfit::{ClusterFit, ColourFit, RangeFit, SingleColourFit};
use crate::colourset::ColourSet;
#[cfg(feature="rayon")]
use rayon::prelude::*;
/// Defines a compression format
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Format {
@@ -78,6 +79,9 @@ pub const COLOUR_WEIGHTS_UNIFORM: ColourWeights = [1.0, 1.0, 1.0];
/// Weights based on the perceived brightness of each colour channel
pub const COLOUR_WEIGHTS_PERCEPTUAL: ColourWeights = [0.2126, 0.7152, 0.0722];
/// Perceptual weights used for GCN/CMPR encoding
pub const COLOUR_WEIGHTS_PERCEPTUAL_GCN: ColourWeights = [0.3086, 0.6094, 0.0820];
#[derive(Clone, Copy)]
pub struct Params {
/// The compression algorithm to be used
@@ -199,7 +203,7 @@ impl Format {
) {
// compress alpha block(s)
match self {
Format::Bc1 => {},
Format::Bc1 | Format::Bc1Gcn => {},
Format::Bc2 => alpha::compress_bc2(&rgba, mask, &mut output[..8]),
Format::Bc3 => alpha::compress_bc3(&rgba, 3, mask, &mut output[..8]),
Format::Bc4 => alpha::compress_bc3(&rgba, 0, mask, &mut output[..8]),
@@ -211,11 +215,11 @@ impl Format {
// compress colour block if the format has one
match self {
Format::Bc1 | Format::Bc2 | Format::Bc3 => {
Format::Bc1 | Format::Bc1Gcn | Format::Bc2 | Format::Bc3 => {
// create the minimal point set
let colours = ColourSet::new(&rgba, mask, self, params.weigh_colour_by_alpha);
let colour_offset = if self == Format::Bc1 { 0 } else { 8 };
let colour_offset = if self == Format::Bc1 || self == Format::Bc1Gcn { 0 } else { 8 };
let colour_block = &mut output[colour_offset..colour_offset + 8];
// compress with appropriate compression algorithm
@@ -246,9 +250,9 @@ impl Format {
let mut rgba;
// decompress colour block
match self {
Format::Bc1 | Format::Bc2 | Format::Bc3 => {
Format::Bc1 | Format::Bc1Gcn | Format::Bc2 | Format::Bc3 => {
// get reference to the actual colour block
let colour_offset = if self == Format::Bc1 { 0 } else { 8 };
let colour_offset = if self == Format::Bc1 || self == Format::Bc1Gcn { 0 } else { 8 };
let colour_block = &block[colour_offset..colour_offset + 8];
// decompress colour block
@@ -261,7 +265,7 @@ impl Format {
// decompress alpha block(s)
match self {
Format::Bc1 => (),
Format::Bc1 | Format::Bc1Gcn => (),
Format::Bc2 => alpha::decompress_bc2(&mut rgba, &block[..8]),
Format::Bc3 => alpha::decompress_bc3(&mut rgba, 3, &block[..8]),
Format::Bc4 => {
@@ -352,6 +356,8 @@ mod tests {
fn test_storage_requirements() {
assert_eq!(Format::Bc1.compressed_size(16, 32), 256);
assert_eq!(Format::Bc1.compressed_size(15, 32), 256);
assert_eq!(Format::Bc1Gcn.compressed_size(16, 32), 256);
assert_eq!(Format::Bc1Gcn.compressed_size(15, 32), 256);
assert_eq!(Format::Bc2.compressed_size(16, 32), 512);
assert_eq!(Format::Bc2.compressed_size(15, 32), 512);
assert_eq!(Format::Bc3.compressed_size(16, 32), 512);
@@ -391,18 +397,6 @@ mod tests {
assert_eq!(output_actual, decoded_block_gray_4x4_as_rgba());
}
#[test]
fn test_storage_requirements_bc1_gcn_exact() {
let estimate = Format::Bc1Gcn.compressed_size(16, 32);
assert_eq!(estimate, 256);
}
#[test]
fn test_storage_requirements_bc1_gcn_padded() {
let estimate = Format::Bc1Gcn.compressed_size(15, 30);
assert_eq!(estimate, 256);
}
#[test]
fn test_bc1_compression_gray() {
fn test(algorithm: Algorithm) {
@@ -431,23 +425,30 @@ mod tests {
// A colour test-pattern (RGB) with the first row in one colour,
// the second in another and the third and last row in a third colour.
static DECODED_BLOCK_COLOUR_4X4: &[u8] = &[
static DECODED_BLOCK_COLOUR_4X4: [u8; 4 * 4 * 3] = [
255, 150, 74, 255, 150, 74, 255, 150, 74, 255, 150, 74, // row 0
255, 120, 52, 255, 120, 52, 255, 120, 52, 255, 120, 52, // row 1
255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, // row 2
255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, // row 3
];
static DECODED_BLOCK_COLOUR_4X4_GCN: [u8; 4 * 4 * 3] = [
255, 150, 74, 255, 150, 74, 255, 150, 74, 255, 150, 74, // row 0
255, 121, 53, 255, 121, 53, 255, 121, 53, 255, 121, 53, // row 1
255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, // row 2
255, 105, 41, 255, 105, 41, 255, 105, 41, 255, 105, 41, // row 3
];
// BC1 data created with AMD Compressonator v4.1.5083 and is the same as libsquish
static ENCODED_BLOCK_COLOUR_4X4: [u8; 8] = [0xA9, 0xFC, 0x45, 0xFB, 0x00, 0xFF, 0x55, 0x55];
static ENCODED_BLOCK_COLOUR_4X4_GCN: [u8; 8] = [0xFC, 0xA9, 0xFB, 0x45, 0x00, 0xFF, 0x55, 0x55];
fn decoded_block_colour_4x4_as_rgba() -> [u8; 4 * 4 * 4] {
fn rgb_to_rgba(block: &[u8; 4 * 4 * 3]) -> [u8; 4 * 4 * 4] {
let mut output = [0u8; 4 * 4 * 4];
for i in 0..4 * 4 {
output[i * 4 + 0] = DECODED_BLOCK_COLOUR_4X4[i * 3 + 0]; // R
output[i * 4 + 1] = DECODED_BLOCK_COLOUR_4X4[i * 3 + 1]; // G
output[i * 4 + 2] = DECODED_BLOCK_COLOUR_4X4[i * 3 + 2]; // B
output[i * 4 + 3] = 0xFF; //A
output[i * 4 + 0] = block[i * 3 + 0]; // R
output[i * 4 + 1] = block[i * 3 + 1]; // G
output[i * 4 + 2] = block[i * 3 + 2]; // B
output[i * 4 + 3] = 0xFF; // A
}
output
}
@@ -457,7 +458,7 @@ mod tests {
let encoded: [u8; 8] = ENCODED_BLOCK_COLOUR_4X4;
let mut output_actual = [0u8; 4 * 4 * 4];
Format::Bc1.decompress(&encoded, 4, 4, &mut output_actual);
assert_eq!(output_actual, decoded_block_colour_4x4_as_rgba());
assert_eq!(output_actual, rgb_to_rgba(&DECODED_BLOCK_COLOUR_4X4));
}
#[test]
@@ -465,7 +466,7 @@ mod tests {
fn test(algorithm: Algorithm) {
let mut output_actual = [0u8; 8];
Format::Bc1.compress(
&decoded_block_colour_4x4_as_rgba(),
&rgb_to_rgba(&DECODED_BLOCK_COLOUR_4X4),
4,
4,
Params {
@@ -484,4 +485,35 @@ mod tests {
test(Algorithm::RangeFit);
test(Algorithm::IterativeClusterFit);
}
#[test]
fn test_bc1gcn_decompression_colour() {
let encoded: [u8; 8] = ENCODED_BLOCK_COLOUR_4X4_GCN;
let mut output_actual = [0u8; 4 * 4 * 4];
Format::Bc1Gcn.decompress(&encoded, 4, 4, &mut output_actual);
assert_eq!(output_actual, rgb_to_rgba(&DECODED_BLOCK_COLOUR_4X4_GCN));
}
#[test]
fn test_bc1gcn_compression_colour() {
fn test(algorithm: Algorithm) {
let mut output_actual = [0u8; 8];
Format::Bc1Gcn.compress(
&rgb_to_rgba(&DECODED_BLOCK_COLOUR_4X4_GCN),
4,
4,
Params {
algorithm,
weights: COLOUR_WEIGHTS_UNIFORM,
weigh_colour_by_alpha: false,
},
&mut output_actual,
);
let output_expected = ENCODED_BLOCK_COLOUR_4X4_GCN;
assert_eq!(output_actual, output_expected);
}
// only RangeFit implemented for GCN
test(Algorithm::RangeFit);
}
}
+4 -4
View File
@@ -32,19 +32,19 @@ pub struct Vec3 {
}
impl Vec3 {
pub fn new(x: f32, y: f32, z: f32) -> Self {
pub const fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
pub fn x(&self) -> f32 {
pub const fn x(&self) -> f32 {
self.x
}
pub fn y(&self) -> f32 {
pub const fn y(&self) -> f32 {
self.y
}
pub fn z(&self) -> f32 {
pub const fn z(&self) -> f32 {
self.z
}
+10 -10
View File
@@ -33,43 +33,43 @@ pub struct Vec4 {
}
impl Vec4 {
pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
Self { x, y, z, w }
}
pub fn x(&self) -> f32 {
pub const fn x(&self) -> f32 {
self.x
}
pub fn y(&self) -> f32 {
pub const fn y(&self) -> f32 {
self.y
}
pub fn z(&self) -> f32 {
pub const fn z(&self) -> f32 {
self.z
}
pub fn w(&self) -> f32 {
pub const fn w(&self) -> f32 {
self.w
}
pub fn to_vec3(&self) -> Vec3 {
pub const fn to_vec3(&self) -> Vec3 {
Vec3::new(self.x, self.y, self.z)
}
pub fn splat_x(&self) -> Vec4 {
pub const fn splat_x(&self) -> Vec4 {
Vec4::new(self.x, self.x, self.x, self.x)
}
pub fn splat_y(&self) -> Vec4 {
pub const fn splat_y(&self) -> Vec4 {
Vec4::new(self.y, self.y, self.y, self.y)
}
pub fn splat_z(&self) -> Vec4 {
pub const fn splat_z(&self) -> Vec4 {
Vec4::new(self.z, self.z, self.z, self.z)
}
pub fn splat_w(&self) -> Vec4 {
pub const fn splat_w(&self) -> Vec4 {
Vec4::new(self.w, self.w, self.w, self.w)
}