Continuing refactoring

This commit is contained in:
Wito Wiala 2026-05-19 00:54:29 +01:00
parent a4c751bbcd
commit 31106696bd
39 changed files with 672 additions and 1289 deletions

View file

@ -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;

View file

@ -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())
}

View file

@ -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,
}

View file

@ -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>,
}

View file

@ -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)
}

View file

@ -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 {}

View file

@ -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.;
}

View file

@ -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::*;

View file

@ -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
}
}
}
}

View file

@ -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),

View file

@ -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;

View file

@ -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))
}
}

View file

@ -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() {

View file

@ -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>,

View file

@ -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);

View file

@ -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
}

View file

@ -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),
}

View file

@ -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()));

View file

@ -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};

View file

@ -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};

View file

@ -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};

View file

@ -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;

View file

@ -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::{

View file

@ -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;

View file

@ -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;

View file

@ -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};

View file

@ -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,
}
}
}

View file

@ -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);
};
}

View file

@ -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;
}
}
}

View file

@ -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),
}
}

View file

@ -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))
}
}

View file

@ -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,
}

View file

@ -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>,
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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>;

View file

@ -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);

View file

@ -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,
}
}
}

View file

@ -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),
}
}
}