diff --git a/src/color.rs b/src/color.rs index 2972ea7..1e25e36 100644 --- a/src/color.rs +++ b/src/color.rs @@ -5,7 +5,9 @@ pub use color::{ xyz_to_rec709_e, }; use float4::Float4; +use half::f16; use spectral_upsampling::meng::{spectrum_xyz_to_p_4, EQUAL_ENERGY_REFLECTANCE}; +use trifloat::signed48; use crate::{lerp::Lerp, math::fast_exp}; @@ -138,6 +140,115 @@ impl Color { Color::Temperature { factor, .. } => factor, } } + + /// Returns the post-compression size of this color. + pub fn compressed_size(&self) -> usize { + match self { + Color::XYZ(_, _, _) => 7, + + Color::Blackbody { .. } => 5, + + Color::Temperature { .. } => 5, + } + } + + /// Writes the compressed form of this color to `out_data`. + /// + /// `out_data` must be at least `compressed_size()` bytes long, otherwise + /// this method will panic. + /// + /// Returns the number of bytes written. + pub fn write_compressed(&self, out_data: &mut [u8]) -> usize { + match *self { + Color::XYZ(x, y, z) => { + out_data[0] = 0; // Discriminant + let col = signed48::encode((x, y, z)); + let col = col.to_le_bytes(); + (&mut out_data[1..7]).copy_from_slice(&col[0..6]); + } + + Color::Blackbody { + temperature, + factor, + } => { + out_data[0] = 1; // Discriminant + let tmp = (temperature.min(std::u16::MAX as f32) as u16).to_le_bytes(); + let fac = f16::from_f32(factor).to_bits().to_le_bytes(); + out_data[1] = tmp[0]; + out_data[2] = tmp[1]; + out_data[3] = fac[0]; + out_data[4] = fac[1]; + } + + Color::Temperature { + temperature, + factor, + } => { + out_data[0] = 2; // Discriminant + let tmp = (temperature.min(std::u16::MAX as f32) as u16).to_le_bytes(); + let fac = f16::from_f32(factor).to_bits().to_le_bytes(); + out_data[1] = tmp[0]; + out_data[2] = tmp[1]; + out_data[3] = fac[0]; + out_data[4] = fac[1]; + } + } + self.compressed_size() + } + + /// Constructs a Color from compressed color data, and also returns the + /// number of bytes consumed from `in_data`. + pub fn from_compressed(in_data: &[u8]) -> (Color, usize) { + match in_data[0] { + 0 => { + // XYZ + let mut bytes = [0u8; 8]; + (&mut bytes[0..6]).copy_from_slice(&in_data[1..7]); + let (x, y, z) = signed48::decode(u64::from_le_bytes(bytes)); + (Color::XYZ(x, y, z), 7) + } + + 1 => { + // Blackbody + let mut tmp = [0u8; 2]; + let mut fac = [0u8; 2]; + tmp[0] = in_data[1]; + tmp[1] = in_data[2]; + fac[0] = in_data[3]; + fac[1] = in_data[4]; + let tmp = u16::from_le_bytes(tmp); + let fac = f16::from_bits(u16::from_le_bytes(fac)); + ( + Color::Blackbody { + temperature: tmp as f32, + factor: fac.into(), + }, + 5, + ) + } + + 2 => { + // Temperature + let mut tmp = [0u8; 2]; + let mut fac = [0u8; 2]; + tmp[0] = in_data[1]; + tmp[1] = in_data[2]; + fac[0] = in_data[3]; + fac[1] = in_data[4]; + let tmp = u16::from_le_bytes(tmp); + let fac = f16::from_bits(u16::from_le_bytes(fac)); + ( + Color::Temperature { + temperature: tmp as f32, + factor: fac.into(), + }, + 5, + ) + } + + _ => unreachable!(), + } + } } impl Mul for Color { diff --git a/src/shading/mod.rs b/src/shading/mod.rs index 73fab72..7927947 100644 --- a/src/shading/mod.rs +++ b/src/shading/mod.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; use crate::{color::Color, surface::SurfaceIntersectionData}; -use self::surface_closure::SurfaceClosure; +pub use self::surface_closure::SurfaceClosure; /// Trait for surface shaders. pub trait SurfaceShader: Debug + Sync { diff --git a/src/shading/surface_closure.rs b/src/shading/surface_closure.rs index 1d01280..be14360 100644 --- a/src/shading/surface_closure.rs +++ b/src/shading/surface_closure.rs @@ -6,7 +6,7 @@ use float4::Float4; use crate::{ color::{Color, SpectralSample}, - lerp::lerp, + lerp::{lerp, Lerp}, math::{clamp, dot, zup_to_vec, Normal, Vector}, sampling::cosine_sample_hemisphere, }; @@ -153,6 +153,128 @@ impl SurfaceClosure { ), } } + + /// Returns the post-compression size of this closure. + pub fn compressed_size(&self) -> usize { + 1 + match *self { + Lambert(color) => color.compressed_size(), + GGX { color, .. } => { + 2 // Roughness + + 2 // Fresnel + + color.compressed_size() // Color + } + Emit(color) => color.compressed_size(), + } + } + + /// Writes the compressed form of this closure to `out_data`. + /// + /// `out_data` must be at least `compressed_size()` bytes long, otherwise + /// this method will panic. + /// + /// Returns the number of bytes written. + pub fn write_compressed(&self, out_data: &mut [u8]) -> usize { + match *self { + Lambert(color) => { + out_data[0] = 0; // Discriminant + color.write_compressed(&mut out_data[1..]); + } + GGX { + color, + roughness, + fresnel, + } => { + out_data[0] = 1; // Discriminant + + // Roughness and fresnel (we write these first because they are + // constant-size, whereas the color is variable-size, so this + // makes things a little easier). + let rgh = + ((roughness.max(0.0).min(1.0) * std::u16::MAX as f32) as u16).to_le_bytes(); + let frs = ((fresnel.max(0.0).min(1.0) * std::u16::MAX as f32) as u16).to_le_bytes(); + out_data[1] = rgh[0]; + out_data[2] = rgh[1]; + out_data[3] = frs[0]; + out_data[4] = frs[1]; + + // Color + color.write_compressed(&mut out_data[5..]); // Color + } + Emit(color) => { + out_data[0] = 2; // Discriminant + color.write_compressed(&mut out_data[1..]); + } + } + self.compressed_size() + } + + /// Constructs a SurfaceClosure from compressed closure data, and also + /// returns the number of bytes consumed from `in_data`. + pub fn from_compressed(in_data: &[u8]) -> (SurfaceClosure, usize) { + match in_data[0] { + 0 => { + // Lambert + let (col, size) = Color::from_compressed(&in_data[1..]); + (SurfaceClosure::Lambert(col), 1 + size) + } + + 1 => { + // GGX + let mut rgh = [0u8; 2]; + let mut frs = [0u8; 2]; + rgh[0] = in_data[1]; + rgh[1] = in_data[2]; + frs[0] = in_data[3]; + frs[1] = in_data[4]; + let rgh = u16::from_le_bytes(rgh) as f32 * (1.0 / std::u16::MAX as f32); + let frs = u16::from_le_bytes(frs) as f32 * (1.0 / std::u16::MAX as f32); + let (col, size) = Color::from_compressed(&in_data[5..]); + ( + SurfaceClosure::GGX { + color: col, + roughness: rgh, + fresnel: frs, + }, + 5 + size, + ) + } + + 2 => { + // Emit + let (col, size) = Color::from_compressed(&in_data[1..]); + (SurfaceClosure::Emit(col), 1 + size) + } + + _ => unreachable!(), + } + } +} + +impl Lerp for SurfaceClosure { + fn lerp(self, other: SurfaceClosure, alpha: f32) -> SurfaceClosure { + match (self, other) { + (Lambert(col1), Lambert(col2)) => Lambert(lerp(col1, col2, alpha)), + ( + GGX { + color: col1, + roughness: rgh1, + fresnel: frs1, + }, + GGX { + color: col2, + roughness: rgh2, + fresnel: frs2, + }, + ) => GGX { + color: lerp(col1, col2, alpha), + roughness: lerp(rgh1, rgh2, alpha), + fresnel: lerp(frs1, frs2, alpha), + }, + (Emit(col1), Emit(col2)) => Emit(lerp(col1, col2, alpha)), + + _ => panic!("Cannot lerp between different surface closure types."), + } + } } /// Lambert closure code. diff --git a/src/surface/micropoly_batch.rs b/src/surface/micropoly_batch.rs index 05beed2..47dfb91 100644 --- a/src/surface/micropoly_batch.rs +++ b/src/surface/micropoly_batch.rs @@ -11,7 +11,7 @@ use crate::{ lerp::lerp_slice, math::{cross, dot, Matrix4x4, Normal, Point}, ray::{RayBatch, RayStack}, - shading::{SimpleSurfaceShader, SurfaceShader}, + shading::SurfaceClosure, }; use super::{triangle, SurfaceIntersection, SurfaceIntersectionData}; @@ -33,7 +33,9 @@ pub struct MicropolyBatch<'a> { normals: &'a [Normal], // Per-vertex shading data. - vertex_closures: &'a [SimpleSurfaceShader], + compressed_vertex_closure_size: usize, // Size in bites of a single compressed closure + vertex_closure_time_sample_count: usize, + compressed_vertex_closures: &'a [u8], // Packed compressed closures // Micro-triangle indices. Each element of the tuple specifies the index // of a vertex, which indexes into all of the arrays above. @@ -127,7 +129,9 @@ impl<'a> MicropolyBatch<'a> { time_sample_count: time_sample_count, vertices: vertices, normals: normals, - vertex_closures: &[], + compressed_vertex_closure_size: 0, + vertex_closure_time_sample_count: 1, + compressed_vertex_closures: &[], indices: indices, accel: accel, } @@ -317,8 +321,16 @@ impl<'a> MicropolyBatch<'a> { // Calculate interpolated surface closure. // TODO: actually interpolate. - let closure = self.vertex_closures - [hit_tri_indices.0 as usize * self.time_sample_count]; + let closure = { + let start_byte = hit_tri_indices.0 as usize + * self.compressed_vertex_closure_size + * self.vertex_closure_time_sample_count; + let end_byte = start_byte + self.compressed_vertex_closure_size; + let (closure, _) = SurfaceClosure::from_compressed( + &self.compressed_vertex_closures[start_byte..end_byte], + ); + closure + }; let intersection_data = SurfaceIntersectionData { incoming: rays.dir(ray_idx), @@ -334,7 +346,7 @@ impl<'a> MicropolyBatch<'a> { // Fill in intersection data isects[ray_idx] = SurfaceIntersection::Hit { intersection_data: intersection_data, - closure: closure.shade(&intersection_data, ray_time), + closure: closure, }; } });