Consolidate all of the compact value storage formats into one crate.

This commit is contained in:
Nathan Vegdahl 2020-12-30 20:41:56 +09:00
parent 6586b38dc9
commit 3d85ce8005
24 changed files with 113 additions and 159 deletions

29
Cargo.lock generated
View File

@ -134,6 +134,15 @@ dependencies = [
name = "color" name = "color"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "compact"
version = "0.1.0"
dependencies = [
"bencher",
"proptest",
"rand 0.6.5",
]
[[package]] [[package]]
name = "copy_in_place" name = "copy_in_place"
version = "0.2.1" version = "0.2.1"
@ -277,15 +286,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "oct32norm"
version = "0.1.0"
dependencies = [
"bencher",
"proptest",
"rand 0.6.5",
]
[[package]] [[package]]
name = "openexr" name = "openexr"
version = "0.6.0" version = "0.6.0"
@ -352,6 +352,7 @@ dependencies = [
"bvh_order", "bvh_order",
"clap", "clap",
"color", "color",
"compact",
"copy_in_place", "copy_in_place",
"crossbeam", "crossbeam",
"fastapprox", "fastapprox",
@ -363,7 +364,6 @@ dependencies = [
"math3d", "math3d",
"nom", "nom",
"num_cpus", "num_cpus",
"oct32norm",
"openexr", "openexr",
"png_encode_mini", "png_encode_mini",
"rustc-serialize", "rustc-serialize",
@ -371,7 +371,6 @@ dependencies = [
"sobol", "sobol",
"spectral_upsampling", "spectral_upsampling",
"time", "time",
"trifloat",
] ]
[[package]] [[package]]
@ -663,14 +662,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "trifloat"
version = "0.1.0"
dependencies = [
"bencher",
"rand 0.6.5",
]
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.8" version = "0.1.8"

View File

@ -2,12 +2,11 @@
members = [ members = [
"sub_crates/bvh_order", "sub_crates/bvh_order",
"sub_crates/color", "sub_crates/color",
"sub_crates/compact",
"sub_crates/halton", "sub_crates/halton",
"sub_crates/math3d", "sub_crates/math3d",
"sub_crates/oct32norm",
"sub_crates/sobol", "sub_crates/sobol",
"sub_crates/spectral_upsampling", "sub_crates/spectral_upsampling",
"sub_crates/trifloat"
] ]
[package] [package]
@ -15,6 +14,7 @@ name = "psychopath"
version = "0.1.0" version = "0.1.0"
authors = ["Nathan Vegdahl <cessen@cessen.com>"] authors = ["Nathan Vegdahl <cessen@cessen.com>"]
edition = "2018" edition = "2018"
license = "GPL v3"
[profile.release] [profile.release]
debug = true debug = true
@ -45,20 +45,17 @@ path = "sub_crates/bvh_order"
[dependencies.color] [dependencies.color]
path = "sub_crates/color" path = "sub_crates/color"
[dependencies.compact]
path = "sub_crates/compact"
[dependencies.halton] [dependencies.halton]
path = "sub_crates/halton" path = "sub_crates/halton"
[dependencies.math3d] [dependencies.math3d]
path = "sub_crates/math3d" path = "sub_crates/math3d"
[dependencies.oct32norm]
path = "sub_crates/oct32norm"
[dependencies.sobol] [dependencies.sobol]
path = "sub_crates/sobol" path = "sub_crates/sobol"
[dependencies.spectral_upsampling] [dependencies.spectral_upsampling]
path = "sub_crates/spectral_upsampling" path = "sub_crates/spectral_upsampling"
[dependencies.trifloat]
path = "sub_crates/trifloat"

View File

@ -4,10 +4,10 @@ pub use color::{
rec709_e_to_xyz, rec709_to_xyz, xyz_to_aces_ap0, xyz_to_aces_ap0_e, xyz_to_rec709, rec709_e_to_xyz, rec709_to_xyz, xyz_to_aces_ap0, xyz_to_aces_ap0_e, xyz_to_rec709,
xyz_to_rec709_e, xyz_to_rec709_e,
}; };
use compact::fluv::fluv32;
use glam::Vec4; use glam::Vec4;
use half::f16; use half::f16;
use spectral_upsampling::meng::{spectrum_xyz_to_p_4, EQUAL_ENERGY_REFLECTANCE}; use spectral_upsampling::meng::{spectrum_xyz_to_p_4, EQUAL_ENERGY_REFLECTANCE};
use trifloat::fluv32;
use crate::{lerp::Lerp, math::fast_exp}; use crate::{lerp::Lerp, math::fast_exp};

View File

@ -3,7 +3,7 @@ name = "bvh_order"
version = "0.1.0" version = "0.1.0"
authors = ["Nathan Vegdahl <cessen@cessen.com>"] authors = ["Nathan Vegdahl <cessen@cessen.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT, Apache 2.0"
build = "build.rs" build = "build.rs"
[lib] [lib]

View File

@ -3,7 +3,7 @@ name = "color"
version = "0.1.0" version = "0.1.0"
authors = ["Nathan Vegdahl <cessen@cessen.com>"] authors = ["Nathan Vegdahl <cessen@cessen.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT, Apache 2.0"
build = "build.rs" build = "build.rs"
[lib] [lib]

View File

@ -1,12 +1,12 @@
[package] [package]
name = "oct32norm" name = "compact"
version = "0.1.0" version = "0.1.0"
authors = ["Nathan Vegdahl <cessen@cessen.com>"] authors = ["Nathan Vegdahl <cessen@cessen.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT, Apache 2.0"
[lib] [lib]
name = "oct32norm" name = "compact"
path = "src/lib.rs" path = "src/lib.rs"
[dev-dependencies] [dev-dependencies]

View File

@ -1,6 +1,10 @@
use bencher::{benchmark_group, benchmark_main, black_box, Bencher}; use bencher::{benchmark_group, benchmark_main, black_box, Bencher};
use compact::{
fluv::fluv32,
shared_exp::{signed48, unsigned32, unsigned40},
unit_vec::oct32,
};
use rand::{rngs::SmallRng, FromEntropy, Rng}; use rand::{rngs::SmallRng, FromEntropy, Rng};
use trifloat::{fluv32, signed48, unsigned32, unsigned40};
//---- //----
@ -115,6 +119,40 @@ fn fluv32_decode_yuv_1000_values(bench: &mut Bencher) {
}); });
} }
fn oct32_encode_1000_values(bench: &mut Bencher) {
let mut rng = SmallRng::from_entropy();
bench.iter(|| {
let x = rng.gen::<f32>() - 0.5;
let y = rng.gen::<f32>() - 0.5;
let z = rng.gen::<f32>() - 0.5;
for _ in 0..1000 {
black_box(oct32::encode(black_box((x, y, z))));
}
});
}
fn oct32_encode_precise_1000_values(bench: &mut Bencher) {
let mut rng = SmallRng::from_entropy();
bench.iter(|| {
let x = rng.gen::<f32>() - 0.5;
let y = rng.gen::<f32>() - 0.5;
let z = rng.gen::<f32>() - 0.5;
for _ in 0..1000 {
black_box(oct32::encode_precise(black_box((x, y, z))));
}
});
}
fn oct32_decode_1000_values(bench: &mut Bencher) {
let mut rng = SmallRng::from_entropy();
bench.iter(|| {
let v = rng.gen::<u32>();
for _ in 0..1000 {
black_box(oct32::decode(black_box(v)));
}
});
}
//---- //----
benchmark_group!( benchmark_group!(
@ -128,5 +166,8 @@ benchmark_group!(
fluv32_encode_1000_values, fluv32_encode_1000_values,
fluv32_decode_1000_values, fluv32_decode_1000_values,
fluv32_decode_yuv_1000_values, fluv32_decode_yuv_1000_values,
oct32_encode_1000_values,
oct32_encode_precise_1000_values,
oct32_decode_1000_values,
); );
benchmark_main!(benches); benchmark_main!(benches);

View File

@ -8,13 +8,12 @@
//! tweaked scales to allow perfect representation of E. //! tweaked scales to allow perfect representation of E.
//! * It uses a floating point rather than log encoding to store luminance, //! * It uses a floating point rather than log encoding to store luminance,
//! mainly for the sake of faster decoding. //! mainly for the sake of faster decoding.
//! * Unlike LogLuv, this format's dynamic range is biased to put more of it
//! above 1.0 (see Luminance details below).
//! * It omits the sign bit of LogLuv, foregoing negative luminance //! * It omits the sign bit of LogLuv, foregoing negative luminance
//! capabilities. //! capabilities.
//! //!
//! This format has the same chroma precision, very slightly improved luminance //! Aside from that, this format has the same chroma precision, very slightly
//! precision, and the same 127-stops of dynamic range as LogLuv. //! improved luminance precision, and the same 127-stops of dynamic range as
//! LogLuv.
//! //!
//! Like the LogLuv format, this is an absolute rather than relative color //! Like the LogLuv format, this is an absolute rather than relative color
//! encoding, and as such takes CIE XYZ triplets as input. It is *not* //! encoding, and as such takes CIE XYZ triplets as input. It is *not*
@ -23,47 +22,38 @@
//! //!
//! The bit layout is (from most to least significant bit): //! The bit layout is (from most to least significant bit):
//! //!
//! * 7 bits: luminance exponent (bias 42) //! * 7 bits: luminance exponent (bias 63)
//! * 9 bits: luminance mantissa (implied leading 1, for 10 bits precision) //! * 9 bits: luminance mantissa (implied leading 1, for 10 bits precision)
//! * 8 bits: u' //! * 8 bits: u'
//! * 8 bits: v' //! * 8 bits: v'
//! //!
//! ## Luminance details //! ## Luminance details
//! //!
//! Like typical floating point, the luminance mantissa is treated as having an
//! implicit leading 1, giving it an extra bit of precision.
//!
//! The luminance exponent is stored in 7 bits with a bias of 63. The smallest
//! exponent indicates a value of zero, and a valid encoding should also set
//! the mantissa to zero in that case (denormal numbers are not supported).
//! The largest exponent is given no special treatment (no infinities, no NaN).
//!
//! All together, this gives Fluv32 a worst-case precision that's slightly
//! better than Logluv, and a luminance range of roughly `10^-19` to `10^19`,
//! essentially the same as Logluv.
//!
//! Quoting Greg Ward about luminance ranges: //! Quoting Greg Ward about luminance ranges:
//! //!
//! > The sun is about `10^8 cd/m^2`, and the underside of a rock on a moonless //! > The sun is about `10^8 cd/m^2`, and the underside of a rock on a moonless
//! > night is probably around `10^-6` or so [...] //! > night is probably around `10^-6` or so [...]
//! //!
//! See also Wikipedia's //! So Fluv32's luminance range is *massively* larger than needed for any
//! [list of luminance levels](https://en.wikipedia.org/wiki/Orders_of_magnitude_(luminance)). //! day-to-day phenomena. The only things that exceed it are stellar events
//! //! such as supernovae, images of which are unliklely to be used with physical
//! The luminance range of the original LogLuv is about `10^-19` to `10^19`, //! units in most practical graphics applications.
//! splitting the range evenly above and below 1.0. Given the massive dynamic
//! range, and the fact that all day-to-day luminance levels trivially fit
//! within that, that's a perfectly reasonable choice.
//!
//! However, there are some stellar events like supernovae that are trillions
//! of times brighter than the sun, and would exceed `10^19`. Conversely,
//! there likely isn't much use for significantly smaller values than `10^-10`
//! or so. So although recording supernovae in physical units with a graphics
//! format seems unlikely, it doesn't hurt to bias the range towards brighter
//! luminance levels.
//!
//! With that in mind, FLuv32 uses an exponent bias of 42, putting twice as
//! many stops of dynamic range above 1.0 as below it, giving a luminance range
//! of roughly `10^-13` to `10^25`. It's the same dynamic range as
//! LogLuv (about 127 stops), but with more of that range placed above 1.0.
//!
//! Like typical floating point, the mantissa is treated as having an implicit
//! leading 1, giving it an extra bit of precision. The smallest exponent
//! indicates a value of zero, and a valid encoding should also set the
//! mantissa to zero in that case (denormal numbers are not supported). The
//! largest exponent is given no special treatment (no infinities, no NaN).
#![allow(clippy::cast_lossless)] #![allow(clippy::cast_lossless)]
const EXP_BIAS: i32 = 42; const EXP_BIAS: i32 = 63;
const BIAS_OFFSET: u32 = 127 - EXP_BIAS as u32; const BIAS_OFFSET: u32 = 127 - EXP_BIAS as u32;
/// The scale factor of the quantized U component. /// The scale factor of the quantized U component.
@ -205,10 +195,10 @@ mod tests {
let tri = encode(fs); let tri = encode(fs);
let fs2 = decode(tri); let fs2 = decode(tri);
assert_eq!(fs.1, fs2.1);
assert!((fs.0 - fs2.0).abs() < 0.0000001); assert!((fs.0 - fs2.0).abs() < 0.0000001);
assert_eq!(fs.1, fs2.1);
assert!((fs.2 - fs2.2).abs() < 0.0000001); assert!((fs.2 - fs2.2).abs() < 0.0000001);
assert_eq!(0x540056c3, tri); assert_eq!(0x7e0056c3, tri);
} }
#[test] #[test]

View File

@ -0,0 +1,5 @@
//! Fluv, a set of formats for compactly storing HDR XYZ colors.
//!
//! At the moment, only a 32-bit variant of the fluv format is provided.
pub mod fluv32;

View File

@ -0,0 +1,8 @@
//! Functions for storing various kinds of data compactly, using domain
//! knowledge of how that data is used.
//!
//! This includes functions for compactly storing e.g. colors and unit vectors.
pub mod fluv;
pub mod shared_exp;
pub mod unit_vec;

View File

@ -1,16 +1,11 @@
//! Functions for storing triplets of floating point values in a //! Shared-exponent float triplet formats.
//! shared-exponent format.
//!
//! The motivating use-case for this is compactly storing HDR RGB colors. But
//! it may be useful for other things as well.
pub mod fluv32;
pub mod signed48; pub mod signed48;
pub mod unsigned32; pub mod unsigned32;
pub mod unsigned40; pub mod unsigned40;
//=========================================================================== //===========================================================================
// Some shared functions used by the other modules in this crate. // Shared functions used by the other modules in this crate.
/// Calculates 2.0^exp using IEEE bit fiddling. /// Calculates 2.0^exp using IEEE bit fiddling.
/// ///

View File

@ -15,7 +15,7 @@
#![allow(clippy::cast_lossless)] #![allow(clippy::cast_lossless)]
use crate::{fiddle_exp2, fiddle_log2}; use super::{fiddle_exp2, fiddle_log2};
/// Largest representable number. /// Largest representable number.
pub const MAX: f32 = ((1u128 << (64 - EXP_BIAS)) - (1 << (64 - EXP_BIAS - 13))) as f32; pub const MAX: f32 = ((1u128 << (64 - EXP_BIAS)) - (1 << (64 - EXP_BIAS - 13))) as f32;

View File

@ -11,7 +11,7 @@
//! of all three values depends on the largest of the three. All integers //! of all three values depends on the largest of the three. All integers
//! up to 512 can be represented exactly in the largest value. //! up to 512 can be represented exactly in the largest value.
use crate::{fiddle_exp2, fiddle_log2}; use super::{fiddle_exp2, fiddle_log2};
/// Largest representable number. /// Largest representable number.
pub const MAX: f32 = ((1u64 << (32 - EXP_BIAS)) - (1 << (32 - EXP_BIAS - 9))) as f32; pub const MAX: f32 = ((1u64 << (32 - EXP_BIAS)) - (1 << (32 - EXP_BIAS - 9))) as f32;

View File

@ -11,7 +11,7 @@
//! of all three values depends on the largest of the three. All integers //! of all three values depends on the largest of the three. All integers
//! up to 2048 can be represented exactly in the largest value. //! up to 2048 can be represented exactly in the largest value.
use crate::{fiddle_exp2, fiddle_log2}; use super::{fiddle_exp2, fiddle_log2};
/// Largest representable number. /// Largest representable number.
pub const MAX: f32 = ((1u128 << (128 - EXP_BIAS)) - (1 << (128 - EXP_BIAS - 11))) as f32; pub const MAX: f32 = ((1u128 << (128 - EXP_BIAS)) - (1 << (128 - EXP_BIAS - 11))) as f32;

View File

@ -0,0 +1,3 @@
//! 3d unit vector formats.
pub mod oct32;

View File

@ -1,8 +1,7 @@
#[macro_use] #[macro_use]
extern crate proptest; extern crate proptest;
extern crate oct32norm;
use oct32norm::{decode, encode, encode_precise}; use compact::unit_vec::oct32::{decode, encode, encode_precise};
use proptest::test_runner::Config; use proptest::test_runner::Config;
/// Calculates the cosine of the angle between the two vectors, /// Calculates the cosine of the angle between the two vectors,
@ -54,7 +53,7 @@ proptest! {
#![proptest_config(Config::with_cases(4096))] #![proptest_config(Config::with_cases(4096))]
#[test] #[test]
fn pt_roundtrip_angle_precision(v in (-1.0f32..1.0, -1.0f32..1.0, -1.0f32..1.0)) { fn oct32_pt_roundtrip_angle_precision(v in (-1.0f32..1.0, -1.0f32..1.0, -1.0f32..1.0)) {
let oct = encode(v); let oct = encode(v);
let octp = encode_precise(v); let octp = encode_precise(v);
@ -68,7 +67,7 @@ proptest! {
} }
#[test] #[test]
fn pt_roundtrip_component_precision(v in (-1.0f32..1.0, -1.0f32..1.0, -1.0f32..1.0)) { fn oct32_pt_roundtrip_component_precision(v in (-1.0f32..1.0, -1.0f32..1.0, -1.0f32..1.0)) {
let oct = encode(v); let oct = encode(v);
let octp = encode_precise(v); let octp = encode_precise(v);

View File

@ -3,7 +3,7 @@ name = "math3d"
version = "0.1.0" version = "0.1.0"
authors = ["Nathan Vegdahl <cessen@cessen.com>"] authors = ["Nathan Vegdahl <cessen@cessen.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT, Apache 2.0"
[lib] [lib]
name = "math3d" name = "math3d"

View File

@ -1,49 +0,0 @@
use bencher::{benchmark_group, benchmark_main, black_box, Bencher};
use oct32norm::{decode, encode, encode_precise};
use rand::{rngs::SmallRng, FromEntropy, Rng};
//----
fn encode_1000_values(bench: &mut Bencher) {
let mut rng = SmallRng::from_entropy();
bench.iter(|| {
let x = rng.gen::<f32>() - 0.5;
let y = rng.gen::<f32>() - 0.5;
let z = rng.gen::<f32>() - 0.5;
for _ in 0..1000 {
black_box(encode(black_box((x, y, z))));
}
});
}
fn encode_precise_1000_values(bench: &mut Bencher) {
let mut rng = SmallRng::from_entropy();
bench.iter(|| {
let x = rng.gen::<f32>() - 0.5;
let y = rng.gen::<f32>() - 0.5;
let z = rng.gen::<f32>() - 0.5;
for _ in 0..1000 {
black_box(encode_precise(black_box((x, y, z))));
}
});
}
fn decode_1000_values(bench: &mut Bencher) {
let mut rng = SmallRng::from_entropy();
bench.iter(|| {
let v = rng.gen::<u32>();
for _ in 0..1000 {
black_box(decode(black_box(v)));
}
});
}
//----
benchmark_group!(
benches,
encode_1000_values,
encode_precise_1000_values,
decode_1000_values,
);
benchmark_main!(benches);

View File

@ -3,7 +3,7 @@ name = "sobol"
version = "0.1.0" version = "0.1.0"
authors = ["Nathan Vegdahl <cessen@cessen.com>"] authors = ["Nathan Vegdahl <cessen@cessen.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT, Apache 2.0"
build = "build.rs" build = "build.rs"
[lib] [lib]

View File

@ -3,7 +3,7 @@ name = "spectral_upsampling"
version = "0.1.0" version = "0.1.0"
authors = ["Nathan Vegdahl <cessen@cessen.com>"] authors = ["Nathan Vegdahl <cessen@cessen.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT, Apache 2.0"
[lib] [lib]
name = "spectral_upsampling" name = "spectral_upsampling"

View File

@ -1,18 +0,0 @@
[package]
name = "trifloat"
version = "0.1.0"
authors = ["Nathan Vegdahl <cessen@cessen.com>"]
edition = "2018"
license = "MIT"
[lib]
name = "trifloat"
path = "src/lib.rs"
[dev-dependencies]
bencher = "0.1.5"
rand = "0.6"
[[bench]]
name = "bench"
harness = false

View File

@ -1,8 +0,0 @@
Copyright (c) 2020 Nathan Vegdahl
This project is licensed under either of
* MIT license (licenses/MIT.txt or http://opensource.org/licenses/MIT)
* Apache License, Version 2.0, (licenses/Apache-2.0.txt or http://www.apache.org/licenses/LICENSE-2.0)
at your option.