diff --git a/shared/src/core/color.rs b/shared/src/core/color.rs index cd9ee9c..381b081 100644 --- a/shared/src/core/color.rs +++ b/shared/src/core/color.rs @@ -606,7 +606,6 @@ pub struct RGBSigmoidPolynomial { } impl RGBSigmoidPolynomial { - #[cfg(not(target_os = "cuda"))] pub fn new(c0: Float, c1: Float, c2: Float) -> Self { Self { c0, c1, c2 } } @@ -1089,8 +1088,8 @@ impl Mul for Coeffs { #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct RGBToSpectrumTable { - pub z_nodes: Ptr, - pub coeffs: Ptr, + pub z_nodes: GVec, + pub coeffs: GVec, pub n_nodes: u32, } @@ -1137,10 +1136,9 @@ impl RGBToSpectrumTable { let x = coord_a / z; let y = coord_b / z; - let z_nodes_slice = - unsafe { core::slice::from_raw_parts(self.z_nodes.as_raw(), RES as usize) }; - let zi = find_interval(RES, |i| z_nodes_slice[i as usize] < z) as usize; - let dz = (z - z_nodes_slice[zi]) / (z_nodes_slice[zi + 1] - z_nodes_slice[zi]); + let z_nodes = &self.z_nodes; + let zi = find_interval(RES, |i| z_nodes[i as usize] < z) as usize; + let dz = (z - z_nodes[zi]) / (z_nodes[zi + 1] - z_nodes[zi]); let x_float = x * (RES - 1) as Float; let xi = (x_float as u32).min(RES - 2); let dx = x_float - xi as Float; diff --git a/shared/src/core/image.rs b/shared/src/core/image.rs index ae1f3c7..2a29748 100644 --- a/shared/src/core/image.rs +++ b/shared/src/core/image.rs @@ -312,6 +312,24 @@ impl Image { unsafe { self.pixels.read(offset, &self.encoding) } } + pub fn lookup_nearest_channel(&self, p: Point2f, c: i32) -> Float { + self.lookup_nearest_channel_with_wrap(p, c, WrapMode::Clamp.into()) + } + + pub fn lookup_nearest_channel_with_wrap( + &self, + p: Point2f, + c: i32, + wrap_mode: WrapMode2D, + ) -> Float { + let pi = Point2i::new( + p.x() as i32 * self.resolution().x(), + p.y() as i32 * self.resolution().y(), + ); + + self.get_channel_with_wrap(pi, c, wrap_mode) + } + pub fn get_channels_array(&self, p: Point2i) -> [Float; N] { self.get_channels_array_with_wrap(p, WrapMode::Clamp.into()) } diff --git a/shared/src/filters/lanczos.rs b/shared/src/filters/lanczos.rs index d763f17..130551d 100644 --- a/shared/src/filters/lanczos.rs +++ b/shared/src/filters/lanczos.rs @@ -1,4 +1,4 @@ -use crate::core::filter::{DeviceFilterSampler, FilterSample, FilterTrait}; +use crate::core::filter::{FilterSampler, FilterSample, FilterTrait}; use crate::core::geometry::{Point2f, Vector2f}; use crate::utils::math::{lerp, windowed_sinc}; use crate::Float; @@ -8,7 +8,7 @@ use crate::Float; pub struct LanczosSincFilter { pub radius: Vector2f, pub tau: Float, - pub sampler: DeviceFilterSampler, + pub sampler: FilterSampler, pub integral: Float, } diff --git a/shared/src/lights/projection.rs b/shared/src/lights/projection.rs index 67b65e4..612d9dd 100644 --- a/shared/src/lights/projection.rs +++ b/shared/src/lights/projection.rs @@ -3,7 +3,7 @@ use crate::core::color::RGB; use crate::core::geometry::{ Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta, }; -use crate::core::image::{DeviceImage, ImageAccess}; +use crate::core::image::{Image, ImageAccess}; use crate::core::light::{ LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, }; @@ -27,7 +27,7 @@ pub struct ProjectionLight { pub screen_from_light: Transform, pub light_from_screen: Transform, pub a: Float, - pub image: Ptr, + pub image: Ptr, pub distrib: Ptr, pub image_color_space: Ptr, } diff --git a/shared/src/materials/diffuse.rs b/shared/src/materials/diffuse.rs index 0e467ca..b1100a6 100644 --- a/shared/src/materials/diffuse.rs +++ b/shared/src/materials/diffuse.rs @@ -5,7 +5,7 @@ use crate::bxdfs::{ use crate::core::bsdf::BSDF; use crate::core::bssrdf::BSSRDF; use crate::core::bxdf::BxDF; -use crate::core::image::DeviceImage; +use crate::core::image::Image; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::spectrum::{Spectrum, SpectrumTrait}; @@ -17,7 +17,7 @@ use crate::utils::math::clamp; #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct DiffuseMaterial { - pub normal_map: Ptr, + pub normal_map: Ptr, pub displacement: Ptr, pub reflectance: Ptr, } @@ -47,7 +47,7 @@ impl MaterialTrait for DiffuseMaterial { tex_eval.can_evaluate(&[], &[self.reflectance]) } - fn get_normal_map(&self) -> Option<&DeviceImage> { + fn get_normal_map(&self) -> Option<&Image> { Some(&*self.normal_map) } @@ -63,7 +63,7 @@ impl MaterialTrait for DiffuseMaterial { #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct DiffuseTransmissionMaterial { - pub image: Ptr, + pub image: Ptr, pub displacement: Ptr, pub reflectance: Ptr, pub transmittance: Ptr, @@ -92,7 +92,7 @@ impl MaterialTrait for DiffuseTransmissionMaterial { tex_eval.can_evaluate(&[self.reflectance, self.transmittance], &[]) } - fn get_normal_map(&self) -> Option<&DeviceImage> { + fn get_normal_map(&self) -> Option<&Image> { Some(&*self.image) } diff --git a/shared/src/spectra/colorspace.rs b/shared/src/spectra/colorspace.rs index e886601..78f78cf 100644 --- a/shared/src/spectra/colorspace.rs +++ b/shared/src/spectra/colorspace.rs @@ -62,16 +62,16 @@ impl ColorSpaceId { } #[repr(C)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct RGBColorSpace { pub r: Point2f, pub g: Point2f, pub b: Point2f, pub w: Point2f, - pub illuminant: DenselySampledSpectrum, - pub rgb_to_spectrum_table: Ptr, pub xyz_from_rgb: SquareMatrix3f, pub rgb_from_xyz: SquareMatrix3f, + pub illuminant: Ptr, + pub rgb_to_spectrum_table: Ptr, } unsafe impl Send for RGBColorSpace {} diff --git a/shared/src/spectra/simple.rs b/shared/src/spectra/simple.rs index f533dc4..b6c5385 100644 --- a/shared/src/spectra/simple.rs +++ b/shared/src/spectra/simple.rs @@ -1,11 +1,10 @@ use super::cie::*; use super::sampled::{LAMBDA_MAX, LAMBDA_MIN}; -use crate::Float; use crate::core::spectrum::{Spectrum, SpectrumTrait}; -use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; +use crate::spectra::{SampledSpectrum, SampledWavelengths, N_SPECTRUM_SAMPLES}; use crate::utils::find_interval; -use crate::utils::ptr::Ptr; -use core::slice; +use crate::{gvec, gvec_with_capacity, Float, GVec, Ptr}; +use core::hash::{Hash, Hasher}; use num_traits::Float as NumFloat; #[repr(C)] @@ -31,20 +30,73 @@ impl SpectrumTrait for ConstantSpectrum { } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub struct DenselySampledSpectrum { pub lambda_min: i32, pub lambda_max: i32, - pub values: Ptr, + pub values: GVec, } unsafe impl Send for DenselySampledSpectrum {} unsafe impl Sync for DenselySampledSpectrum {} impl DenselySampledSpectrum { + pub fn new(lambda_min: i32, lambda_max: i32, values: GVec) -> Self { + let func_integral = 0.0; + Self { + lambda_min, + lambda_max, + values, + } + } + + pub fn new_zero(lambda_min: i32, lambda_max: i32) -> Self { + let n = (lambda_max - lambda_min + 1).max(0) as usize; + let mut values = gvec_with_capacity(n); + values.resize(n, 0.0); + Self { + lambda_min, + lambda_max, + values, + } + } + + pub fn from_spectrum(spec: &Spectrum) -> Self { + let mut values = gvec_with_capacity((LAMBDA_MAX - LAMBDA_MIN + 1) as usize); + for lambda in LAMBDA_MIN..=LAMBDA_MAX { + values.push(spec.evaluate(lambda as Float)); + } + Self { + lambda_min: LAMBDA_MIN, + lambda_max: LAMBDA_MAX, + values, + } + } + + pub fn from_function(f: F, lambda_min: i32, lambda_max: i32) -> Self + where + F: Fn(Float) -> Float, + { + let mut values = gvec_with_capacity((lambda_max - lambda_min + 1) as usize); + for lambda in lambda_min..=lambda_max { + values.push(f(lambda as Float)); + } + Self { + lambda_min, + lambda_max, + values, + } + } + + pub fn scale(&mut self, s: Float) { + for v in &mut self.values { + *v *= s; + } + } + #[inline(always)] pub fn count(&self) -> usize { - if self.values.is_null() { + if self.values.is_empty() { 0 } else { (self.lambda_max - self.lambda_min + 1) as usize @@ -52,8 +104,8 @@ impl DenselySampledSpectrum { } #[inline(always)] - fn get(&self, idx: u32) -> Float { - unsafe { *self.values.add(idx as usize) } + pub fn value(&self, idx: u32) -> Float { + unsafe { *self.values.as_ptr().add(idx as usize) } } } @@ -67,21 +119,41 @@ impl PartialEq for DenselySampledSpectrum { impl Eq for DenselySampledSpectrum {} +impl Hash for DenselySampledSpectrum { + fn hash(&self, state: &mut H) { + self.lambda_min.hash(state); + self.lambda_max.hash(state); + for &val in self.values.iter() { + val.to_bits().hash(state); + } + } +} + impl SpectrumTrait for DenselySampledSpectrum { + fn max_value(&self) -> Float { + if self.values.is_empty() { + return 0.0; + } + let mut max_val = Float::NEG_INFINITY; + for i in 0..self.count() { + let val = self.value(i as u32); + if val > max_val { + max_val = val; + } + } + max_val + } + fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { let mut s = SampledSpectrum::default(); let n = self.count() as i32; - for i in 0..N_SPECTRUM_SAMPLES { let offset = lambda[i].round() as i32 - self.lambda_min; - - if offset < 0 || offset >= n { - s[i] = 0.0; + s[i] = if offset < 0 || offset >= n { + 0.0 } else { - unsafe { - s[i] = *self.values.add(offset as usize); - } - } + self.value(offset as u32) + }; } s } @@ -92,47 +164,64 @@ impl SpectrumTrait for DenselySampledSpectrum { if offset < 0 || offset >= n { 0.0 } else { - unsafe { *self.values.add(offset as usize) } + self.value(offset as u32) } } - - fn max_value(&self) -> Float { - if self.values.is_null() { - return 0.; - } - - let n = self.count(); - let mut max_val = Float::NEG_INFINITY; - - for i in 0..n { - unsafe { - let val = *self.values.add(i); - if val > max_val { - max_val = val; - } - } - } - max_val - } } #[repr(C)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct PiecewiseLinearSpectrum { - pub lambdas: Ptr, - pub values: Ptr, + pub lambdas: GVec, + pub values: GVec, pub count: u32, } impl PiecewiseLinearSpectrum { #[inline(always)] - fn lambda(&self, i: u32) -> Float { - unsafe { *self.lambdas.add(i as usize) } + pub fn count(&self) -> usize { + if self.values.is_empty() { + 0 + } else { + (self.lambda_max - self.lambda_min + 1) as usize + } } #[inline(always)] - fn value(&self, i: u32) -> Float { - unsafe { *self.values.add(i as usize) } + pub fn value(&self, idx: u32) -> Float { + unsafe { *self.values.as_ptr().add(idx as usize) } + } + + pub fn new(lambdas: GVec, values: GVec) -> Self { + assert_eq!(lambdas.len(), values.len()); + let count = lambdas.len() as u32; + Self { + lambdas, + values, + count, + } + } + + pub fn from_interleaved(data: &[Float], _normalize: bool) -> Self { + assert!( + data.len() % 2 == 0, + "Interleaved data must have even length" + ); + + let n = data.len() / 2; + let mut pairs: GVec<(Float, Float)> = gvec_with_capacity(n); + for chunk in data.chunks(2) { + pairs.push((chunk[0], chunk[1])); + } + pairs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal)); + + let mut lambdas = gvec_with_capacity(n); + let mut values = gvec_with_capacity(n); + for (l, v) in pairs.iter() { + lambdas.push(*l); + values.push(*v); + } + Self::new(lambdas, values) } } @@ -141,7 +230,7 @@ unsafe impl Sync for PiecewiseLinearSpectrum {} impl SpectrumTrait for PiecewiseLinearSpectrum { fn evaluate(&self, lambda: Float) -> Float { - if self.lambdas.is_null() { + if self.lambdas.is_empty() { return 0.0; } @@ -165,7 +254,7 @@ impl SpectrumTrait for PiecewiseLinearSpectrum { } fn max_value(&self) -> Float { - if self.values.is_null() { + if self.values.is_empty() { return 0.; } diff --git a/shared/src/utils/alloc.rs b/shared/src/utils/alloc.rs index c6db9a1..265b3dd 100644 --- a/shared/src/utils/alloc.rs +++ b/shared/src/utils/alloc.rs @@ -38,43 +38,6 @@ unsafe impl Allocator for SystemAlloc { } // Unified memory via cudaMallocManaged - -#[cfg(feature = "cuda")] -pub mod cuda { - use super::*; - use cust::memory::{cuda_free_unified, cuda_malloc_unified, UnifiedPointer}; - - #[derive(Debug, Clone, Copy, Default)] - pub struct CudaAlloc; - - unsafe impl Allocator for CudaAlloc { - fn allocate(&self, layout: Layout) -> Result, AllocError> { - if layout.size() == 0 { - // Zero-sized allocations: return a dangling aligned pointer - // with zero length, which is valid for ZSTs. - let ptr = NonNull::new(layout.align() as *mut u8).ok_or(AllocError)?; - return Ok(NonNull::slice_from_raw_parts(ptr, 0)); - } - - let ptr = cuda_malloc_unified::(layout.size()).map_err(|_| AllocError)?; - - let raw = ptr.as_raw_mut(); - core::mem::forget(ptr); // Arena owns the raw pointer, not the RAII wrapper - - let nn = NonNull::new(raw).ok_or(AllocError)?; - Ok(NonNull::slice_from_raw_parts(nn, layout.size())) - } - - unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { - if layout.size() == 0 { - return; - } - let _ = cuda_free_unified(UnifiedPointer::wrap(ptr.as_ptr())); - } - } -} - -// Host-visible GPU memory via gpu-allocator #[cfg(feature = "cuda")] pub mod cuda { use super::*; diff --git a/shared/src/utils/math.rs b/shared/src/utils/math.rs index 426ec27..6401034 100644 --- a/shared/src/utils/math.rs +++ b/shared/src/utils/math.rs @@ -2,9 +2,10 @@ use crate::core::color::{RGB, XYZ}; use crate::core::geometry::{Lerp, MulAdd, Point, Point2f, Point2i, Vector, Vector3f, VectorLike}; use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI_OVER_4}; use crate::utils::hash::{hash_buffer, mix_bits}; +use crate::utils::math::permutation_element; use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV}; -use crate::utils::{Ptr, gpu_array_from_fn}; +use crate::utils::{gpu_array_from_fn, Ptr}; use core::fmt::{self, Display, Write}; use core::iter::{Product, Sum}; use core::mem; @@ -266,7 +267,11 @@ pub fn quadratic(a: Float, b: Float, c: Float) -> Option<(Float, Float)> { pub fn smooth_step(x: Float, a: Float, b: Float) -> Float { if a == b { - if x < a { return 0. } else { return 1. } + if x < a { + return 0.; + } else { + return 1.; + } } let t = clamp((x - a) / (b - a), 0., 1.); @@ -765,18 +770,50 @@ pub fn inverse_radical_inverse(mut inverse: u64, base: u64, n_digits: u64) -> u6 // Digit scrambling #[repr(C)] -#[derive(Default, Debug, Copy, Clone)] -pub struct DeviceDigitPermutation { +#[derive(Default, Debug, Clone)] +pub struct DigitPermutation { pub base: i32, pub n_digits: u32, - pub permutations: Ptr, + pub permutations: GVec, } -impl DeviceDigitPermutation { +impl DigitPermutation { + pub fn new(base: i32, seed: u64) -> Self { + assert!(base < 65536); + let mut n_digits: u32 = 0; + let inv_base = 1. / base as Float; + let mut inv_base_m = 1.; + + while 1.0 - ((base as Float - 1.0) * inv_base_m) < 1.0 { + n_digits += 1; + inv_base_m *= inv_base; + } + + let mut permutations = gvec_with_capacity(n_digits as usize * base as usize); + + for digit_index in 0..n_digits { + let hash_input = [base as u64, digit_index as u64, seed]; + let dseed = hash_buffer(&hash_input, 0); + + for digit_value in 0..base { + let index = (digit_index as i32 * base + digit_value) as usize; + + permutations[index] = + permutation_element(digit_value as u32, base as u32, dseed as u32) as u16; + } + } + + Self { + device, + permutations, + n_digits, + } + } + #[inline(always)] pub fn permute(&self, digit_index: i32, digit_value: i32) -> i32 { let idx = (digit_index * self.base as i32 + digit_value) as usize; - let permutation = unsafe { *self.permutations.add(idx.into()) }; + let permutation = unsafe { *self.permutations.as_ptr().add(idx.into()) }; permutation as i32 } } @@ -1448,7 +1485,11 @@ where } let det: T = (0..N).map(|i| lum[i][i]).product(); - if parity < 0 { -det } else { det } + if parity < 0 { + -det + } else { + det + } } } } diff --git a/shared/src/utils/sampling.rs b/shared/src/utils/sampling.rs index db63650..50007f7 100644 --- a/shared/src/utils/sampling.rs +++ b/shared/src/utils/sampling.rs @@ -1,7 +1,6 @@ use crate::core::geometry::{ Bounds2f, Frame, Point2f, Point2i, Point3f, Vector2f, Vector2i, Vector3f, VectorLike, }; -use crate::{GVec, gvec_with_capacity, gvec_from_slice, Array2D}; use crate::utils::find_interval; use crate::utils::math::{ catmull_rom_weights, clamp, difference_of_products, evaluate_polynomial, lerp, logistic, @@ -9,6 +8,7 @@ use crate::utils::math::{ }; use crate::utils::ptr::Ptr; use crate::utils::rng::Rng; +use crate::{gvec_from_slice, gvec_with_capacity, Array2D, GVec}; use crate::{Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4}; use num_traits::Float as NumFloat; use num_traits::Num; @@ -834,17 +834,37 @@ impl PiecewiseConstant2D { for v in 0..n_v { let row = &data[v * n_u..(v + 1) * n_u]; - let conditional = PiecewiseConstant1D::new_with_bounds( - row, domain.p_min.x(), domain.p_max.x(), - ); + let conditional = + PiecewiseConstant1D::new_with_bounds(row, domain.p_min.x(), domain.p_max.x()); marginal_func.push(conditional.integral()); conditionals.push(conditional); } let marginal = PiecewiseConstant1D::new_with_bounds( - &marginal_func, domain.p_min.y(), domain.p_max.y(), + &marginal_func, + domain.p_min.y(), + domain.p_max.y(), ); + pub fn from_image(image: &Image) -> Self { + let res = image.resolution(); + let n_u = res.x() as usize; + let n_v = res.y() as usize; + + let mut data = Vec::with_capacity(n_u * n_v); + for v in 0..n_v { + for u in 0..n_u { + data.push( + image + .get_channels(Point2i::new(u as i32, v as i32)) + .average(), + ); + } + } + + Self::from_slice(&data, n_u, n_v, Bounds2f::unit()) + } + Self { conditionals, marginal, @@ -869,15 +889,36 @@ impl PiecewiseConstant2D { } } - #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct DeviceSummedAreaTable { +pub struct SummedAreaTable { pub sum: Array2D, } -impl DeviceSummedAreaTable { - // +impl SummedAreaTable { + pub fn new(values: &Array2D) -> Self { + let width = values.x_size() as i32; + let height = values.y_size() as i32; + + let mut sum = Array2D::::new_dims(width, height); + sum[(0, 0)] = values[(0, 0)] as f64; + + for x in 1..width { + sum[(x, 0)] = values[(x, 0)] as f64 + sum[(x - 1, 0)]; + } + for y in 1..height { + sum[(0, y)] = values[(0, y)] as f64 + sum[(0, y - 1)]; + } + for y in 1..height { + for x in 1..width { + sum[(x, y)] = + values[(x, y)] as f64 + sum[(x - 1, y)] + sum[(x, y - 1)] - sum[(x - 1, y - 1)]; + } + } + + Self { sum } + } + pub fn integral(&self, extent: Bounds2f) -> Float { let s = self.lookup(extent.p_max.x(), extent.p_max.y()) - self.lookup(extent.p_min.x(), extent.p_max.y()) @@ -924,12 +965,17 @@ impl DeviceSummedAreaTable { #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct DeviceWindowedPiecewiseConstant2D { - pub sat: DeviceSummedAreaTable, +pub struct WindowedPiecewiseConstant2D { + pub sat: SummedAreaTable, pub func: Array2D, } -impl DeviceWindowedPiecewiseConstant2D { +impl WindowedPiecewiseConstant2D { + pub fn new(func: Array2D) -> Self { + let sat = SummedAreaTable::new(&func); + Self { sat, func } + } + pub fn sample(&self, u: Point2f, b: Bounds2f) -> Option<(Point2f, Float)> { let b_int = self.sat.integral(b); if b_int == 0.0 { @@ -1039,62 +1085,116 @@ pub struct Bin { } #[repr(C)] -#[derive(Copy, Debug, Clone)] +#[derive(Debug, Clone)] pub struct AliasTable { - pub bins: Ptr, - pub size: u32, + pub bins: GVec, } unsafe impl Send for AliasTable {} unsafe impl Sync for AliasTable {} impl AliasTable { - #[inline(always)] - fn bin(&self, idx: u32) -> Ptr { - unsafe { self.bins.add(idx as usize) } + pub fn new(weights: &[Float]) -> Self { + let n = weights.len(); + if n == 0 { + return Self { bins: gvec() }; + } + + let sum: f64 = weights.iter().map(|&w| w as f64).sum(); + assert!(sum > 0.0, "Sum of weights must be positive"); + + let mut bins = gvec_with_capacity(n); + for &w in weights { + bins.push(Bin { + p: (w as f64 / sum) as Float, + q: 0.0, + alias: 0, + }); + } + + struct Outcome { + p_hat: f64, + index: usize, + } + + let mut under = Vec::with_capacity(n); + let mut over = Vec::with_capacity(n); + + for (i, bin) in bins.iter().enumerate() { + let p_hat = (bin.p as f64) * (n as f64); + if p_hat < 1.0 { + under.push(Outcome { p_hat, index: i }); + } else { + over.push(Outcome { p_hat, index: i }); + } + } + + while !under.is_empty() && !over.is_empty() { + let un = under.pop().unwrap(); + let ov = over.pop().unwrap(); + bins[un.index].q = un.p_hat as Float; + bins[un.index].alias = ov.index as u32; + let p_excess = un.p_hat + ov.p_hat - 1.0; + if p_excess < 1.0 { + under.push(Outcome { + p_hat: p_excess, + index: ov.index, + }); + } else { + over.push(Outcome { + p_hat: p_excess, + index: ov.index, + }); + } + } + + while let Some(ov) = over.pop() { + bins[ov.index].q = 1.0; + bins[ov.index].alias = ov.index as u32; + } + while let Some(un) = under.pop() { + bins[un.index].q = 1.0; + bins[un.index].alias = un.index as u32; + } + + Self { bins } } pub fn size(&self) -> u32 { - self.size + self.bins.len() as u32 + } + + pub fn is_empty(&self) -> bool { + self.bins.is_empty() } pub fn pmf(&self, index: u32) -> Float { if index >= self.size() { return 0.0; } - self.bin(index).p + self.bins[index as usize].p } pub fn sample(&self, u: Float) -> (u32, Float, Float) { - if self.size == 0 { + if self.bins.is_empty() { return (0, 0.0, 0.0); } - let n = self.size as Float; - + let n = self.bins.len() as Float; let val = u * n; let offset = (val.min(n - 1.0)) as u32; + let up = (val - offset as Float).min(ONE_MINUS_EPSILON); - let up = (val - (offset as Float)).min(ONE_MINUS_EPSILON); - - let bin = self.bin(offset); + let bin = &self.bins[offset as usize]; if up < bin.q { - debug_assert!(bin.p > 0.0); - let pmf = bin.p; - let u_remapped = (up / bin.q).min(ONE_MINUS_EPSILON); - (offset, pmf, u_remapped) } else { let alias_idx = bin.alias; - - let alias_p = self.bin(alias_idx).p; - debug_assert!(alias_p > 0.0); - + let alias_p = self.bins[alias_idx as usize].p; let u_remapped = ((up - bin.q) / (1.0 - bin.q)).min(ONE_MINUS_EPSILON); - (alias_idx, alias_p, u_remapped) } } @@ -1114,6 +1214,114 @@ pub struct PiecewiseLinear2D { } impl PiecewiseLinear2D { + pub fn new( + data: &[Float], + x_size: i32, + y_size: i32, + param_res: [usize; N], + param_values: [&[Float]; N], + normalize: bool, + build_cdf: bool, + ) -> Self { + if build_cdf && !normalize { + panic!("PiecewiseLinear2D: build_cdf implies normalize=true"); + } + + let size = Vector2i::new(x_size, y_size); + let inv_patch_size = + Vector2f::new(1.0 / (x_size - 1) as Float, 1.0 / (y_size - 1) as Float); + + let mut param_size = [0u32; N]; + let mut param_strides = [0u32; N]; + let owned_param_values: [Vec; N] = gpu_array_from_fn(|i| param_values[i].to_vec()); + + let mut slices: u32 = 1; + for i in (0..N).rev() { + assert!(param_res[i] >= 1, "Parameter resolution must be >= 1"); + param_size[i] = param_res[i] as u32; + param_strides[i] = if param_res[i] > 1 { slices } else { 0 }; + slices *= param_size[i]; + } + + let n_values = (x_size * y_size) as usize; + let mut new_data = vec![0.0; slices as usize * n_values]; + let mut marginal_cdf = if build_cdf { + vec![0.0; slices as usize * y_size as usize] + } else { + Vec::new() + }; + let mut conditional_cdf = if build_cdf { + vec![0.0; slices as usize * n_values] + } else { + Vec::new() + }; + + let mut data_offset = 0; + for slice in 0..slices as usize { + let slice_offset = slice * n_values; + let current_data = &data[data_offset..data_offset + n_values]; + let mut sum = 0.0_f64; + + if normalize { + for y in 0..(y_size - 1) { + for x in 0..(x_size - 1) { + let i = (y * x_size + x) as usize; + let v00 = current_data[i] as f64; + let v10 = current_data[i + 1] as f64; + let v01 = current_data[i + x_size as usize] as f64; + let v11 = current_data[i + 1 + x_size as usize] as f64; + sum += 0.25 * (v00 + v10 + v01 + v11); + } + } + } + + let normalization = if normalize && sum > 0.0 { + 1.0 / sum as Float + } else { + 1.0 + }; + for k in 0..n_values { + new_data[slice_offset + k] = current_data[k] * normalization; + } + + if build_cdf { + let marginal_slice_offset = slice * y_size as usize; + for y in 0..y_size as usize { + let mut cdf_sum = 0.0; + let i_base = y * x_size as usize; + conditional_cdf[slice_offset + i_base] = 0.0; + for x in 0..(x_size - 1) as usize { + let i = i_base + x; + cdf_sum += + 0.5 * (new_data[slice_offset + i] + new_data[slice_offset + i + 1]); + conditional_cdf[slice_offset + i + 1] = cdf_sum; + } + } + marginal_cdf[marginal_slice_offset] = 0.0; + let mut marginal_sum = 0.0; + for y in 0..(y_size - 1) as usize { + let cdf1 = conditional_cdf[slice_offset + (y + 1) * x_size as usize - 1]; + let cdf2 = conditional_cdf[slice_offset + (y + 2) * x_size as usize - 1]; + marginal_sum += 0.5 * (cdf1 + cdf2); + marginal_cdf[marginal_slice_offset + y + 1] = marginal_sum; + } + } + data_offset += n_values; + } + + Self { + size, + inv_patch_size, + param_size, + param_strides, + storage, + data: new_data, + marginal_cdf, + conditional_cdf, + param_values: owned_param_values, + } + } + pub fn sample(&self, mut sample: Point2f, params: [Float; N]) -> PLSample { sample = Point2f::new( sample.x().clamp(0.0, ONE_MINUS_EPSILON), diff --git a/src/core/aggregates.rs b/src/core/aggregates.rs index fc03238..821d51e 100644 --- a/src/core/aggregates.rs +++ b/src/core/aggregates.rs @@ -1,5 +1,5 @@ use rayon::prelude::*; -use shared::core::aggregates::{DeviceBVHAggregate, LinearBVHNode}; +use shared::core::aggregates::{BVHAggregate, LinearBVHNode}; use shared::core::geometry::{Bounds3f, Point3f, Ray, Vector3f}; use shared::core::primitive::{Primitive, PrimitiveTrait}; use shared::core::shape::ShapeIntersection; diff --git a/src/core/color.rs b/src/core/color.rs index 42ef9d4..fef7e3c 100644 --- a/src/core/color.rs +++ b/src/core/color.rs @@ -1,50 +1,33 @@ use crate::utils::read_float_file; use anyhow::Result; use shared::core::color::{Coeffs, RES, RGBToSpectrumTable}; -use shared::{Float, Ptr}; +use shared::{Float, Ptr, gvec_from_slice}; use std::ops::Deref; use std::path::Path; -pub struct RGBToSpectrumTableData { - _z_nodes: Vec, - _coeffs: Vec, - - pub view: RGBToSpectrumTable, +pub trait CreateRGBToSpectrumTable { + fn from_data(z_nodes: &[Float], coeffs: &[Float]) -> Self; + fn load(base_dir: &Path, name: &str) -> Result where Self: Sized; } -impl Deref for RGBToSpectrumTableData { - type Target = RGBToSpectrumTable; - fn deref(&self) -> &Self::Target { - &self.view - } -} - -impl RGBToSpectrumTableData { - pub fn new(z_nodes: Vec, coeffs: Vec) -> Self { - eprintln!("z_nodes.len() = {}, coeffs.len() = {}", z_nodes.len(), coeffs.len()); +impl CreateRGBToSpectrumTable for RGBToSpectrumTable { + fn new(z_nodes: &[Float], coeffs: &[Float]) -> Self { assert_eq!(z_nodes.len(), RES as usize); assert_eq!(coeffs.len(), (RES * RES * RES) as usize * 3 * 3); - - let view = RGBToSpectrumTable { - z_nodes: Ptr::from(z_nodes.as_ptr()), - coeffs: Ptr::from(coeffs.as_ptr() as *const Coeffs), - n_nodes: z_nodes.len() as u32, - }; - Self { - _z_nodes: z_nodes, - _coeffs: coeffs, - view, + z_nodes: gvec_from_slice(z_nodes), + coeffs: gvec_from_slice(unsafe { + core::slice::from_raw_parts( + coeffs.as_ptr() as *const Coeffs, + coeffs.len() / 3, + ) + }), } } - pub fn load(base_dir: &Path, name: &str) -> Result { - let z_path = base_dir.join(format!("{}_znodes.dat", name)); - let c_path = base_dir.join(format!("{}_coeffs.dat", name)); - - let z_nodes = read_float_file(&z_path.to_str().unwrap())?; - let coeffs = read_float_file(&c_path.to_str().unwrap())?; - - Ok(Self::new(z_nodes, coeffs)) + fn load(base_dir: &Path, name: &str) -> Result { + let scale = read_float_file(base_dir.join(format!("{}_scale.dat", name)).to_str().unwrap())?; + let coeffs = read_float_file(base_dir.join(format!("{}_coeffs.dat", name)).to_str().unwrap())?; + Ok(Self::new(&scale, &coeffs)) } } diff --git a/src/core/film.rs b/src/core/film.rs index b19ea73..5bb41bb 100644 --- a/src/core/film.rs +++ b/src/core/film.rs @@ -1,53 +1,62 @@ -use crate::core::image::{Image, ImageChannelDesc, ImageChannelValues, ImageIO, ImageMetadata}; +use crate::core::image::{HostImage, ImageChannelDesc, ImageChannelValues, ImageIO, ImageMetadata}; use crate::films::*; use crate::spectra::data::get_named_spectrum; -use crate::spectra::piecewise::PiecewiseLinearSpectrumBuffer; -use crate::Arena; use anyhow::{anyhow, Result}; use rayon::iter::ParallelIterator; use rayon::prelude::IntoParallelIterator; use shared::core::camera::CameraTransform; use shared::core::color::{white_balance, RGB, SRGB, XYZ}; -use shared::core::film::{DevicePixelSensor, Film, FilmBase, GBufferFilm, RGBFilm, SpectralFilm}; +use shared::core::film::{Film, FilmBase, GBufferFilm, PixelSensor, RGBFilm, SpectralFilm}; use shared::core::filter::{Filter, FilterTrait}; use shared::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i}; use shared::core::image::PixelFormat; use shared::core::spectrum::Spectrum; -use shared::spectra::{cie::SWATCHES_RAW, RGBColorSpace}; +use shared::spectra::{ + cie::SWATCHES_RAW, DenselySampledSpectrum, PiecewiseLinearSpectrum, RGBColorSpace, +}; use shared::utils::math::{linear_least_squares, SquareMatrix}; use shared::{Float, Ptr}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, LazyLock}; -use crate::spectra::{ - get_spectra_context, DenselySampledSpectrumBuffer, CIE_X_DATA, CIE_Y_DATA, CIE_Z_DATA, -}; -use crate::utils::{FileLoc, ParameterDictionary}; +use crate::spectra::{get_spectra_context, CIE_X_DATA, CIE_Y_DATA, CIE_Z_DATA}; +use crate::{Arena, FileLoc, ParameterDictionary}; const N_SWATCH_REFLECTANCES: usize = 24; const SWATCH_REFLECTANCES: LazyLock<[Spectrum; N_SWATCH_REFLECTANCES]> = LazyLock::new(|| { std::array::from_fn(|i| { let raw_data = SWATCHES_RAW[i]; - let pls = PiecewiseLinearSpectrumBuffer::from_interleaved(raw_data, false); - Spectrum::Piecewise(pls.device) + let pls = PiecewiseLinearSpectrum::from_interleaved(raw_data, false); + Spectrum::Piecewise(pls) }) }); -#[derive(Debug, Clone)] -struct SensorStorage { - r_bar: DenselySampledSpectrumBuffer, - g_bar: DenselySampledSpectrumBuffer, - b_bar: DenselySampledSpectrumBuffer, +pub trait CreatePixelSensor: Sized { + fn create( + params: &ParameterDictionary, + output_colorspace: Arc, + exposure_time: Float, + loc: &FileLoc, + ) -> Result; + + fn new( + r: &Spectrum, + g: &Spectrum, + b: &Spectrum, + output_colorspace: Arc, + sensor_illum: Option<&Spectrum>, + imaging_ratio: Float, + ) -> Self; + + fn new_with_white_balance( + output_colorspace: &RGBColorSpace, + sensor_illum: Option<&Spectrum>, + imaging_ratio: Float, + ) -> Self; } -#[derive(Debug, Clone)] -pub struct PixelSensor { - device: DevicePixelSensor, - data: SensorStorage, -} - -impl PixelSensor { - pub fn create( +impl CreatePixelSensor for PixelSensor { + fn create( params: &ParameterDictionary, output_colorspace: Arc, exposure_time: Float, @@ -65,9 +74,9 @@ impl PixelSensor { let imaging_ratio = exposure_time * iso / 100.; let d_illum = if white_balance_temp == 0. { - DenselySampledSpectrumBuffer::generate_cie_d(6500.) + DenselySampledSpectrum::generate_cie_d(6500.) } else { - DenselySampledSpectrumBuffer::generate_cie_d(white_balance_temp) + DenselySampledSpectrum::generate_cie_d(white_balance_temp) }; let sensor_illum: Option> = if white_balance_temp != 0. { @@ -121,28 +130,25 @@ impl PixelSensor { sensor_illum: Option<&Spectrum>, imaging_ratio: Float, ) -> Self { - // As seen in usages of this constructos, sensor_illum can be null - // Going with the colorspace's own illuminant, but this might not be the right choice - // TODO: Test this let illum: &Spectrum = match sensor_illum { Some(arc_illum) => arc_illum, None => &Spectrum::Dense(output_colorspace.as_ref().illuminant), }; - let r_bar = DenselySampledSpectrumBuffer::from_spectrum(r); - let g_bar = DenselySampledSpectrumBuffer::from_spectrum(g); - let b_bar = DenselySampledSpectrumBuffer::from_spectrum(b); + let r_bar = DenselySampledSpectrum::from_spectrum(r); + let g_bar = DenselySampledSpectrum::from_spectrum(g); + let b_bar = DenselySampledSpectrum::from_spectrum(b); let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES]; let swatches = Self::get_swatches(); for i in 0..N_SWATCH_REFLECTANCES { - let rgb = DevicePixelSensor::project_reflectance::( + let rgb = PixelSensor::project_reflectance::( &swatches[i], illum, - &Spectrum::Dense(r_bar.device()), - &Spectrum::Dense(g_bar.device()), - &Spectrum::Dense(b_bar.device()), + &Spectrum::Dense(r_bar), + &Spectrum::Dense(g_bar), + &Spectrum::Dense(b_bar), ); for c in 0..3 { rgb_camera[i][c] = rgb[c]; @@ -151,11 +157,11 @@ impl PixelSensor { let mut xyz_output = [[0.; 3]; N_SWATCH_REFLECTANCES]; let spectra = get_spectra_context(); - let sensor_white_g = illum.inner_product(&Spectrum::Dense(g_bar.device())); + let sensor_white_g = illum.inner_product(&Spectrum::Dense(g_bar)); let sensor_white_y = illum.inner_product(&Spectrum::Dense(spectra.y)); for i in 0..N_SWATCH_REFLECTANCES { let s = swatches[i].clone(); - let xyz = DevicePixelSensor::project_reflectance::( + let xyz = PixelSensor::project_reflectance::( &s, illum, &Spectrum::Dense(spectra.x), @@ -170,21 +176,13 @@ impl PixelSensor { let xyz_from_sensor_rgb = linear_least_squares(rgb_camera, xyz_output) .expect("Could not convert sensor illuminance to XYZ space"); - let data = SensorStorage { + PixelSensor { r_bar: r_bar.clone(), g_bar: g_bar.clone(), b_bar: b_bar.clone(), - }; - - let device = DevicePixelSensor { - r_bar: r_bar.device(), - g_bar: g_bar.device(), - b_bar: b_bar.device(), imaging_ratio, xyz_from_sensor_rgb, - }; - - Self { device, data } + } } fn new_with_white_balance( @@ -206,25 +204,13 @@ impl PixelSensor { xyz_from_sensor_rgb = SquareMatrix::::default(); } - let data = SensorStorage { + PixelSensor { r_bar: r_bar.clone(), g_bar: g_bar.clone(), b_bar: b_bar.clone(), - }; - - let device = DevicePixelSensor { - r_bar: r_bar.device(), - g_bar: g_bar.device(), - b_bar: b_bar.device(), xyz_from_sensor_rgb, imaging_ratio, - }; - - Self { data, device } - } - - pub fn device(&self) -> DevicePixelSensor { - self.device + } } fn get_swatches() -> Arc<[Spectrum; N_SWATCH_REFLECTANCES]> { @@ -236,7 +222,7 @@ pub trait CreateFilmBase { fn create( params: &ParameterDictionary, filter: Filter, - sensor: Option<&DevicePixelSensor>, + sensor: Option<&PixelSensor>, loc: &FileLoc, ) -> Result where @@ -247,7 +233,7 @@ impl CreateFilmBase for FilmBase { fn create( params: &ParameterDictionary, filter: Filter, - sensor: Option<&DevicePixelSensor>, + sensor: Option<&PixelSensor>, loc: &FileLoc, ) -> Result where @@ -314,7 +300,7 @@ pub trait FilmTrait: Sync { image.write(filename, metadata).expect("Something") } - fn get_image(&self, _metadata: &ImageMetadata, splat_scale: Float) -> Image { + fn get_image(&self, _metadata: &ImageMetadata, splat_scale: Float) -> HostImage { let write_fp16 = true; let format = if write_fp16 { PixelFormat::F16 @@ -362,7 +348,7 @@ pub trait FilmTrait: Sync { }) .collect(); - let mut image = Image::new(format, resolution, channel_names, SRGB.into()); + let mut image = HostImage::new(format, resolution, channel_names, SRGB.into()); let _rgb_desc = ImageChannelDesc::new(&[0, 1, 2]); for (iy, row_data) in processed_rows.into_iter().enumerate() { diff --git a/src/core/light.rs b/src/core/light.rs index 8858645..1494914 100644 --- a/src/core/light.rs +++ b/src/core/light.rs @@ -1,26 +1,26 @@ use crate::core::spectrum::SPECTRUM_CACHE; use crate::core::texture::FloatTexture; -use crate::spectra::DenselySampledSpectrumBuffer; -use crate::utils::{Arena, FileLoc, ParameterDictionary}; -use anyhow::{Result, anyhow}; +use crate::{Arena, FileLoc, ParameterDictionary}; +use anyhow::{anyhow, Result}; use shared::core::camera::CameraTransform; use shared::core::light::Light; use shared::core::medium::Medium; use shared::core::shape::Shape; +use shared::spectra::DenselySampledSpectrum; use shared::core::spectrum::Spectrum; use shared::spectra::RGBColorSpace; -use shared::utils::Transform; +use shared::Transform; use std::sync::Arc; -pub fn lookup_spectrum(s: &Spectrum) -> Arc { +pub fn lookup_spectrum(s: &Spectrum) -> Arc { let cache = &SPECTRUM_CACHE; - let dense_spectrum = DenselySampledSpectrumBuffer::from_spectrum(s); + let dense_spectrum = DenselySampledSpectrum::from_spectrum(s); cache.lookup(dense_spectrum).into() } // Placeholders for non-area lights that never inspect these arguments. -// TODO: refactor each light's create to only take what it actually needs, -// then delete these. +// TODO: refactor each light to only take what it actually needs, +// then delete this bullshit fn dummy_shape() -> Shape { Shape::default() } @@ -29,7 +29,6 @@ fn dummy_alpha() -> FloatTexture { FloatTexture::default() } -/// Create a non-area light from a scene file directive. pub fn create_light( name: &str, render_from_light: Transform, @@ -110,8 +109,7 @@ pub fn create_light( } } -/// Create a diffuse area light bound to a specific shape. -/// Called once per sub-shape (e.g. once per triangle in a mesh). +/// Create a diffuse area light bound to a specific shape pub fn create_area_light( render_from_light: Transform, medium: Option, diff --git a/src/core/medium.rs b/src/core/medium.rs index 370b5f6..e2bb5a6 100644 --- a/src/core/medium.rs +++ b/src/core/medium.rs @@ -1,8 +1,7 @@ -use crate::spectra::dense::DenselySampledSpectrumBuffer; use shared::core::geometry::{Bounds3f, Point3i}; use shared::core::medium::{GridMedium, HGPhaseFunction, HomogeneousMedium, RGBGridMedium}; use shared::core::spectrum::{Spectrum, SpectrumTrait}; -use shared::spectra::{RGBIlluminantSpectrum, RGBUnboundedSpectrum}; +use shared::spectra::{RGBIlluminantSpectrum, RGBUnboundedSpectrum, DenselySampledSpectrum}; use shared::utils::Transform; use shared::utils::containers::SampledGrid; use shared::{Float, core::medium::MajorantGrid}; @@ -109,13 +108,13 @@ impl GridMediumCreator for GridMedium { le: &Spectrum, le_scale: SampledGrid, ) -> Self { - let mut sigma_a_spec = DenselySampledSpectrumBuffer::from_spectrum(sigma_a); - let mut sigma_s_spec = DenselySampledSpectrumBuffer::from_spectrum(sigma_s); + let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(sigma_a); + let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(sigma_s); sigma_a_spec.scale(sigma_scale); sigma_s_spec.scale(sigma_scale); - let le_spec = DenselySampledSpectrumBuffer::from_spectrum(le); + let le_spec = DenselySampledSpectrum::from_spectrum(le); let mut majorant_grid = MajorantGridHost::new(*bounds, Point3i::new(16, 16, 16)).device; let is_emissive = if temperature_grid.is_some() { @@ -169,9 +168,9 @@ impl HomogeneousMediumCreator for HomogeneousMedium { le_scale: Float, g: Float, ) -> Self { - let mut sigma_a_spec = DenselySampledSpectrumBuffer::from_spectrum(&sigma_a); - let mut sigma_s_spec = DenselySampledSpectrumBuffer::from_spectrum(&sigma_s); - let mut le_spec = DenselySampledSpectrumBuffer::from_spectrum(&le); + let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(&sigma_a); + let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(&sigma_s); + let mut le_spec = DenselySampledSpectrum::from_spectrum(&le); sigma_a_spec.scale(sigma_scale); sigma_s_spec.scale(sigma_scale); diff --git a/src/core/spectrum.rs b/src/core/spectrum.rs index e836a91..70cb74e 100644 --- a/src/core/spectrum.rs +++ b/src/core/spectrum.rs @@ -1,18 +1,19 @@ -use crate::spectra::{DenselySampledSpectrumBuffer, cie_y}; +use crate::spectra::cie_y; use crate::utils::containers::InternCache; use parking_lot::Mutex; use shared::Float; use shared::core::spectrum::Spectrum; +use shared::spectra::DenselySampledSpectrum; use std::collections::HashMap; use std::sync::LazyLock; -pub static SPECTRUM_CACHE: LazyLock> = +pub static SPECTRUM_CACHE: LazyLock> = LazyLock::new(InternCache::new); pub static SPECTRUM_FILE_CACHE: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); -pub fn get_spectrum_cache() -> &'static InternCache { +pub fn get_spectrum_cache() -> &'static InternCache { &SPECTRUM_CACHE } diff --git a/src/core/texture.rs b/src/core/texture.rs index 56d7eca..1de74fa 100644 --- a/src/core/texture.rs +++ b/src/core/texture.rs @@ -1,7 +1,7 @@ use crate::textures::*; use crate::utils::mipmap::{MIPMapFilterOptions, MIPMap}; use crate::utils::TextureParameterDictionary; -use crate::{Arena, Device, FileLoc, DeviceRepr}; +use crate::{Arena, FileLoc}; use anyhow::{anyhow, Result}; use enum_dispatch::enum_dispatch; use shared::core::color::ColorEncoding; @@ -29,37 +29,18 @@ pub trait SpectrumTextureTrait { fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum; } -#[derive(Clone, Debug, Device)] -#[device(name = "GPUFloatTexture")] +#[derive(Clone, Debug)] pub enum FloatTexture { - #[device(clone)] Constant(FloatConstantTexture), - - #[device(clone)] Checkerboard(FloatCheckerboardTexture), - - #[device(clone)] Dots(FloatDotsTexture), - - #[device(clone)] FBm(FBmTexture), - - #[device(clone)] Windy(WindyTexture), - - #[device(clone)] Wrinkled(WrinkledTexture), - - Scaled(FloatScaledTexture), - Mix(FloatMixTexture), - DirectionMix(FloatDirectionMixTexture), - - #[device(custom = "upload_image", variant_type = "GPUFloatImageTexture")] + // #[device(custom = "upload_image", variant_type = "GPUFloatImageTexture")] Image(FloatImageTexture), - - #[device(clone)] Bilerp(FloatBilerpTexture), } @@ -124,34 +105,19 @@ impl FloatTexture { } } -#[derive(Clone, Debug, Device)] -#[device(name = "GPUSpectrumTexture")] pub enum SpectrumTexture { - #[device(clone)] Constant(SpectrumConstantTexture), - - #[device(clone)] Checkerboard(SpectrumCheckerboardTexture), - - #[device(clone)] Dots(SpectrumDotsTexture), - - #[device( - custom = "upload_spectrum_image", - variant_type = "GPUSpectrumImageTexture" - )] + // #[device( + // custom = "upload_spectrum_image", + // variant_type = "GPUSpectrumImageTexture" + // )] Image(SpectrumImageTexture), - - #[device(clone)] Bilerp(SpectrumBilerpTexture), - Scaled(SpectrumScaledTexture), - - #[device(clone)] Marble(MarbleTexture), - Mix(SpectrumMixTexture), - DirectionMix(SpectrumDirectionMixTexture), } diff --git a/src/globals.rs b/src/globals.rs index 7b15fde..2ac7618 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -1,6 +1,6 @@ -use crate::core::color::RGBToSpectrumTableData; -use shared::core::color::RES; +use crate::core::color::CreateRGBToSpectrumTable; use once_cell::sync::Lazy; +use shared::core::color::{RGBToSpectrumTable, RES}; use shared::Float; use shared::PBRTOptions; use std::sync::OnceLock; @@ -30,7 +30,6 @@ fn aligned_cast(bytes: &[u8]) -> &[Float] { } } - static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../data/srgb_scale.dat"); static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../data/srgb_coeffs.dat"); static DCI_P3_SCALE_BYTES: &[u8] = include_bytes!("../data/dcip3_scale.dat"); @@ -40,7 +39,6 @@ static ACES_COEFFS_BYTES: &[u8] = include_bytes!("../data/aces_coeffs.dat"); static REC2020_SCALE_BYTES: &[u8] = include_bytes!("../data/rec2020_scale.dat"); static REC2020_COEFFS_BYTES: &[u8] = include_bytes!("../data/rec2020_coeffs.dat"); - fn strip_to_len(bytes: &[u8], expected_len: usize) -> &'static [Float] { let all: Vec = bytemuck::pod_collect_to_vec(bytes); let skip = all.len() - expected_len; @@ -53,20 +51,24 @@ const COEFFS_LEN: usize = (RES * RES * RES) as usize * 3 * 3; pub static SRGB_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(SRGB_SCALE_BYTES, RES as usize)); pub static SRGB_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(SRGB_COEFFS_BYTES, COEFFS_LEN)); -pub static DCI_P3_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(DCI_P3_SCALE_BYTES, RES as usize)); -pub static DCI_P3_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(DCI_P3_COEFFS_BYTES, COEFFS_LEN)); +pub static DCI_P3_SCALE: Lazy<&[Float]> = + Lazy::new(|| strip_to_len(DCI_P3_SCALE_BYTES, RES as usize)); +pub static DCI_P3_COEFFS: Lazy<&[Float]> = + Lazy::new(|| strip_to_len(DCI_P3_COEFFS_BYTES, COEFFS_LEN)); pub static ACES_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(ACES_SCALE_BYTES, RES as usize)); pub static ACES_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(ACES_COEFFS_BYTES, COEFFS_LEN)); -pub static REC2020_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(REC2020_SCALE_BYTES, RES as usize)); -pub static REC2020_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(REC2020_COEFFS_BYTES, COEFFS_LEN)); +pub static REC2020_SCALE: Lazy<&[Float]> = + Lazy::new(|| strip_to_len(REC2020_SCALE_BYTES, RES as usize)); +pub static REC2020_COEFFS: Lazy<&[Float]> = + Lazy::new(|| strip_to_len(REC2020_COEFFS_BYTES, COEFFS_LEN)); -pub static SRGB_TABLE: Lazy = - Lazy::new(|| RGBToSpectrumTableData::new(SRGB_SCALE.to_vec(), SRGB_COEFFS.to_vec())); -pub static DCI_P3_TABLE: Lazy = +pub static SRGB_TABLE: Lazy = + Lazy::new(|| RGBToSpectrumTableData::new(SRGB_SCALE, SRGB_COEFFS)); +pub static DCI_P3_TABLE: Lazy = Lazy::new(|| RGBToSpectrumTableData::new(DCI_P3_SCALE.to_vec(), DCI_P3_COEFFS.to_vec())); -pub static REC2020_TABLE: Lazy = +pub static REC2020_TABLE: Lazy = Lazy::new(|| RGBToSpectrumTableData::new(REC2020_SCALE.to_vec(), REC2020_COEFFS.to_vec())); -pub static ACES_TABLE: Lazy = +pub static ACES_TABLE: Lazy = Lazy::new(|| RGBToSpectrumTableData::new(ACES_SCALE.to_vec(), ACES_COEFFS.to_vec())); diff --git a/src/lib.rs b/src/lib.rs index c84be56..4b4627b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,5 +16,4 @@ pub mod utils; #[cfg(feature = "cuda")] pub mod gpu; - -pub use utils::{Arena, Device, DeviceRepr, FileLoc, ParameterDictionary}; +pub use utils::{Arena, FileLoc, ParameterDictionary}; diff --git a/src/lights/diffuse.rs b/src/lights/diffuse.rs index 1edd25b..ab6069e 100644 --- a/src/lights/diffuse.rs +++ b/src/lights/diffuse.rs @@ -5,7 +5,7 @@ use crate::core::light::lookup_spectrum; use crate::core::spectrum::spectrum_to_photometric; use crate::core::texture::FloatTexture; use crate::utils::resolve_filename; -use crate::{Arena, DeviceRepr, FileLoc, ParameterDictionary}; +use crate::{Arena, FileLoc, ParameterDictionary}; use anyhow::{anyhow, Result}; use shared::core::geometry::Point2i; use shared::core::light::{Light, LightBase, LightType}; diff --git a/src/lights/goniometric.rs b/src/lights/goniometric.rs index cec54f3..2ba7633 100644 --- a/src/lights/goniometric.rs +++ b/src/lights/goniometric.rs @@ -6,7 +6,7 @@ use crate::core::spectrum::spectrum_to_photometric; use crate::core::texture::FloatTexture; use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::resolve_filename; -use crate::{Arena, FileLoc, ParameterDictionary, DeviceRepr}; +use crate::{Arena, FileLoc, ParameterDictionary}; use anyhow::{Result, anyhow}; use shared::core::geometry::Point2i; use shared::core::light::{Light, LightBase, LightType}; diff --git a/src/lights/infinite.rs b/src/lights/infinite.rs index 020e6c8..370d05f 100644 --- a/src/lights/infinite.rs +++ b/src/lights/infinite.rs @@ -4,7 +4,7 @@ use crate::core::spectrum::spectrum_to_photometric; use crate::spectra::get_spectra_context; use crate::utils::resolve_filename; use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D}; -use crate::{Arena, DeviceRepr, FileLoc, ParameterDictionary}; +use crate::{Arena, FileLoc, ParameterDictionary}; use anyhow::{anyhow, Result}; use rayon::prelude::*; use shared::core::camera::CameraTransform; diff --git a/src/lights/projection.rs b/src/lights/projection.rs index 650d781..00fe486 100644 --- a/src/lights/projection.rs +++ b/src/lights/projection.rs @@ -3,7 +3,7 @@ use crate::core::spectrum::spectrum_to_photometric; use crate::core::texture::FloatTexture; use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::resolve_filename; -use crate::{Arena, DeviceRepr, FileLoc, ParameterDictionary}; +use crate::{Arena, FileLoc, ParameterDictionary}; use anyhow::{Result, anyhow}; use shared::Float; use shared::core::geometry::{ diff --git a/src/materials/coated.rs b/src/materials/coated.rs index c21511c..2db4367 100644 --- a/src/materials/coated.rs +++ b/src/materials/coated.rs @@ -4,7 +4,7 @@ use crate::core::texture::SpectrumTexture; use crate::globals::get_options; use crate::spectra::data::get_named_spectrum; use crate::utils::TextureParameterDictionary; -use crate::{Arena, DeviceRepr, FileLoc}; +use crate::{Arena, FileLoc}; use anyhow::{bail, Result}; use shared::core::material::Material; use shared::core::spectrum::Spectrum; diff --git a/src/materials/complex.rs b/src/materials/complex.rs index 1ed7c93..c0bc02d 100644 --- a/src/materials/complex.rs +++ b/src/materials/complex.rs @@ -2,7 +2,7 @@ use crate::core::image::Image; use crate::core::material::CreateMaterial; use crate::core::texture::SpectrumTexture; use crate::spectra::get_colorspace_device; -use crate::{Arena, DeviceRepr, FileLoc}; +use crate::{Arena, FileLoc}; use crate::utils::TextureParameterDictionary; use shared::bxdfs::HairBxDF; use shared::core::material::Material; diff --git a/src/shapes/mesh.rs b/src/shapes/mesh.rs index edb4d85..25d7e67 100644 --- a/src/shapes/mesh.rs +++ b/src/shapes/mesh.rs @@ -1,5 +1,5 @@ use crate::utils::sampling::PiecewiseConstant2D; -use crate::{Arena, Device, DeviceRepr}; +use crate::Arena; use anyhow::{bail, Context, Result as AnyResult}; use ply_rs::parser::Parser; use ply_rs::ply::{DefaultElement, Property}; diff --git a/src/spectra/colorspace.rs b/src/spectra/colorspace.rs index 1a1f62b..109e732 100644 --- a/src/spectra/colorspace.rs +++ b/src/spectra/colorspace.rs @@ -1,41 +1,39 @@ use crate::spectra::get_spectra_context; - -use super::DenselySampledSpectrumBuffer; -use shared::core::color::{RGB, RGBToSpectrumTable, XYZ}; +use shared::core::color::{RGBToSpectrumTable, RGB, XYZ}; use shared::core::geometry::Point2f; use shared::core::spectrum::Spectrum; -use shared::spectra::RGBColorSpace; +use shared::spectra::{DenselySampledSpectrum, RGBColorSpace}; use shared::utils::math::SquareMatrix; -use shared::utils::ptr::Ptr; +use shared::Ptr; use std::sync::Arc; -#[derive(Clone, Debug)] -pub struct RGBColorSpaceData { - illuminant: Arc, - pub view: RGBColorSpace, -} - -impl std::ops::Deref for RGBColorSpaceData { - type Target = RGBColorSpace; - fn deref(&self) -> &Self::Target { - &self.view - } -} - -impl RGBColorSpaceData { - pub fn new( +pub trait CreateRGBColorSpace { + fn new( r: Point2f, g: Point2f, b: Point2f, - illuminant: Arc, + illuminant: Arc, + rgb_to_spectrum_table: Ptr, + ) -> Self; +} + +impl CreateRGBColorSpace for RGBColorSpace { + fn new( + r: Point2f, + g: Point2f, + b: Point2f, + illuminant: Arc, rgb_to_spectrum_table: Ptr, ) -> Self { let stdspec = get_spectra_context(); - let w_xyz: XYZ = Spectrum::Dense(illuminant.device()).to_xyz(&stdspec); + let illum_spectrum = Spectrum::Dense(illuminant.as_ref().clone()); + let w_xyz: XYZ = illum_spectrum.to_xyz(&stdspec); let w = w_xyz.xy(); + let r_xyz = XYZ::from_xyy(r, Some(1.0)); let g_xyz = XYZ::from_xyy(g, Some(1.0)); let b_xyz = XYZ::from_xyy(b, Some(1.0)); + let rgb_values = [ [r_xyz.x(), g_xyz.x(), b_xyz.x()], [r_xyz.y(), g_xyz.y(), b_xyz.y()], @@ -44,23 +42,17 @@ impl RGBColorSpaceData { let rgb = SquareMatrix::new(rgb_values); let c: RGB = rgb.inverse().unwrap() * w_xyz; let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]); - let rgb_from_xyz = xyz_from_rgb - .inverse() - .expect("XYZ from RGB matrix is singular"); - let view = RGBColorSpace { + let rgb_from_xyz = xyz_from_rgb.inverse().expect("singular"); + + RGBColorSpace { r, g, b, w, - illuminant: illuminant.device(), + illuminant: Ptr::from(illuminant.as_ref()), + rgb_to_spectrum_table, xyz_from_rgb, rgb_from_xyz, - rgb_to_spectrum_table, - }; - - Self { - illuminant: illuminant.into(), - view, } } } diff --git a/src/spectra/data.rs b/src/spectra/data.rs index 6c54f63..7e64341 100644 --- a/src/spectra/data.rs +++ b/src/spectra/data.rs @@ -1,11 +1,11 @@ -use crate::spectra::{DenselySampledSpectrumBuffer, piecewise::PiecewiseLinearSpectrumBuffer}; use shared::Float; use shared::core::spectrum::Spectrum; use shared::spectra::cie::*; +use shared::spectra::{PiecewiseLinearSpectrum, DenselySampledSpectrum}; use std::collections::HashMap; use std::sync::LazyLock; -pub fn create_cie_buffer(data: &[Float]) -> DenselySampledSpectrumBuffer { +pub fn create_cie(data: &[Float]) -> DenselySampledSpectrum { let (start_lambda, step) = match data.len() { 471 => (360.0, 1.0), 95 => (300.0, 5.0), @@ -16,9 +16,9 @@ pub fn create_cie_buffer(data: &[Float]) -> DenselySampledSpectrumBuffer { .map(|i| start_lambda + i as Float * step) .collect(); - let buffer = PiecewiseLinearSpectrumBuffer::new(lambdas, data.to_vec()); - let spec = Spectrum::Piecewise(buffer.device); - DenselySampledSpectrumBuffer::from_spectrum(&spec) + let buffer = PiecewiseLinearSpectrum::new(lambdas, data.to_vec()); + let spec = Spectrum::Piecewise(buffer); + DenselySampledSpectrum::from_spectrum(&spec) } pub static NAMED_SPECTRA: LazyLock> = LazyLock::new(|| { @@ -26,8 +26,8 @@ pub static NAMED_SPECTRA: LazyLock> = LazyLock::new(|| macro_rules! add { ($name:expr, $data:expr, $norm:expr) => { - let buffer = PiecewiseLinearSpectrumBuffer::from_interleaved($data, $norm); - let spectrum = Spectrum::Piecewise(*buffer); + let buffer = PiecewiseLinearSpectrum::from_interleaved($data, $norm); + let spectrum = Spectrum::Piecewise(buffer); m.insert($name.to_string(), spectrum); }; } diff --git a/src/spectra/dense.rs b/src/spectra/dense.rs deleted file mode 100644 index cfd54c4..0000000 --- a/src/spectra/dense.rs +++ /dev/null @@ -1,117 +0,0 @@ -use shared::Float; -use shared::core::spectrum::{Spectrum, SpectrumTrait}; -use shared::spectra::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, N_CIES}; -use shared::spectra::{ - BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, PiecewiseLinearSpectrum, -}; -use shared::utils::math::square; -use std::hash::{Hash, Hasher}; - -#[derive(Clone, Debug, PartialEq)] -pub struct DenselySampledSpectrumBuffer { - pub lambda_min: i32, - pub lambda_max: i32, - pub values: Vec, -} - -impl Eq for DenselySampledSpectrumBuffer {} - -impl Hash for DenselySampledSpectrumBuffer { - fn hash(&self, state: &mut H) { - self.lambda_min.hash(state); - self.lambda_max.hash(state); - for &val in &self.values { - val.to_bits().hash(state); - } - } -} - -impl DenselySampledSpectrumBuffer { - pub fn new(lambda_min: i32, lambda_max: i32, values: Vec) -> Self { - Self { - lambda_min, - lambda_max, - values, - } - } - - pub fn new_zero(lambda_min: i32, lambda_max: i32) -> Self { - let n_values = (lambda_max - lambda_min + 1).max(0) as usize; - let values = vec![0.0; n_values]; - Self::new(lambda_min, lambda_max, values) - } - - pub fn from_spectrum(spec: &Spectrum) -> Self { - let lambda_min = LAMBDA_MIN; - let lambda_max = LAMBDA_MAX; - - let mut values = Vec::with_capacity((lambda_max - lambda_min + 1) as usize); - - for lambda in lambda_min..=lambda_max { - values.push(spec.evaluate(lambda as Float)); - } - - Self::new(lambda_min, lambda_max, values) - } - - pub fn from_function(f: F, lambda_min: i32, lambda_max: i32) -> Self - where - F: Fn(Float) -> Float, - { - let mut values = Vec::with_capacity((lambda_max - lambda_min + 1) as usize); - - for lambda in lambda_min..=lambda_max { - values.push(f(lambda as Float)); - } - - Self::new(lambda_min, lambda_max, values) - } - - pub fn generate_cie_d(temperature: Float) -> Self { - let cct = temperature * 1.4388 / 1.4380; - - if cct < 4000.0 { - return Self::from_function( - |lambda| BlackbodySpectrum::new(cct).evaluate(lambda), - LAMBDA_MIN, - LAMBDA_MAX, - ); - } - - let x = if cct < 7000. { - -4.607 * 1e9 / cct.powi(3) + 2.9678 * 1e6 / square(cct) + 0.09911 * 1e3 / cct + 0.244063 - } else { - -2.0064 * 1e9 / cct.powi(3) + 1.9018 * 1e6 / square(cct) + 0.24748 * 1e3 / cct + 0.23704 - }; - let y = -3. * x + 2.87 * x - 0.275; - let m = 0.0241 + 0.2562 * x - 0.7341 * y; - let m1 = (-1.3515 - 1.7703 * x + 5.9114 * y) / m; - let m2 = (0.0300 - 31.4424 * x + 30.0717 * y) / m; - - let coarse_values: Vec = (0..N_CIES) - .map(|i| (CIE_S0[i] + CIE_S1[i] * m1 + CIE_S2[i] * m2) * 0.01) - .collect(); - - let temp_pls = PiecewiseLinearSpectrum { - lambdas: CIE_S_LAMBDA.as_ptr().into(), - values: coarse_values.as_ptr().into(), - count: N_CIES as u32, - }; - - Self::from_function(|lambda| temp_pls.evaluate(lambda), LAMBDA_MIN, LAMBDA_MAX) - } - - pub fn device(&self) -> DenselySampledSpectrum { - DenselySampledSpectrum { - lambda_min: self.lambda_min, - lambda_max: self.lambda_max, - values: self.values.as_ptr().into(), - } - } - - pub fn scale(&mut self, s: Float) { - for v in &mut self.values { - *v *= s; - } - } -} diff --git a/src/spectra/mod.rs b/src/spectra/mod.rs index ddb7f71..1067cc7 100644 --- a/src/spectra/mod.rs +++ b/src/spectra/mod.rs @@ -1,13 +1,11 @@ use crate::globals::{ACES_TABLE, DCI_P3_TABLE, REC2020_TABLE, SRGB_TABLE}; -use crate::spectra::colorspace::RGBColorSpaceData; -use anyhow::{Result, anyhow}; +use crate::spectra::colorspace::CreateRGBColorSpace; +use anyhow::{anyhow, Result}; use shared::core::geometry::Point2f; -use shared::core::spectrum::Spectrum; -use shared::core::spectrum::StandardSpectra; -use shared::spectra::RGBColorSpace; -use shared::spectra::DeviceStandardColorSpaces; +use shared::core::spectrum::{Spectrum, StandardSpectra}; use shared::spectra::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z}; -use shared::utils::Ptr; +use shared::spectra::{DenselySampledSpectrum, DeviceStandardColorSpaces, RGBColorSpace}; +use shared::Ptr; use std::sync::Arc; use std::sync::LazyLock; @@ -16,65 +14,61 @@ pub mod data; pub mod dense; pub mod piecewise; -pub use dense::DenselySampledSpectrumBuffer; +pub static CIE_X_DATA: LazyLock = + LazyLock::new(|| data::create_cie(&CIE_X)); +pub static CIE_Y_DATA: LazyLock = + LazyLock::new(|| data::create_cie(&CIE_Y)); +pub static CIE_Z_DATA: LazyLock = + LazyLock::new(|| data::create_cie(&CIE_Z)); +pub static CIE_D65_DATA: LazyLock = + LazyLock::new(|| data::create_cie(&CIE_D65)); -pub static CIE_X_DATA: LazyLock = - LazyLock::new(|| data::create_cie_buffer(&CIE_X)); -pub static CIE_Y_DATA: LazyLock = - LazyLock::new(|| data::create_cie_buffer(&CIE_Y)); -pub static CIE_Z_DATA: LazyLock = - LazyLock::new(|| data::create_cie_buffer(&CIE_Z)); -pub static CIE_D65_DATA: LazyLock = - LazyLock::new(|| data::create_cie_buffer(&CIE_D65)); - -fn get_d65_illuminant_buffer() -> Arc { +fn get_d65_illuminant_buffer() -> Arc { Arc::new(CIE_D65_DATA.clone()) } pub fn cie_x() -> Spectrum { - Spectrum::Dense(CIE_X_DATA.device()) + Spectrum::Dense(CIE_X_DATA) } pub fn cie_y() -> Spectrum { - Spectrum::Dense(CIE_Y_DATA.device()) + Spectrum::Dense(CIE_Y_DATA) } pub fn cie_z() -> Spectrum { - Spectrum::Dense(CIE_Z_DATA.device()) + Spectrum::Dense(CIE_Z_DATA) } pub fn cie_d65() -> Spectrum { - Spectrum::Dense(CIE_D65_DATA.device()) + Spectrum::Dense(CIE_D65_DATA) } pub fn get_spectra_context() -> StandardSpectra { StandardSpectra { - x: CIE_X_DATA.device(), - y: CIE_Y_DATA.device(), - z: CIE_Z_DATA.device(), - d65: CIE_D65_DATA.device(), + x: CIE_X_DATA, + y: CIE_Y_DATA, + z: CIE_Z_DATA, + d65: CIE_D65_DATA, } } -pub static SRGB: LazyLock> = LazyLock::new(|| { +pub static SRGB: LazyLock> = LazyLock::new(|| { let illum = get_d65_illuminant_buffer(); let r = Point2f::new(0.64, 0.33); let g = Point2f::new(0.3, 0.6); let b = Point2f::new(0.15, 0.06); - let table_ptr = Ptr::from(&SRGB_TABLE.view); + let table_ptr = Ptr::from(&*SRGB_TABLE); - Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr)) + Arc::new(RGBColorSpace::new(r, g, b, illum, table_ptr)) }); -pub static DCI_P3: LazyLock> = LazyLock::new(|| { +pub static DCI_P3: LazyLock> = LazyLock::new(|| { let illum = get_d65_illuminant_buffer(); let r = Point2f::new(0.680, 0.320); let g = Point2f::new(0.265, 0.690); let b = Point2f::new(0.150, 0.060); - - let table_ptr = Ptr::from(&DCI_P3_TABLE.view); - - Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr)) + let table_ptr = Ptr::from(&*DCI_P3_TABLE); + Arc::new(RGBColorSpace::new(r, g, b, illum, table_ptr)) }); pub static REC2020: LazyLock> = LazyLock::new(|| { @@ -82,9 +76,8 @@ pub static REC2020: LazyLock> = LazyLock::new(|| { let r = Point2f::new(0.708, 0.292); let g = Point2f::new(0.170, 0.797); let b = Point2f::new(0.131, 0.046); - - let table_ptr = Ptr::from(&REC2020_TABLE.view); - Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr)) + let table_ptr = Ptr::from(&*REC2020_TABLE); + Arc::new(RGBColorSpace::new(r, g, b, illum, table_ptr)) }); pub static ACES: LazyLock> = LazyLock::new(|| { @@ -92,9 +85,8 @@ pub static ACES: LazyLock> = LazyLock::new(|| { let r = Point2f::new(0.7347, 0.2653); let g = Point2f::new(0.0000, 1.0000); let b = Point2f::new(0.0001, -0.0770); - let table_ptr = Ptr::from(&ACES_TABLE.view); - Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr)) + Arc::new(RGBColorSpace::new(r, g, b, illum, table_ptr)) }); #[derive(Debug, Clone)] @@ -106,7 +98,7 @@ pub struct StandardColorSpaces { } impl StandardColorSpaces { - pub fn get_named(&self, name: &str) -> Result> { + pub fn get_named(&self, name: &str) -> Result> { match name.to_lowercase().as_str() { "srgb" => Ok(self.srgb.clone()), "dci-p3" => Ok(self.dci_p3.clone()), @@ -128,10 +120,10 @@ pub fn get_colorspace_context() -> StandardColorSpaces { pub fn get_colorspace_device() -> DeviceStandardColorSpaces { DeviceStandardColorSpaces { - srgb: Ptr::from(&SRGB.view), - dci_p3: Ptr::from(&DCI_P3.view), - rec2020: Ptr::from(&REC2020.view), - aces2065_1: Ptr::from(&ACES.view), + srgb: Ptr::from(&*SRGB), + dci_p3: Ptr::from(&*DCI_P3), + rec2020: Ptr::from(&*REC2020), + aces2065_1: Ptr::from(&*ACES), } } diff --git a/src/spectra/piecewise.rs b/src/spectra/piecewise.rs index e58cb34..4805320 100644 --- a/src/spectra/piecewise.rs +++ b/src/spectra/piecewise.rs @@ -1,79 +1,21 @@ use crate::utils::read_float_file; -use shared::Float; use shared::spectra::PiecewiseLinearSpectrum; -use std::cmp::Ordering; -use std::ops::Deref; -pub struct PiecewiseLinearSpectrumBuffer { - pub device: PiecewiseLinearSpectrum, - _lambdas: Vec, - _values: Vec, +pub trait ReadFromFile: Sized { + fn read(filepath: &str) -> Option; } -impl Deref for PiecewiseLinearSpectrumBuffer { - type Target = PiecewiseLinearSpectrum; - fn deref(&self) -> &Self::Target { - &self.device - } -} - -impl PiecewiseLinearSpectrumBuffer { - pub fn new(lambdas: Vec, values: Vec) -> Self { - assert_eq!(lambdas.len(), values.len()); - let view = PiecewiseLinearSpectrum { - lambdas: lambdas.as_ptr().into(), - values: values.as_ptr().into(), - count: lambdas.len() as u32, - }; - Self { - device: view, - _lambdas: lambdas, - _values: values, - } - } - - pub fn from_interleaved(data: &[Float], _normalize: bool) -> Self { - if data.len() % 2 != 0 { - panic!("Interleaved data must have an even number of elements"); - } - - let mut temp: Vec<(Float, Float)> = - data.chunks(2).map(|chunk| (chunk[0], chunk[1])).collect(); - - temp.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); - - let (lambdas, values): (Vec, Vec) = temp.into_iter().unzip(); - - // (Normalization logic usually goes here) - - Self::new(lambdas, values) - } - - pub fn read(filepath: &str) -> Option { +impl ReadFromFile for PiecewiseLinearSpectrum { + fn read(filepath: &str) -> Option { let vals = read_float_file(filepath).ok()?; - - if vals.is_empty() || vals.len() % 2 == 0 { + if vals.is_empty() || vals.len() % 2 != 0 { return None; } - - let count = vals.len() / 2; - let mut lambdas = Vec::with_capacity(count); - let mut values = Vec::with_capacity(count); - - for (_, pair) in vals.chunks(2).enumerate() { - let curr_lambda = pair[0]; - let curr_val = pair[1]; - - if let Some(&prev_lambda) = lambdas.last() { - if curr_lambda <= prev_lambda { - return None; - } + for pair in vals.chunks(2).collect::>().windows(2) { + if pair[1][0] <= pair[0][0] { + return None; } - - lambdas.push(curr_lambda); - values.push(curr_val); } - - Some(Self::new(lambdas, values)) + Some(Self::from_interleaved(&vals, false)) } } diff --git a/src/textures/mix.rs b/src/textures/mix.rs index 93ab749..01a8749 100644 --- a/src/textures/mix.rs +++ b/src/textures/mix.rs @@ -2,7 +2,7 @@ use crate::core::texture::{ CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTexture, SpectrumTextureTrait, }; use crate::utils::{FileLoc, TextureParameterDictionary}; -use crate::{Arena, Device, DeviceRepr}; +use crate::Arena; use anyhow::Result; use shared::core::geometry::{Vector3f, VectorLike}; use shared::core::texture::{SpectrumType, TextureEvalContext}; @@ -15,14 +15,10 @@ use shared::utils::Transform; use shared::Float; use std::sync::Arc; -#[derive(Clone, Debug, Device)] -#[device(name = "GPUFloatMixTexture")] +#[derive(Clone, Debug)] pub struct FloatMixTexture { - #[device(upload)] pub tex1: Arc, - #[device(upload)] pub tex2: Arc, - #[device(upload)] pub amount: Arc, } @@ -64,12 +60,9 @@ impl FloatTextureTrait for FloatMixTexture { } } -#[derive(Clone, Debug, Device)] -#[device(name = "GPUFloatDirectionMixTexture")] +#[derive(Clone, Debug)] pub struct FloatDirectionMixTexture { - #[device(upload)] pub tex1: Arc, - #[device(upload)] pub tex2: Arc, pub dir: Vector3f, } @@ -100,14 +93,10 @@ impl FloatTextureTrait for FloatDirectionMixTexture { } } -#[derive(Clone, Debug, Device)] -#[device(name = "GPUSpectrumMixTexture")] +#[derive(Clone, Debug)] pub struct SpectrumMixTexture { - #[device(upload)] pub tex1: Arc, - #[device(upload)] pub tex2: Arc, - #[device(upload)] pub amount: Arc, } @@ -128,12 +117,9 @@ impl SpectrumTextureTrait for SpectrumMixTexture { } } -#[derive(Clone, Debug, Device)] -#[device(name = "GPUSpectrumDirectionMixTexture")] +#[derive(Clone, Debug)] pub struct SpectrumDirectionMixTexture { - #[device(upload)] pub tex1: Arc, - #[device(upload)] pub tex2: Arc, pub dir: Vector3f, } diff --git a/src/textures/scaled.rs b/src/textures/scaled.rs index 19c909c..ba43763 100644 --- a/src/textures/scaled.rs +++ b/src/textures/scaled.rs @@ -1,7 +1,7 @@ use crate::core::texture::{CreateSpectrumTexture, FloatTexture, SpectrumTexture}; use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait}; use crate::utils::{FileLoc, TextureParameterDictionary}; -use crate::{Arena, Device, DeviceRepr}; +use crate::Arena; use anyhow::Result; use shared::core::texture::{SpectrumType, TextureEvalContext}; use shared::spectra::{SampledSpectrum, SampledWavelengths}; @@ -10,12 +10,9 @@ use shared::utils::Transform; use shared::Float; use std::sync::Arc; -#[derive(Clone, Debug, Device)] -#[device(name = "GPUFloatScaledTexture")] +#[derive(Clone, Debug)] pub struct FloatScaledTexture { - #[device(upload)] pub tex: Arc, - #[device(upload)] pub scale: Arc, } @@ -65,12 +62,9 @@ impl FloatTextureTrait for FloatScaledTexture { } } -#[derive(Clone, Debug, Device)] -#[device(name = "GPUSpectrumScaledTexture")] +#[derive(Clone, Debug)] pub struct SpectrumScaledTexture { - #[device(upload)] pub tex: Arc, - #[device(upload)] pub scale: Arc, } diff --git a/src/utils/containers.rs b/src/utils/containers.rs index 5a767e4..763f50d 100644 --- a/src/utils/containers.rs +++ b/src/utils/containers.rs @@ -1,7 +1,5 @@ -use crate::{Arena, DeviceRepr}; use parking_lot::Mutex; use shared::core::geometry::{Bounds2i, Point2i}; -use shared::utils::containers::DeviceArray2D; use std::collections::HashSet; use std::hash::Hash; use std::sync::Arc; diff --git a/src/utils/math.rs b/src/utils/math.rs index 500f919..eb3f2b5 100644 --- a/src/utils/math.rs +++ b/src/utils/math.rs @@ -1,6 +1,6 @@ use half::f16; use shared::utils::hash::hash_buffer; -use shared::utils::math::{permutation_element, DeviceDigitPermutation, PRIMES}; +use shared::utils::math::{permutation_element, DigitPermutation, PRIMES}; use shared::Float; #[inline(always)] @@ -27,51 +27,8 @@ pub fn f16_to_f32(bits: u16) -> f32 { } } -pub struct DigitPermutation { - pub permutations: Vec, - pub device: DeviceDigitPermutation, -} -impl DigitPermutation { - pub fn new(base: i32, seed: u64) -> Self { - assert!(base < 65536); - let mut n_digits: u32 = 0; - let inv_base = 1. / base as Float; - let mut inv_base_m = 1.; - - while 1.0 - ((base as Float - 1.0) * inv_base_m) < 1.0 { - n_digits += 1; - inv_base_m *= inv_base; - } - - let mut permutations = vec![0u16; n_digits as usize * base as usize]; - - for digit_index in 0..n_digits { - let hash_input = [base as u64, digit_index as u64, seed]; - let dseed = hash_buffer(&hash_input, 0); - - for digit_value in 0..base { - let index = (digit_index as i32 * base + digit_value) as usize; - - permutations[index] = - permutation_element(digit_value as u32, base as u32, dseed as u32) as u16; - } - } - - let device = DeviceDigitPermutation { - base, - n_digits, - permutations: permutations.as_ptr().into(), - }; - - Self { - device, - permutations, - } - } -} - -pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec, Vec) { +pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec, Vec) { let temp_data: Vec> = PRIMES .iter() .map(|&base| DigitPermutation::new(base as i32, seed).permutations) @@ -93,7 +50,7 @@ pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec, Vec; diff --git a/src/utils/parameters.rs b/src/utils/parameters.rs index f81ddaa..db4cd77 100644 --- a/src/utils/parameters.rs +++ b/src/utils/parameters.rs @@ -1,7 +1,7 @@ use crate::core::spectrum::SPECTRUM_FILE_CACHE; +use crate::spectra::piecewise::ReadFromFile; use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::spectra::data::get_named_spectrum; -use crate::spectra::piecewise::PiecewiseLinearSpectrumBuffer; use crate::utils::FileLoc; use anyhow::{bail, Result}; use shared::core::color::RGB; @@ -685,7 +685,7 @@ fn read_spectrum_from_file(filename: &str) -> Result { } } - let pls = PiecewiseLinearSpectrumBuffer::read(&fn_key) + let pls = PiecewiseLinearSpectrum::read(&fn_key) .ok_or_else(|| format!("unable to read or parse spectrum file '{}'", fn_key))?; let spectrum = Spectrum::Piecewise(*pls); diff --git a/src/utils/sampling.rs b/src/utils/sampling.rs deleted file mode 100644 index bfe026f..0000000 --- a/src/utils/sampling.rs +++ /dev/null @@ -1,443 +0,0 @@ -use crate::core::image::Image; -use crate::utils::containers::Array2D; -use crate::{Arena, DeviceRepr}; -use shared::core::geometry::{Bounds2f, Point2i, Vector2f, Vector2i}; -use shared::utils::sampling::{ - AliasTable, Bin, PiecewiseConstant1D, PiecewiseConstant2D, - SummedAreaTable, WindowedPiecewiseConstant2D, PiecewiseLinear2D, -}; -use shared::utils::{gpu_array_from_fn, Ptr}; -use shared::Float; -use std::sync::Arc; - -#[derive(Debug, Clone)] -pub struct HostPiecewiseConstant1D { - func: Vec, - cdf: Vec, - pub min: Float, - pub max: Float, -} - -impl HostPiecewiseConstant1D { - pub fn n(&self) -> usize { self.func.len() } - pub fn func(&self) -> &[Float] { &self.func } - pub fn cdf(&self) -> &[Float] { &self.cdf } - - pub fn integral(&self) -> Float { - let n = self.func.len(); - let delta = (self.max - self.min) / n as Float; - self.func.iter().sum::() * delta - } - - pub fn sample_host(&self, u: Float) -> (Float, Float, usize) { - let offset = self.find_interval_host(u); - let cdf_offset = self.cdf[offset]; - let cdf_next = self.cdf[offset + 1]; - let du = if cdf_next - cdf_offset > 0.0 { - (u - cdf_offset) / (cdf_next - cdf_offset) - } else { - 0.0 - }; - let n = self.func.len(); - let delta = (self.max - self.min) / n as Float; - let x = self.min + (offset as Float + du) * delta; - let func_integral = self.integral(); - let pdf = if func_integral > 0.0 { - self.func[offset] / func_integral - } else { - 0.0 - }; - (x, pdf, offset) - } - - fn find_interval_host(&self, u: Float) -> usize { - let n = self.func.len(); - let mut size = n; - let mut first = 0usize; - while size > 0 { - let half = size >> 1; - let middle = first + half; - if self.cdf[middle] <= u { - first = middle + 1; - size -= half + 1; - } else { - size = half; - } - } - first.saturating_sub(1).min(n - 1) - } -} - -#[derive(Debug, Clone)] -pub struct PiecewiseConstant2D { - pub conditionals: Vec, - pub marginal: PiecewiseConstant1D, - pub n_u: usize, - pub n_v: usize, -} - -impl PiecewiseConstant2D { - pub fn new(data: &Array2D) -> Self { - Self::new_with_bounds(data, Bounds2f::unit()) - } - - pub fn new_with_bounds(data: &Array2D, domain: Bounds2f) -> Self { - Self::from_slice(data.as_slice(), data.x_size(), data.y_size(), domain) - } - - pub fn from_slice(data: &[Float], n_u: usize, n_v: usize, domain: Bounds2f) -> Self { - assert_eq!(data.len(), n_u * n_v); - - let mut conditionals = Vec::with_capacity(n_v); - let mut marginal_func = Vec::with_capacity(n_v); - - for v in 0..n_v { - let row = data[v * n_u..(v + 1) * n_u].to_vec(); - let conditional = - PiecewiseConstant1D::new_with_bounds(row, domain.p_min.x(), domain.p_max.x()); - marginal_func.push(conditional.integral()); - conditionals.push(conditional); - } - - let marginal = - PiecewiseConstant1D::new_with_bounds(marginal_func, domain.p_min.y(), domain.p_max.y()); - - Self { conditionals, marginal, n_u, n_v } - } - - pub fn from_image(image: &Image) -> Self { - let res = image.resolution(); - let n_u = res.x() as usize; - let n_v = res.y() as usize; - - let mut data = Vec::with_capacity(n_u * n_v); - for v in 0..n_v { - for u in 0..n_u { - data.push( - image.get_channels(Point2i::new(u as i32, v as i32)).average(), - ); - } - } - - Self::from_slice(&data, n_u, n_v, Bounds2f::unit()) - } - - pub fn integral(&self) -> Float { - self.marginal.integral() - } -} - -impl DeviceRepr for PiecewiseConstant2D { - type Target = DevicePiecewiseConstant2D; - - fn upload_value(&self, arena: &Arena) -> DevicePiecewiseConstant2D { - let uploaded: Vec = self - .conditionals - .iter() - .map(|c| c.upload_value(arena)) - .collect(); - let (conditionals_ptr, _) = arena.alloc_slice(&uploaded); - - DevicePiecewiseConstant2D { - conditionals: conditionals_ptr, - marginal: self.marginal.upload_value(arena), - n_u: self.n_u as u32, - n_v: self.n_v as u32, - } - } -} - -#[derive(Debug, Clone)] -pub struct AliasTableHost { - bins: Vec, -} - -impl AliasTableHost { - pub fn new(weights: &[Float]) -> Self { - let n = weights.len(); - if n == 0 { - return Self { bins: Vec::new() }; - } - - let sum: f64 = weights.iter().map(|&w| w as f64).sum(); - assert!(sum > 0.0, "Sum of weights must be positive"); - - let mut bins = Vec::with_capacity(n); - for &w in weights { - bins.push(Bin { - p: (w as f64 / sum) as Float, - q: 0.0, - alias: 0, - }); - } - - struct Outcome { p_hat: f64, index: usize } - - let mut under = Vec::with_capacity(n); - let mut over = Vec::with_capacity(n); - - for (i, bin) in bins.iter().enumerate() { - let p_hat = (bin.p as f64) * (n as f64); - if p_hat < 1.0 { - under.push(Outcome { p_hat, index: i }); - } else { - over.push(Outcome { p_hat, index: i }); - } - } - - while !under.is_empty() && !over.is_empty() { - let un = under.pop().unwrap(); - let ov = over.pop().unwrap(); - bins[un.index].q = un.p_hat as Float; - bins[un.index].alias = ov.index as u32; - let p_excess = un.p_hat + ov.p_hat - 1.0; - if p_excess < 1.0 { - under.push(Outcome { p_hat: p_excess, index: ov.index }); - } else { - over.push(Outcome { p_hat: p_excess, index: ov.index }); - } - } - - while let Some(ov) = over.pop() { - bins[ov.index].q = 1.0; - bins[ov.index].alias = ov.index as u32; - } - while let Some(un) = under.pop() { - bins[un.index].q = 1.0; - bins[un.index].alias = un.index as u32; - } - - Self { bins } - } - - pub fn size(&self) -> usize { self.bins.len() } - pub fn is_empty(&self) -> bool { self.bins.is_empty() } -} - -impl DeviceRepr for AliasTableHost { - type Target = AliasTable; - - fn upload_value(&self, arena: &Arena) -> AliasTable { - if self.bins.is_empty() { - return AliasTable { bins: Ptr::null(), size: 0 }; - } - let (bins_ptr, _) = arena.alloc_slice(&self.bins); - AliasTable { bins: bins_ptr, size: self.bins.len() as u32 } - } -} - -#[derive(Clone, Debug)] -pub struct SummedAreaTable { - sum: Array2D, -} - -impl SummedAreaTable { - pub fn new(values: &Array2D) -> Self { - let width = values.x_size() as i32; - let height = values.y_size() as i32; - - let mut sum = Array2D::::new_dims(width, height); - sum[(0, 0)] = values[(0, 0)] as f64; - - for x in 1..width { - sum[(x, 0)] = values[(x, 0)] as f64 + sum[(x - 1, 0)]; - } - for y in 1..height { - sum[(0, y)] = values[(0, y)] as f64 + sum[(0, y - 1)]; - } - for y in 1..height { - for x in 1..width { - sum[(x, y)] = values[(x, y)] as f64 - + sum[(x - 1, y)] - + sum[(x, y - 1)] - - sum[(x - 1, y - 1)]; - } - } - - Self { sum } - } -} - -impl DeviceRepr for SummedAreaTable { - type Target = DeviceSummedAreaTable; - - fn upload_value(&self, arena: &Arena) -> DeviceSummedAreaTable { - DeviceSummedAreaTable { - sum: self.sum.upload_value(arena), - } - } -} - -#[derive(Clone, Debug)] -pub struct WindowedPiecewiseConstant2D { - sat: SummedAreaTable, - func: Array2D, -} - -impl WindowedPiecewiseConstant2D { - pub fn new(func: Array2D) -> Self { - let sat = SummedAreaTable::new(&func); - Self { sat, func } - } -} - -impl DeviceRepr for WindowedPiecewiseConstant2D { - type Target = DeviceWindowedPiecewiseConstant2D; - - fn upload_value(&self, arena: &Arena) -> DeviceWindowedPiecewiseConstant2D { - DeviceWindowedPiecewiseConstant2D { - sat: self.sat.upload_value(arena), - func: self.func.upload_value(arena), - } - } -} - -struct PiecewiseLinear2DStorage { - data: Vec, - marginal_cdf: Vec, - conditional_cdf: Vec, - param_values: [Vec; N], -} - -pub struct PiecewiseLinear2DHost { - size: Vector2i, - inv_patch_size: Vector2f, - param_size: [u32; N], - param_strides: [u32; N], - storage: Arc>, -} - -impl PiecewiseLinear2DHost { - pub fn new( - data: &[Float], - x_size: i32, - y_size: i32, - param_res: [usize; N], - param_values: [&[Float]; N], - normalize: bool, - build_cdf: bool, - ) -> Self { - if build_cdf && !normalize { - panic!("PiecewiseLinear2D: build_cdf implies normalize=true"); - } - - let size = Vector2i::new(x_size, y_size); - let inv_patch_size = - Vector2f::new(1.0 / (x_size - 1) as Float, 1.0 / (y_size - 1) as Float); - - let mut param_size = [0u32; N]; - let mut param_strides = [0u32; N]; - let owned_param_values: [Vec; N] = gpu_array_from_fn(|i| param_values[i].to_vec()); - - let mut slices: u32 = 1; - for i in (0..N).rev() { - assert!(param_res[i] >= 1, "Parameter resolution must be >= 1"); - param_size[i] = param_res[i] as u32; - param_strides[i] = if param_res[i] > 1 { slices } else { 0 }; - slices *= param_size[i]; - } - - let n_values = (x_size * y_size) as usize; - let mut new_data = vec![0.0; slices as usize * n_values]; - let mut marginal_cdf = if build_cdf { - vec![0.0; slices as usize * y_size as usize] - } else { - Vec::new() - }; - let mut conditional_cdf = if build_cdf { - vec![0.0; slices as usize * n_values] - } else { - Vec::new() - }; - - let mut data_offset = 0; - for slice in 0..slices as usize { - let slice_offset = slice * n_values; - let current_data = &data[data_offset..data_offset + n_values]; - let mut sum = 0.0_f64; - - if normalize { - for y in 0..(y_size - 1) { - for x in 0..(x_size - 1) { - let i = (y * x_size + x) as usize; - let v00 = current_data[i] as f64; - let v10 = current_data[i + 1] as f64; - let v01 = current_data[i + x_size as usize] as f64; - let v11 = current_data[i + 1 + x_size as usize] as f64; - sum += 0.25 * (v00 + v10 + v01 + v11); - } - } - } - - let normalization = if normalize && sum > 0.0 { - 1.0 / sum as Float - } else { - 1.0 - }; - for k in 0..n_values { - new_data[slice_offset + k] = current_data[k] * normalization; - } - - if build_cdf { - let marginal_slice_offset = slice * y_size as usize; - for y in 0..y_size as usize { - let mut cdf_sum = 0.0; - let i_base = y * x_size as usize; - conditional_cdf[slice_offset + i_base] = 0.0; - for x in 0..(x_size - 1) as usize { - let i = i_base + x; - cdf_sum += - 0.5 * (new_data[slice_offset + i] + new_data[slice_offset + i + 1]); - conditional_cdf[slice_offset + i + 1] = cdf_sum; - } - } - marginal_cdf[marginal_slice_offset] = 0.0; - let mut marginal_sum = 0.0; - for y in 0..(y_size - 1) as usize { - let cdf1 = conditional_cdf[slice_offset + (y + 1) * x_size as usize - 1]; - let cdf2 = conditional_cdf[slice_offset + (y + 2) * x_size as usize - 1]; - marginal_sum += 0.5 * (cdf1 + cdf2); - marginal_cdf[marginal_slice_offset + y + 1] = marginal_sum; - } - } - data_offset += n_values; - } - - let storage = Arc::new(PiecewiseLinear2DStorage { - data: new_data, - marginal_cdf, - conditional_cdf, - param_values: owned_param_values, - }); - - Self { size, inv_patch_size, param_size, param_strides, storage } - } -} - -impl DeviceRepr for PiecewiseLinear2DHost { - type Target = PiecewiseLinear2D; - - fn upload_value(&self, arena: &Arena) -> PiecewiseLinear2D { - let s = &self.storage; - - let (data_ptr, _) = arena.alloc_slice(&s.data); - let (marginal_ptr, _) = arena.alloc_slice(&s.marginal_cdf); - let (conditional_ptr, _) = arena.alloc_slice(&s.conditional_cdf); - - let param_ptrs: [Ptr; N] = std::array::from_fn(|i| { - let (ptr, _) = arena.alloc_slice(&s.param_values[i]); - ptr - }); - - PiecewiseLinear2D { - size: self.size, - inv_patch_size: self.inv_patch_size, - param_size: self.param_size, - param_strides: self.param_strides, - param_values: param_ptrs, - data: data_ptr, - marginal_cdf: marginal_ptr, - conditional_cdf: conditional_ptr, - } - } -} diff --git a/src/utils/upload.rs b/src/utils/upload.rs deleted file mode 100644 index 40b3555..0000000 --- a/src/utils/upload.rs +++ /dev/null @@ -1,167 +0,0 @@ -use crate::core::image::Image; -use crate::spectra::DenselySampledSpectrumBuffer; -use crate::Arena; -use shared::core::color::RGBToSpectrumTable; -use shared::core::image::DeviceImage; -use shared::core::light::Light; -use shared::core::material::Material; -use shared::core::shape::Shape; -use shared::core::spectrum::Spectrum; -use shared::spectra::{DenselySampledSpectrum, DeviceStandardColorSpaces, RGBColorSpace}; -use shared::Ptr; -use std::slice::from_raw_parts; - -pub trait DeviceRepr { - /// The `#[repr(C)] Copy` device-side struct. - type Target: Copy; - - /// Upload into the arena and return the device struct by value. - /// Use this when embedding the result inline in another device struct. - fn upload_value(&self, arena: &Arena) -> Self::Target; - - /// Upload into the arena and return a Ptr to the device struct. - /// This is the common entry point — allocates the Target in the arena. - fn upload(&self, arena: &Arena) -> Ptr { - let value = self.upload_value(arena); - arena.alloc(value) - } -} - -impl DeviceRepr for Option { - type Target = T::Target; - - fn upload_value(&self, arena: &Arena) -> Self::Target { - match self { - Some(val) => val.upload_value(arena), - None => panic!("Cannot upload_value on None — use upload() which returns Ptr::null()"), - } - } - - fn upload(&self, arena: &Arena) -> Ptr { - match self { - Some(val) => val.upload(arena), - None => Ptr::null(), - } - } -} - -impl DeviceRepr for std::sync::Arc { - type Target = T::Target; - - fn upload_value(&self, arena: &Arena) -> Self::Target { - (**self).upload_value(arena) - } - - fn upload(&self, arena: &Arena) -> Ptr { - (**self).upload(arena) - } -} - -impl DeviceRepr for Box { - type Target = T::Target; - - fn upload_value(&self, arena: &Arena) -> Self::Target { - (**self).upload_value(arena) - } - - fn upload(&self, arena: &Arena) -> Ptr { - (**self).upload(arena) - } -} - -impl DeviceRepr for Shape { - type Target = Shape; - fn upload_value(&self, _arena: &Arena) -> Shape { - self.clone() - } -} - -impl DeviceRepr for Light { - type Target = Light; - fn upload_value(&self, _arena: &Arena) -> Light { - self.clone() - } -} - -impl DeviceRepr for Spectrum { - type Target = Spectrum; - fn upload_value(&self, _arena: &Arena) -> Spectrum { - self.clone() - } -} - -impl DeviceRepr for Material { - type Target = Material; - fn upload_value(&self, _arena: &Arena) -> Material { - self.clone() - } -} - -impl DeviceRepr for Image { - type Target = DeviceImage; - fn upload_value(&self, _arena: &Arena) -> DeviceImage { - *self.device() - } -} - -impl DeviceRepr for DenselySampledSpectrumBuffer { - type Target = DenselySampledSpectrum; - fn upload_value(&self, _arena: &Arena) -> DenselySampledSpectrum { - self.device() - } -} - -impl DeviceRepr for RGBToSpectrumTable { - type Target = RGBToSpectrumTable; - - fn upload_value(&self, arena: &Arena) -> RGBToSpectrumTable { - let n_nodes = self.n_nodes as usize; - - // Safety: these Ptrs point into static or previously-uploaded data; - // we're copying the contents into the arena for a new lifetime. - let z_slice = unsafe { from_raw_parts(self.z_nodes.as_raw(), n_nodes) }; - let (z_ptr, _) = arena.alloc_slice(z_slice); - - let n_coeffs = 3 * n_nodes.pow(3); - let coeffs_slice = unsafe { from_raw_parts(self.coeffs.as_raw(), n_coeffs) }; - let (c_ptr, _) = arena.alloc_slice(coeffs_slice); - - RGBToSpectrumTable { - z_nodes: z_ptr, - coeffs: c_ptr, - n_nodes: self.n_nodes, - } - } -} - -impl DeviceRepr for RGBColorSpace { - type Target = RGBColorSpace; - - fn upload_value(&self, arena: &Arena) -> RGBColorSpace { - let table_ptr = self.rgb_to_spectrum_table.upload(arena); - - RGBColorSpace { - r: self.r, - g: self.g, - b: self.b, - w: self.w, - illuminant: self.illuminant.clone(), - rgb_to_spectrum_table: table_ptr, - xyz_from_rgb: self.xyz_from_rgb, - rgb_from_xyz: self.rgb_from_xyz, - } - } -} - -impl DeviceRepr for DeviceStandardColorSpaces { - type Target = DeviceStandardColorSpaces; - - fn upload_value(&self, arena: &Arena) -> DeviceStandardColorSpaces { - DeviceStandardColorSpaces { - srgb: self.srgb.upload(arena), - dci_p3: self.dci_p3.upload(arena), - rec2020: self.rec2020.upload(arena), - aces2065_1: self.aces2065_1.upload(arena), - } - } -}