Continuing refactoring
This commit is contained in:
parent
a4c751bbcd
commit
31106696bd
39 changed files with 672 additions and 1289 deletions
|
|
@ -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<Float> for Coeffs {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RGBToSpectrumTable {
|
||||
pub z_nodes: Ptr<Float>,
|
||||
pub coeffs: Ptr<Coeffs>,
|
||||
pub z_nodes: GVec<Float>,
|
||||
pub coeffs: GVec<Coeffs>,
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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<const N: usize>(&self, p: Point2i) -> [Float; N] {
|
||||
self.get_channels_array_with_wrap(p, WrapMode::Clamp.into())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<DeviceImage>,
|
||||
pub image: Ptr<Image>,
|
||||
pub distrib: Ptr<DevicePiecewiseConstant2D>,
|
||||
pub image_color_space: Ptr<RGBColorSpace>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<DeviceImage>,
|
||||
pub normal_map: Ptr<Image>,
|
||||
pub displacement: Ptr<GPUFloatTexture>,
|
||||
pub reflectance: Ptr<GPUSpectrumTexture>,
|
||||
}
|
||||
|
|
@ -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<DeviceImage>,
|
||||
pub image: Ptr<Image>,
|
||||
pub displacement: Ptr<GPUFloatTexture>,
|
||||
pub reflectance: Ptr<GPUFloatTexture>,
|
||||
pub transmittance: Ptr<GPUFloatTexture>,
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<RGBToSpectrumTable>,
|
||||
pub xyz_from_rgb: SquareMatrix3f,
|
||||
pub rgb_from_xyz: SquareMatrix3f,
|
||||
pub illuminant: Ptr<DenselySampledSpectrum>,
|
||||
pub rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
|
||||
}
|
||||
|
||||
unsafe impl Send for RGBColorSpace {}
|
||||
|
|
|
|||
|
|
@ -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<Float>,
|
||||
pub values: GVec<Float>,
|
||||
}
|
||||
|
||||
unsafe impl Send for DenselySampledSpectrum {}
|
||||
unsafe impl Sync for DenselySampledSpectrum {}
|
||||
|
||||
impl DenselySampledSpectrum {
|
||||
pub fn new(lambda_min: i32, lambda_max: i32, values: GVec<Float>) -> 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: 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<H: Hasher>(&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<Float>,
|
||||
pub values: Ptr<Float>,
|
||||
pub lambdas: GVec<Float>,
|
||||
pub values: GVec<Float>,
|
||||
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<Float>, values: GVec<Float>) -> 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.;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<NonNull<[u8]>, 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::<u8>(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<u8>, 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::*;
|
||||
|
|
|
|||
|
|
@ -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<u16>,
|
||||
pub permutations: GVec<u16>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceDigitPermutation {
|
||||
#[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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<f64>,
|
||||
}
|
||||
|
||||
impl DeviceSummedAreaTable {
|
||||
//
|
||||
impl SummedAreaTable {
|
||||
pub fn new(values: &Array2D<Float>) -> Self {
|
||||
let width = values.x_size() as i32;
|
||||
let height = values.y_size() as i32;
|
||||
|
||||
let mut sum = Array2D::<f64>::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<Float>,
|
||||
}
|
||||
|
||||
impl DeviceWindowedPiecewiseConstant2D {
|
||||
impl WindowedPiecewiseConstant2D {
|
||||
pub fn new(func: Array2D<Float>) -> 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<Bin>,
|
||||
pub size: u32,
|
||||
pub bins: GVec<Bin>,
|
||||
}
|
||||
|
||||
unsafe impl Send for AliasTable {}
|
||||
unsafe impl Sync for AliasTable {}
|
||||
|
||||
impl AliasTable {
|
||||
#[inline(always)]
|
||||
fn bin(&self, idx: u32) -> Ptr<Bin> {
|
||||
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<const N: usize> {
|
|||
}
|
||||
|
||||
impl<const N: usize> PiecewiseLinear2D<N> {
|
||||
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<Float>; 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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<Float>,
|
||||
_coeffs: Vec<Float>,
|
||||
|
||||
pub view: RGBToSpectrumTable,
|
||||
pub trait CreateRGBToSpectrumTable {
|
||||
fn from_data(z_nodes: &[Float], coeffs: &[Float]) -> Self;
|
||||
fn load(base_dir: &Path, name: &str) -> Result<Self> 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<Float>, coeffs: Vec<Float>) -> 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<Self> {
|
||||
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<Self> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
116
src/core/film.rs
116
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<RGBColorSpace>,
|
||||
exposure_time: Float,
|
||||
loc: &FileLoc,
|
||||
) -> Result<Self>;
|
||||
|
||||
fn new(
|
||||
r: &Spectrum,
|
||||
g: &Spectrum,
|
||||
b: &Spectrum,
|
||||
output_colorspace: Arc<RGBColorSpace>,
|
||||
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<RGBColorSpace>,
|
||||
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<Arc<Spectrum>> = 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::<RGB>(
|
||||
let rgb = PixelSensor::project_reflectance::<RGB>(
|
||||
&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::<XYZ>(
|
||||
let xyz = PixelSensor::project_reflectance::<XYZ>(
|
||||
&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::<Float, 3>::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<Self>
|
||||
where
|
||||
|
|
@ -247,7 +233,7 @@ impl CreateFilmBase for FilmBase {
|
|||
fn create(
|
||||
params: &ParameterDictionary,
|
||||
filter: Filter,
|
||||
sensor: Option<&DevicePixelSensor>,
|
||||
sensor: Option<&PixelSensor>,
|
||||
loc: &FileLoc,
|
||||
) -> Result<Self>
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -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<DenselySampledSpectrumBuffer> {
|
||||
pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
|
||||
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<Medium>,
|
||||
|
|
|
|||
|
|
@ -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<Float>,
|
||||
) -> 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);
|
||||
|
|
|
|||
|
|
@ -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<InternCache<DenselySampledSpectrumBuffer>> =
|
||||
pub static SPECTRUM_CACHE: LazyLock<InternCache<DenselySampledSpectrum>> =
|
||||
LazyLock::new(InternCache::new);
|
||||
|
||||
pub static SPECTRUM_FILE_CACHE: LazyLock<Mutex<HashMap<String, Spectrum>>> =
|
||||
LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
pub fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrumBuffer> {
|
||||
pub fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrum> {
|
||||
&SPECTRUM_CACHE
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Float> = 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<RGBToSpectrumTableData> =
|
||||
Lazy::new(|| RGBToSpectrumTableData::new(SRGB_SCALE.to_vec(), SRGB_COEFFS.to_vec()));
|
||||
pub static DCI_P3_TABLE: Lazy<RGBToSpectrumTableData> =
|
||||
pub static SRGB_TABLE: Lazy<RGBToSpectrumTable> =
|
||||
Lazy::new(|| RGBToSpectrumTableData::new(SRGB_SCALE, SRGB_COEFFS));
|
||||
pub static DCI_P3_TABLE: Lazy<RGBToSpectrumTable> =
|
||||
Lazy::new(|| RGBToSpectrumTableData::new(DCI_P3_SCALE.to_vec(), DCI_P3_COEFFS.to_vec()));
|
||||
pub static REC2020_TABLE: Lazy<RGBToSpectrumTableData> =
|
||||
pub static REC2020_TABLE: Lazy<RGBToSpectrumTable> =
|
||||
Lazy::new(|| RGBToSpectrumTableData::new(REC2020_SCALE.to_vec(), REC2020_COEFFS.to_vec()));
|
||||
pub static ACES_TABLE: Lazy<RGBToSpectrumTableData> =
|
||||
pub static ACES_TABLE: Lazy<RGBToSpectrumTable> =
|
||||
Lazy::new(|| RGBToSpectrumTableData::new(ACES_SCALE.to_vec(), ACES_COEFFS.to_vec()));
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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::{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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<DenselySampledSpectrumBuffer>,
|
||||
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<DenselySampledSpectrumBuffer>,
|
||||
illuminant: Arc<DenselySampledSpectrum>,
|
||||
rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
|
||||
) -> Self;
|
||||
}
|
||||
|
||||
impl CreateRGBColorSpace for RGBColorSpace {
|
||||
fn new(
|
||||
r: Point2f,
|
||||
g: Point2f,
|
||||
b: Point2f,
|
||||
illuminant: Arc<DenselySampledSpectrum>,
|
||||
rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
|
||||
) -> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<HashMap<String, Spectrum>> = LazyLock::new(|| {
|
||||
|
|
@ -26,8 +26,8 @@ pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = 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);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Float>,
|
||||
}
|
||||
|
||||
impl Eq for DenselySampledSpectrumBuffer {}
|
||||
|
||||
impl Hash for DenselySampledSpectrumBuffer {
|
||||
fn hash<H: Hasher>(&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<Float>) -> 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: 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<Float> = (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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DenselySampledSpectrum> =
|
||||
LazyLock::new(|| data::create_cie(&CIE_X));
|
||||
pub static CIE_Y_DATA: LazyLock<DenselySampledSpectrum> =
|
||||
LazyLock::new(|| data::create_cie(&CIE_Y));
|
||||
pub static CIE_Z_DATA: LazyLock<DenselySampledSpectrum> =
|
||||
LazyLock::new(|| data::create_cie(&CIE_Z));
|
||||
pub static CIE_D65_DATA: LazyLock<DenselySampledSpectrum> =
|
||||
LazyLock::new(|| data::create_cie(&CIE_D65));
|
||||
|
||||
pub static CIE_X_DATA: LazyLock<DenselySampledSpectrumBuffer> =
|
||||
LazyLock::new(|| data::create_cie_buffer(&CIE_X));
|
||||
pub static CIE_Y_DATA: LazyLock<DenselySampledSpectrumBuffer> =
|
||||
LazyLock::new(|| data::create_cie_buffer(&CIE_Y));
|
||||
pub static CIE_Z_DATA: LazyLock<DenselySampledSpectrumBuffer> =
|
||||
LazyLock::new(|| data::create_cie_buffer(&CIE_Z));
|
||||
pub static CIE_D65_DATA: LazyLock<DenselySampledSpectrumBuffer> =
|
||||
LazyLock::new(|| data::create_cie_buffer(&CIE_D65));
|
||||
|
||||
fn get_d65_illuminant_buffer() -> Arc<DenselySampledSpectrumBuffer> {
|
||||
fn get_d65_illuminant_buffer() -> Arc<DenselySampledSpectrum> {
|
||||
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<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
|
||||
pub static SRGB: LazyLock<Arc<RGBColorSpace>> = 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<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
|
||||
pub static DCI_P3: LazyLock<Arc<RGBColorSpace>> = 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<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
|
||||
|
|
@ -82,9 +76,8 @@ pub static REC2020: LazyLock<Arc<RGBColorSpaceData>> = 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<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
|
||||
|
|
@ -92,9 +85,8 @@ pub static ACES: LazyLock<Arc<RGBColorSpaceData>> = 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<Arc<RGBColorSpaceData>> {
|
||||
pub fn get_named(&self, name: &str) -> Result<Arc<RGBColorSpace>> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Float>,
|
||||
_values: Vec<Float>,
|
||||
pub trait ReadFromFile: Sized {
|
||||
fn read(filepath: &str) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl Deref for PiecewiseLinearSpectrumBuffer {
|
||||
type Target = PiecewiseLinearSpectrum;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl PiecewiseLinearSpectrumBuffer {
|
||||
pub fn new(lambdas: Vec<Float>, values: Vec<Float>) -> 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<Float>, Vec<Float>) = temp.into_iter().unzip();
|
||||
|
||||
// (Normalization logic usually goes here)
|
||||
|
||||
Self::new(lambdas, values)
|
||||
}
|
||||
|
||||
pub fn read(filepath: &str) -> Option<Self> {
|
||||
impl ReadFromFile for PiecewiseLinearSpectrum {
|
||||
fn read(filepath: &str) -> Option<Self> {
|
||||
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 {
|
||||
for pair in vals.chunks(2).collect::<Vec<_>>().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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<FloatTexture>,
|
||||
#[device(upload)]
|
||||
pub tex2: Arc<FloatTexture>,
|
||||
#[device(upload)]
|
||||
pub amount: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
|
|
@ -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<FloatTexture>,
|
||||
#[device(upload)]
|
||||
pub tex2: Arc<FloatTexture>,
|
||||
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<SpectrumTexture>,
|
||||
#[device(upload)]
|
||||
pub tex2: Arc<SpectrumTexture>,
|
||||
#[device(upload)]
|
||||
pub amount: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
|
|
@ -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<SpectrumTexture>,
|
||||
#[device(upload)]
|
||||
pub tex2: Arc<SpectrumTexture>,
|
||||
pub dir: Vector3f,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<FloatTexture>,
|
||||
#[device(upload)]
|
||||
pub scale: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
|
|
@ -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<SpectrumTexture>,
|
||||
#[device(upload)]
|
||||
pub scale: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<u16>,
|
||||
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<u16>, Vec<DeviceDigitPermutation>) {
|
||||
pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec<u16>, Vec<DigitPermutation>) {
|
||||
let temp_data: Vec<Vec<u16>> = PRIMES
|
||||
.iter()
|
||||
.map(|&base| DigitPermutation::new(base as i32, seed).permutations)
|
||||
|
|
@ -93,7 +50,7 @@ pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec<u16>, Vec<DeviceD
|
|||
|
||||
// let ptr_to_data = storage_base_ptr.add(current_offset);
|
||||
|
||||
views.push(DigitPermutation::new(base as i32, n_digits as u64).device);
|
||||
views.push(DigitPermutation::new(base as i32, n_digits as u64));
|
||||
|
||||
// current_offset += len;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,14 +13,12 @@ pub mod sampling;
|
|||
pub mod strings;
|
||||
pub mod upload;
|
||||
|
||||
pub use device_derive::Device;
|
||||
pub use error::FileLoc;
|
||||
pub use file::{read_float_file, resolve_filename};
|
||||
pub use parameters::{
|
||||
ParameterDictionary, ParsedParameter, ParsedParameterVector, TextureParameterDictionary,
|
||||
};
|
||||
pub use strings::*;
|
||||
pub use upload::DeviceRepr;
|
||||
|
||||
#[cfg(feature = "vulkan")]
|
||||
pub type Arena = arena::Arena<backend::vulkan::VulkanAllocator>;
|
||||
|
|
|
|||
|
|
@ -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<Spectrum, String> {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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<Float>,
|
||||
cdf: Vec<Float>,
|
||||
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::<Float>() * 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<PiecewiseConstant1D>,
|
||||
pub marginal: PiecewiseConstant1D,
|
||||
pub n_u: usize,
|
||||
pub n_v: usize,
|
||||
}
|
||||
|
||||
impl PiecewiseConstant2D {
|
||||
pub fn new(data: &Array2D<Float>) -> Self {
|
||||
Self::new_with_bounds(data, Bounds2f::unit())
|
||||
}
|
||||
|
||||
pub fn new_with_bounds(data: &Array2D<Float>, 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<DevicePiecewiseConstant1D> = 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<Bin>,
|
||||
}
|
||||
|
||||
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<f64>,
|
||||
}
|
||||
|
||||
impl SummedAreaTable {
|
||||
pub fn new(values: &Array2D<Float>) -> Self {
|
||||
let width = values.x_size() as i32;
|
||||
let height = values.y_size() as i32;
|
||||
|
||||
let mut sum = Array2D::<f64>::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<Float>,
|
||||
}
|
||||
|
||||
impl WindowedPiecewiseConstant2D {
|
||||
pub fn new(func: Array2D<Float>) -> 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<const N: usize> {
|
||||
data: Vec<Float>,
|
||||
marginal_cdf: Vec<Float>,
|
||||
conditional_cdf: Vec<Float>,
|
||||
param_values: [Vec<Float>; N],
|
||||
}
|
||||
|
||||
pub struct PiecewiseLinear2DHost<const N: usize> {
|
||||
size: Vector2i,
|
||||
inv_patch_size: Vector2f,
|
||||
param_size: [u32; N],
|
||||
param_strides: [u32; N],
|
||||
storage: Arc<PiecewiseLinear2DStorage<N>>,
|
||||
}
|
||||
|
||||
impl<const N: usize> PiecewiseLinear2DHost<N> {
|
||||
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<Float>; 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<const N: usize> DeviceRepr for PiecewiseLinear2DHost<N> {
|
||||
type Target = PiecewiseLinear2D<N>;
|
||||
|
||||
fn upload_value(&self, arena: &Arena) -> PiecewiseLinear2D<N> {
|
||||
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<Float>; 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Self::Target> {
|
||||
let value = self.upload_value(arena);
|
||||
arena.alloc(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DeviceRepr> DeviceRepr for Option<T> {
|
||||
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<Self::Target> {
|
||||
match self {
|
||||
Some(val) => val.upload(arena),
|
||||
None => Ptr::null(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DeviceRepr> DeviceRepr for std::sync::Arc<T> {
|
||||
type Target = T::Target;
|
||||
|
||||
fn upload_value(&self, arena: &Arena) -> Self::Target {
|
||||
(**self).upload_value(arena)
|
||||
}
|
||||
|
||||
fn upload(&self, arena: &Arena) -> Ptr<Self::Target> {
|
||||
(**self).upload(arena)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DeviceRepr> DeviceRepr for Box<T> {
|
||||
type Target = T::Target;
|
||||
|
||||
fn upload_value(&self, arena: &Arena) -> Self::Target {
|
||||
(**self).upload_value(arena)
|
||||
}
|
||||
|
||||
fn upload(&self, arena: &Arena) -> Ptr<Self::Target> {
|
||||
(**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),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue