Fixing errors in CPU code

This commit is contained in:
pingu 2026-01-22 14:18:57 +00:00
parent 1e21cc64f9
commit 93bcd465eb
75 changed files with 2786 additions and 3031 deletions

View file

@ -39,4 +39,27 @@ void ptex_filter_release(PtexFilterHandle filter) {
} }
} }
PtexTextureHandle ptex_texture_open(const char* filename, char** error_str) {
Ptex::String error;
Ptex::PtexTexture* tex = Ptex::PtexTexture::open(filename, error);
if (!tex && error_str) {
*error_str = strdup(error.c_str());
}
return tex;
}
void ptex_texture_release(PtexTextureHandle texture) {
Ptex::PtexTexture* tex = static_cast<Ptex::PtexTexture*>(texture);
if (tex) {
tex->release();
}
}
int32_t ptex_texture_num_channels(PtexTextureHandle texture) {
Ptex::PtexTexture* tex = static_cast<Ptex::PtexTexture*>(texture);
return tex ? tex->numChannels() : 0;
}
} }

View file

@ -27,8 +27,12 @@ typedef struct {
int32_t noedgeblend; int32_t noedgeblend;
} PtexFilterOptions; } PtexFilterOptions;
PtexTextureHandle ptex_texture_open(const char* filename, char** error_str);
void ptex_filter_release(PtexFilterHandle filter);
int32_t ptex_texture_num_channels(PtexTextureHandle texture);
PtexFilterHandle ptex_filter_create(PtexTextureHandle texture, const PtexFilterOptions* opts); PtexFilterHandle ptex_filter_create(PtexTextureHandle texture, const PtexFilterOptions* opts);
void ptex_filter_eval( void ptex_filter_eval(
PtexFilterHandle filter, PtexFilterHandle filter,
float* result, float* result,
@ -39,9 +43,9 @@ void ptex_filter_eval(
float dudx, float dvdx, float dudx, float dvdx,
float dudy, float dvdy float dudy, float dvdy
); );
void ptex_filter_release(PtexFilterHandle filter); void ptex_filter_release(PtexFilterHandle filter);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -51,4 +51,10 @@ extern "C" {
); );
pub fn ptex_filter_release(filter: *mut c_void); pub fn ptex_filter_release(filter: *mut c_void);
pub fn ptex_texture_open(
filename: *const std::ffi::c_char,
error_str: *mut *mut std::ffi::c_char,
) -> *mut c_void;
pub fn ptex_texture_release(texture: *mut c_void);
pub fn ptex_texture_num_channels(texture: *mut c_void) -> i32;
} }

View file

@ -12,6 +12,9 @@ pub struct PtexFilter {
} }
impl PtexFilter { impl PtexFilter {
/// Creates a new Ptex filter pointer
///
/// # Safety
pub unsafe fn new(texture_ptr: *mut c_void, opts: &PtexFilterOptions) -> Option<Self> { pub unsafe fn new(texture_ptr: *mut c_void, opts: &PtexFilterOptions) -> Option<Self> {
let handle = ffi::ptex_filter_create(texture_ptr, opts); let handle = ffi::ptex_filter_create(texture_ptr, opts);
NonNull::new(handle).map(|h| Self { NonNull::new(handle).map(|h| Self {

View file

@ -10,7 +10,7 @@ use crate::core::scattering::{
TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, fresnel_moment1, reflect, TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, fresnel_moment1, reflect,
refract, refract,
}; };
use crate::spectra::{RGBUnboundedSpectrum, SampledSpectrum, StandardColorSpaces}; use crate::spectra::{DeviceStandardColorSpaces, RGBUnboundedSpectrum, SampledSpectrum};
use crate::utils::math::{ use crate::utils::math::{
clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete, clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete,
square, trimmed_logistic, square, trimmed_logistic,
@ -34,7 +34,7 @@ pub struct HairBxDF {
pub s: Float, pub s: Float,
pub sin_2k_alpha: [Float; P_MAX], pub sin_2k_alpha: [Float; P_MAX],
pub cos_2k_alpha: [Float; P_MAX], pub cos_2k_alpha: [Float; P_MAX],
pub colorspaces: StandardColorSpaces, pub colorspaces: DeviceStandardColorSpaces,
} }
impl HairBxDF { impl HairBxDF {
@ -45,7 +45,7 @@ impl HairBxDF {
beta_m: Float, beta_m: Float,
beta_n: Float, beta_n: Float,
alpha: Float, alpha: Float,
colorspaces: StandardColorSpaces, colorspaces: DeviceStandardColorSpaces,
) -> Self { ) -> Self {
let mut sin_2k_alpha = [0.; P_MAX]; let mut sin_2k_alpha = [0.; P_MAX];
let mut cos_2k_alpha = [0.; P_MAX]; let mut cos_2k_alpha = [0.; P_MAX];
@ -141,7 +141,7 @@ impl HairBxDF {
pub fn sigma_a_from_concentration( pub fn sigma_a_from_concentration(
ce: Float, ce: Float,
cp: Float, cp: Float,
stdcs: StandardColorSpaces, stdcs: DeviceStandardColorSpaces,
) -> RGBUnboundedSpectrum { ) -> RGBUnboundedSpectrum {
let eumelanin_sigma_a = RGB::new(0.419, 0.697, 1.37); let eumelanin_sigma_a = RGB::new(0.419, 0.697, 1.37);
let pheomelanin_sigma_a = RGB::new(0.187, 0.4, 1.05); let pheomelanin_sigma_a = RGB::new(0.187, 0.4, 1.05);

View file

@ -16,8 +16,8 @@ use crate::core::scattering::{
refract, refract,
}; };
use crate::spectra::{ use crate::spectra::{
N_SPECTRUM_SAMPLES, RGBColorSpace, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, DeviceStandardColorSpaces, N_SPECTRUM_SAMPLES, RGBColorSpace, RGBUnboundedSpectrum,
StandardColorSpaces, SampledSpectrum, SampledWavelengths,
}; };
use crate::utils::hash::hash_buffer; use crate::utils::hash::hash_buffer;
use crate::utils::math::{ use crate::utils::math::{

View file

@ -7,6 +7,7 @@ use std::ops::{
use crate::Float; use crate::Float;
use crate::core::geometry::Point2f; use crate::core::geometry::Point2f;
use crate::core::spectrum::Spectrum; use crate::core::spectrum::Spectrum;
use crate::utils::Ptr;
use crate::utils::find_interval; use crate::utils::find_interval;
use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp}; use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp};
@ -1065,8 +1066,9 @@ impl Mul<Float> for Coeffs {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct RGBToSpectrumTable { pub struct RGBToSpectrumTable {
pub z_nodes: *const Float, pub z_nodes: Ptr<Float>,
pub coeffs: *const Coeffs, pub coeffs: Ptr<Coeffs>,
pub n_nodes: u32,
} }
unsafe impl Send for RGBToSpectrumTable {} unsafe impl Send for RGBToSpectrumTable {}
@ -1112,7 +1114,8 @@ impl RGBToSpectrumTable {
let x = coord_a / z; let x = coord_a / z;
let y = coord_b / z; let y = coord_b / z;
let z_nodes_slice = unsafe { core::slice::from_raw_parts(self.z_nodes, RES as usize) }; 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 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 dz = (z - z_nodes_slice[zi]) / (z_nodes_slice[zi + 1] - z_nodes_slice[zi]);
let x_float = x * (RES - 1) as Float; let x_float = x * (RES - 1) as Float;

View file

@ -1,6 +1,7 @@
use crate::Float;
use crate::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR}; use crate::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i}; use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
use crate::core::pbrt::Float; use crate::utils::Ptr;
use crate::utils::containers::Array2D; use crate::utils::containers::Array2D;
use crate::utils::math::{f16_to_f32, lerp, square}; use crate::utils::math::{f16_to_f32, lerp, square};
use core::hash; use core::hash;
@ -70,9 +71,9 @@ impl PixelFormat {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Pixels { pub enum Pixels {
U8(*const u8), U8(Ptr<u8>),
F16(*const u16), F16(Ptr<u16>),
F32(*const f32), F32(Ptr<f32>),
} }
#[repr(C)] #[repr(C)]

View file

@ -220,18 +220,18 @@ pub struct ShadingGeom {
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
pub struct SurfaceInteraction { pub struct SurfaceInteraction {
pub area_light: Ptr<Light>,
pub material: Ptr<Material>,
pub shape: Ptr<Shape>,
pub common: InteractionBase, pub common: InteractionBase,
pub shading: ShadingGeom,
pub dpdu: Vector3f, pub dpdu: Vector3f,
pub dpdv: Vector3f, pub dpdv: Vector3f,
pub dndu: Normal3f, pub dndu: Normal3f,
pub dndv: Normal3f, pub dndv: Normal3f,
pub shading: ShadingGeom,
pub face_index: u32,
pub area_light: Ptr<Light>,
pub material: Ptr<Material>,
pub shape: Ptr<Shape>,
pub dpdx: Vector3f, pub dpdx: Vector3f,
pub dpdy: Vector3f, pub dpdy: Vector3f,
pub face_index: i32,
pub dudx: Float, pub dudx: Float,
pub dvdx: Float, pub dvdx: Float,
pub dudy: Float, pub dudy: Float,
@ -609,7 +609,7 @@ impl SurfaceInteraction {
dndv: Normal3f, dndv: Normal3f,
time: Float, time: Float,
flip: bool, flip: bool,
face_index: u32, face_index: i32,
) -> Self { ) -> Self {
let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip); let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip);
si.face_index = face_index; si.face_index = face_index;

View file

@ -65,7 +65,7 @@ pub struct NormalBumpEvalContext {
pub dudy: Float, pub dudy: Float,
pub dvdx: Float, pub dvdx: Float,
pub dvdy: Float, pub dvdy: Float,
pub face_index: u32, pub face_index: i32,
} }
impl From<&SurfaceInteraction> for NormalBumpEvalContext { impl From<&SurfaceInteraction> for NormalBumpEvalContext {

View file

@ -5,7 +5,7 @@ use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4};
use crate::utils::Ptr; use crate::utils::Ptr;
use crate::utils::containers::Array2D; use crate::utils::containers::Array2D;
use crate::utils::math::{ use crate::utils::math::{
BinaryPermuteScrambler, DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler, BinaryPermuteScrambler, DeviceDigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler,
PRIME_TABLE_SIZE, Scrambler, clamp, encode_morton_2, inverse_radical_inverse, lerp, log2_int, PRIME_TABLE_SIZE, Scrambler, clamp, encode_morton_2, inverse_radical_inverse, lerp, log2_int,
owen_scrambled_radical_inverse, permutation_element, radical_inverse, round_up_pow2, owen_scrambled_radical_inverse, permutation_element, radical_inverse, round_up_pow2,
scrambled_radical_inverse, sobol_interval_to_index, sobol_sample, scrambled_radical_inverse, sobol_interval_to_index, sobol_sample,
@ -42,13 +42,13 @@ where
#[repr(C)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
pub struct IndependentSampler { pub struct IndependentSampler {
pub samples_per_pixel: u32, pub samples_per_pixel: i32,
pub seed: u64, pub seed: u64,
pub rng: Rng, pub rng: Rng,
} }
impl IndependentSampler { impl IndependentSampler {
pub fn new(samples_per_pixel: u32, seed: u64) -> Self { pub fn new(samples_per_pixel: i32, seed: u64) -> Self {
Self { Self {
samples_per_pixel, samples_per_pixel,
seed, seed,
@ -58,10 +58,10 @@ impl IndependentSampler {
} }
impl SamplerTrait for IndependentSampler { impl SamplerTrait for IndependentSampler {
fn samples_per_pixel(&self) -> u32 { fn samples_per_pixel(&self) -> i32 {
self.samples_per_pixel self.samples_per_pixel
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
let hash_input = [p.x() as u64, p.y() as u64, self.seed]; let hash_input = [p.x() as u64, p.y() as u64, self.seed];
let sequence_index = hash_buffer(&hash_input, 0); let sequence_index = hash_buffer(&hash_input, 0);
self.rng.set_sequence(sequence_index); self.rng.set_sequence(sequence_index);
@ -95,65 +95,18 @@ pub enum RandomizeStrategy {
#[repr(C)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
pub struct HaltonSampler { pub struct HaltonSampler {
samples_per_pixel: u32, pub samples_per_pixel: i32,
randomize: RandomizeStrategy, pub randomize: RandomizeStrategy,
base_scales: [u64; 2], pub base_scales: [u64; 2],
base_exponents: [u64; 2], pub base_exponents: [u64; 2],
mult_inverse: [u64; 2], pub mult_inverse: [u64; 2],
halton_index: u64, pub halton_index: u64,
dim: u32, pub dim: u32,
digit_permutations: Ptr<DigitPermutation>, pub digit_permutations: Ptr<DeviceDigitPermutation>,
} }
impl HaltonSampler { impl HaltonSampler {
// pub fn new( pub fn sample_dimension(&self, dimension: u32) -> Float {
// samples_per_pixel: u32,
// full_res: Point2i,
// randomize: RandomizeStrategy,
// seed: u64,
// ) -> Self {
// let digit_permutations = compute_radical_inverse_permutations(seed);
// let mut base_scales = [0u64; 2];
// let mut base_exponents = [0u64; 2];
// let bases = [2, 3];
// let res_coords = [full_res.x(), full_res.y()];
//
// for i in 0..2 {
// let base = bases[i] as u64;
// let mut scale = 1u64;
// let mut exp = 0u64;
//
// let limit = std::cmp::min(res_coords[i], MAX_HALTON_RESOLUTION) as u64;
//
// while scale < limit {
// scale *= base;
// exp += 1;
// }
//
// base_scales[i] = scale;
// base_exponents[i] = exp;
// }
//
// let mut mult_inverse = [0u64; 2];
//
// mult_inverse[0] =
// Self::multiplicative_inverse(base_scales[0] as i64, base_scales[0] as i64);
// mult_inverse[1] =
// Self::multiplicative_inverse(base_scales[1] as i64, base_scales[1] as i64);
//
// Self {
// samples_per_pixel,
// randomize,
// digit_permutations,
// base_scales,
// base_exponents,
// mult_inverse,
// halton_index: 0,
// dim: 0,
// }
// }
fn sample_dimension(&self, dimension: u32) -> Float {
if self.randomize == RandomizeStrategy::None { if self.randomize == RandomizeStrategy::None {
radical_inverse(dimension, self.halton_index) radical_inverse(dimension, self.halton_index)
} else if self.randomize == RandomizeStrategy::PermuteDigits { } else if self.randomize == RandomizeStrategy::PermuteDigits {
@ -168,12 +121,12 @@ impl HaltonSampler {
} }
} }
fn multiplicative_inverse(a: i64, n: i64) -> u64 { pub fn multiplicative_inverse(a: i64, n: i64) -> u64 {
let (x, _) = Self::extended_gcd(a as u64, n as u64); let (x, _) = Self::extended_gcd(a as u64, n as u64);
x.rem_euclid(n) as u64 x.rem_euclid(n) as u64
} }
fn extended_gcd(a: u64, b: u64) -> (i64, i64) { pub fn extended_gcd(a: u64, b: u64) -> (i64, i64) {
if b == 0 { if b == 0 {
return (1, 0); return (1, 0);
} }
@ -187,11 +140,11 @@ impl HaltonSampler {
} }
impl SamplerTrait for HaltonSampler { impl SamplerTrait for HaltonSampler {
fn samples_per_pixel(&self) -> u32 { fn samples_per_pixel(&self) -> i32 {
self.samples_per_pixel self.samples_per_pixel
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
self.halton_index = 0; self.halton_index = 0;
let sample_stride = self.base_scales[0] * self.base_scales[1]; let sample_stride = self.base_scales[0] * self.base_scales[1];
@ -252,20 +205,20 @@ impl SamplerTrait for HaltonSampler {
#[repr(C)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
pub struct StratifiedSampler { pub struct StratifiedSampler {
x_pixel_samples: u32, x_pixel_samples: i32,
y_pixel_samples: u32, y_pixel_samples: i32,
jitter: bool, jitter: bool,
seed: u64, seed: u64,
rng: Rng, rng: Rng,
pixel: Point2i, pixel: Point2i,
sample_index: u32, sample_index: i32,
dim: u32, dim: u32,
} }
impl StratifiedSampler { impl StratifiedSampler {
pub fn new( pub fn new(
x_pixel_samples: u32, x_pixel_samples: i32,
y_pixel_samples: u32, y_pixel_samples: i32,
seed: Option<u64>, seed: Option<u64>,
jitter: bool, jitter: bool,
) -> Self { ) -> Self {
@ -283,11 +236,11 @@ impl StratifiedSampler {
} }
impl SamplerTrait for StratifiedSampler { impl SamplerTrait for StratifiedSampler {
fn samples_per_pixel(&self) -> u32 { fn samples_per_pixel(&self) -> i32 {
self.x_pixel_samples * self.y_pixel_samples self.x_pixel_samples * self.y_pixel_samples
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
self.pixel = p; self.pixel = p;
self.sample_index = sample_index; self.sample_index = sample_index;
let hash_input = [p.x() as u64, p.y() as u64, self.seed]; let hash_input = [p.x() as u64, p.y() as u64, self.seed];
@ -360,16 +313,16 @@ impl SamplerTrait for StratifiedSampler {
#[repr(C)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
pub struct PaddedSobolSampler { pub struct PaddedSobolSampler {
samples_per_pixel: u32, samples_per_pixel: i32,
seed: u64, seed: u64,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
pixel: Point2i, pixel: Point2i,
sample_index: u32, sample_index: i32,
dim: u32, dim: u32,
} }
impl PaddedSobolSampler { impl PaddedSobolSampler {
pub fn new(samples_per_pixel: u32, randomize: RandomizeStrategy, seed: Option<u64>) -> Self { pub fn new(samples_per_pixel: i32, randomize: RandomizeStrategy, seed: Option<u64>) -> Self {
Self { Self {
samples_per_pixel, samples_per_pixel,
seed: seed.unwrap_or(0), seed: seed.unwrap_or(0),
@ -398,10 +351,10 @@ impl PaddedSobolSampler {
} }
impl SamplerTrait for PaddedSobolSampler { impl SamplerTrait for PaddedSobolSampler {
fn samples_per_pixel(&self) -> u32 { fn samples_per_pixel(&self) -> i32 {
self.samples_per_pixel self.samples_per_pixel
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
self.pixel = p; self.pixel = p;
self.sample_index = sample_index; self.sample_index = sample_index;
self.dim = dim.unwrap_or(0); self.dim = dim.unwrap_or(0);
@ -449,7 +402,7 @@ impl SamplerTrait for PaddedSobolSampler {
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct SobolSampler { pub struct SobolSampler {
samples_per_pixel: u32, samples_per_pixel: i32,
scale: i32, scale: i32,
seed: u64, seed: u64,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
@ -460,7 +413,7 @@ pub struct SobolSampler {
impl SobolSampler { impl SobolSampler {
pub fn new( pub fn new(
samples_per_pixel: u32, samples_per_pixel: i32,
full_resolution: Point2i, full_resolution: Point2i,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: Option<u64>, seed: Option<u64>,
@ -501,10 +454,10 @@ impl SobolSampler {
} }
impl SamplerTrait for SobolSampler { impl SamplerTrait for SobolSampler {
fn samples_per_pixel(&self) -> u32 { fn samples_per_pixel(&self) -> i32 {
self.samples_per_pixel self.samples_per_pixel
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
self.pixel = p; self.pixel = p;
self.dim = 2.max(dim.unwrap_or(0)); self.dim = 2.max(dim.unwrap_or(0));
self.sobol_index = self.sobol_index =
@ -557,7 +510,7 @@ impl SamplerTrait for SobolSampler {
pub struct ZSobolSampler { pub struct ZSobolSampler {
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: u64, seed: u64,
log2_samples_per_pixel: u32, log2_samples_per_pixel: i32,
n_base4_digits: u32, n_base4_digits: u32,
morton_index: u64, morton_index: u64,
dim: u32, dim: u32,
@ -565,7 +518,7 @@ pub struct ZSobolSampler {
impl ZSobolSampler { impl ZSobolSampler {
pub fn new( pub fn new(
samples_per_pixel: u32, samples_per_pixel: i32,
full_resolution: Point2i, full_resolution: Point2i,
randomize: RandomizeStrategy, randomize: RandomizeStrategy,
seed: Option<u64>, seed: Option<u64>,
@ -573,11 +526,11 @@ impl ZSobolSampler {
let log2_samples_per_pixel = log2_int(samples_per_pixel as Float) as u32; let log2_samples_per_pixel = log2_int(samples_per_pixel as Float) as u32;
let res = round_up_pow2(full_resolution.x().max(full_resolution.y())); let res = round_up_pow2(full_resolution.x().max(full_resolution.y()));
let log4_samples_per_pixel = log2_samples_per_pixel.div_ceil(2); let log4_samples_per_pixel = log2_samples_per_pixel.div_ceil(2);
let n_base4_digits = log2_int(res as Float) as u32 + log4_samples_per_pixel; let n_base4_digits = log2_int(res as Float) as u32 + log4_samples_per_pixel as u32;
Self { Self {
randomize, randomize,
seed: seed.unwrap_or(0), seed: seed.unwrap_or(0),
log2_samples_per_pixel, log2_samples_per_pixel: log2_samples_per_pixel as i32,
n_base4_digits, n_base4_digits,
morton_index: 0, morton_index: 0,
dim: 0, dim: 0,
@ -641,10 +594,10 @@ impl ZSobolSampler {
} }
impl SamplerTrait for ZSobolSampler { impl SamplerTrait for ZSobolSampler {
fn samples_per_pixel(&self) -> u32 { fn samples_per_pixel(&self) -> i32 {
todo!() todo!()
} }
fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>) { fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>) {
self.dim = dim.unwrap_or(0); self.dim = dim.unwrap_or(0);
self.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32) self.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32)
<< self.log2_samples_per_pixel) << self.log2_samples_per_pixel)
@ -710,10 +663,10 @@ impl SamplerTrait for ZSobolSampler {
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct MLTSampler; pub struct MLTSampler;
impl SamplerTrait for MLTSampler { impl SamplerTrait for MLTSampler {
fn samples_per_pixel(&self) -> u32 { fn samples_per_pixel(&self) -> i32 {
todo!() todo!()
} }
fn start_pixel_sample(&mut self, _p: Point2i, _sample_index: u32, _dim: Option<u32>) { fn start_pixel_sample(&mut self, _p: Point2i, _sample_index: i32, _dim: Option<u32>) {
todo!() todo!()
} }
fn get1d(&mut self) -> Float { fn get1d(&mut self) -> Float {
@ -729,8 +682,8 @@ impl SamplerTrait for MLTSampler {
#[enum_dispatch] #[enum_dispatch]
pub trait SamplerTrait { pub trait SamplerTrait {
fn samples_per_pixel(&self) -> u32; fn samples_per_pixel(&self) -> i32;
fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>); fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>);
fn get1d(&mut self) -> Float; fn get1d(&mut self) -> Float;
fn get2d(&mut self) -> Point2f; fn get2d(&mut self) -> Point2f;
fn get_pixel2d(&mut self) -> Point2f; fn get_pixel2d(&mut self) -> Point2f;

View file

@ -272,7 +272,7 @@ pub struct TextureEvalContext {
pub dudy: Float, pub dudy: Float,
pub dvdx: Float, pub dvdx: Float,
pub dvdy: Float, pub dvdy: Float,
pub face_index: u32, pub face_index: i32,
} }
impl TextureEvalContext { impl TextureEvalContext {
@ -287,7 +287,7 @@ impl TextureEvalContext {
dudy: Float, dudy: Float,
dvdx: Float, dvdx: Float,
dvdy: Float, dvdy: Float,
face_index: u32, face_index: i32,
) -> Self { ) -> Self {
Self { Self {
p, p,

View file

@ -48,7 +48,7 @@ impl DiffuseAreaLight {
return false; return false;
}; };
let ctx = TextureEvalContext::from(intr); let ctx = TextureEvalContext::from(intr);
let a = UniversalTextureEvaluator.evaluate_float(&*self.alpha, &ctx); let a = UniversalTextureEvaluator.evaluate_float(&self.alpha, &ctx);
if a >= 1.0 { if a >= 1.0 {
return false; return false;
} }

View file

@ -15,8 +15,8 @@ use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths
use crate::spectra::{RGBColorSpace, RGBIlluminantSpectrum}; use crate::spectra::{RGBColorSpace, RGBIlluminantSpectrum};
use crate::utils::math::{clamp, equal_area_sphere_to_square, equal_area_square_to_sphere, square}; use crate::utils::math::{clamp, equal_area_sphere_to_square, equal_area_square_to_sphere, square};
use crate::utils::sampling::{ use crate::utils::sampling::{
AliasTable, DevicePiecewiseConstant2D, WindowedPiecewiseConstant2D, sample_uniform_sphere, AliasTable, DevicePiecewiseConstant2D, DeviceWindowedPiecewiseConstant2D,
uniform_sphere_pdf, sample_uniform_sphere, uniform_sphere_pdf,
}; };
use crate::utils::{Ptr, Transform}; use crate::utils::{Ptr, Transform};
use crate::{Float, PI}; use crate::{Float, PI};
@ -132,11 +132,11 @@ impl ImageInfiniteLight {
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel_with_wrap( rgb[c] = self.image.lookup_nearest_channel_with_wrap(
uv, uv,
c as i32, c,
WrapMode::OctahedralSphere.into(), WrapMode::OctahedralSphere.into(),
); );
} }
let spec = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero()); let spec = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero());
self.scale * spec.sample(lambda) self.scale * spec.sample(lambda)
} }
} }
@ -222,11 +222,11 @@ impl LightTrait for ImageInfiniteLight {
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.get_channel_with_wrap( rgb[c] = self.image.get_channel_with_wrap(
Point2i::new(u, v), Point2i::new(u, v),
c as i32, c,
WrapMode::OctahedralSphere.into(), WrapMode::OctahedralSphere.into(),
); );
} }
sum_l += RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero()) sum_l += RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero())
.sample(&lambda); .sample(&lambda);
} }
} }
@ -255,7 +255,7 @@ pub struct PortalInfiniteLight {
pub scale: Float, pub scale: Float,
pub portal: [Point3f; 4], pub portal: [Point3f; 4],
pub portal_frame: Frame, pub portal_frame: Frame,
pub distribution: WindowedPiecewiseConstant2D, pub distribution: DeviceWindowedPiecewiseConstant2D,
pub scene_center: Point3f, pub scene_center: Point3f,
pub scene_radius: Float, pub scene_radius: Float,
} }
@ -264,9 +264,9 @@ impl PortalInfiniteLight {
pub fn image_lookup(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn image_lookup(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
rgb[c] = self.image.lookup_nearest_channel(uv, c as i32) rgb[c] = self.image.lookup_nearest_channel(uv, c)
} }
let spec = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero()); let spec = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero());
self.scale * spec.sample(lambda) self.scale * spec.sample(lambda)
} }

View file

@ -31,6 +31,7 @@ pub struct HairMaterial {
impl HairMaterial { impl HairMaterial {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
sigma_a: Ptr<GPUSpectrumTexture>, sigma_a: Ptr<GPUSpectrumTexture>,
color: Ptr<GPUSpectrumTexture>, color: Ptr<GPUSpectrumTexture>,

View file

@ -35,7 +35,7 @@ impl TriangleIntersection {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct TriangleShape { pub struct TriangleShape {
pub mesh: DeviceTriangleMesh, pub mesh: DeviceTriangleMesh,
pub tri_index: u32, pub tri_index: i32,
} }
impl TriangleShape { impl TriangleShape {
@ -111,7 +111,7 @@ impl TriangleShape {
} }
} }
pub fn new(mesh: DeviceTriangleMesh, tri_index: u32) -> Self { pub fn new(mesh: DeviceTriangleMesh, tri_index: i32) -> Self {
Self { mesh, tri_index } Self { mesh, tri_index }
} }

View file

@ -9,14 +9,14 @@ use std::cmp::{Eq, PartialEq};
#[repr(C)] #[repr(C)]
#[derive(Copy, Debug, Clone)] #[derive(Copy, Debug, Clone)]
pub struct StandardColorSpaces { pub struct DeviceStandardColorSpaces {
pub srgb: Ptr<RGBColorSpace>, pub srgb: Ptr<RGBColorSpace>,
pub dci_p3: Ptr<RGBColorSpace>, pub dci_p3: Ptr<RGBColorSpace>,
pub rec2020: Ptr<RGBColorSpace>, pub rec2020: Ptr<RGBColorSpace>,
pub aces2065_1: Ptr<RGBColorSpace>, pub aces2065_1: Ptr<RGBColorSpace>,
} }
impl StandardColorSpaces { impl DeviceStandardColorSpaces {
#[cfg(not(target_arch = "nvptx64"))] #[cfg(not(target_arch = "nvptx64"))]
pub fn get_named(&self, name: &str) -> Option<Ptr<RGBColorSpace>> { pub fn get_named(&self, name: &str) -> Option<Ptr<RGBColorSpace>> {
match name.to_lowercase().as_str() { match name.to_lowercase().as_str() {

View file

@ -6,7 +6,7 @@ pub mod simple;
use crate::core::pbrt::Float; use crate::core::pbrt::Float;
pub use colorspace::{RGBColorSpace, StandardColorSpaces}; pub use colorspace::{DeviceStandardColorSpaces, RGBColorSpace};
pub use rgb::*; pub use rgb::*;
pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN}; pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN};
pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};

View file

@ -6,10 +6,10 @@ use crate::spectra::{
RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
SampledWavelengths, SampledWavelengths,
}; };
use crate::utils::Ptr;
/* GPU heavy code, dont know if this will ever work the way Im doing things. /* GPU heavy code, dont know if this will ever work the way Im doing things.
* Leaving it here isolated, for careful handling */ * Leaving it here isolated, for careful handling */
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]
pub struct GPUSpectrumImageTexture { pub struct GPUSpectrumImageTexture {

View file

@ -14,15 +14,15 @@ pub struct GPUFloatMixTexture {
impl GPUFloatMixTexture { impl GPUFloatMixTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let amt = self.amount.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0); let amt = self.amount.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0);
let t1 = if amt != 1.0 { let t1 = if amt != 1.0 {
self.tex1.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0) self.tex1.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0)
} else { } else {
0.0 0.0
}; };
let t2 = if amt != 0.0 { let t2 = if amt != 0.0 {
self.tex2.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0) self.tex2.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0)
} else { } else {
0.0 0.0
}; };
@ -43,13 +43,13 @@ impl GPUFloatDirectionMixTexture {
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float { pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
let amt = self.dir.abs_dot(ctx.n.into()); let amt = self.dir.abs_dot(ctx.n.into());
let t1 = if amt != 1.0 { let t1 = if amt != 1.0 {
self.tex1.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0) self.tex1.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0)
} else { } else {
0.0 0.0
}; };
let t2 = if amt != 0.0 { let t2 = if amt != 0.0 {
self.tex2.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0) self.tex2.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0)
} else { } else {
0.0 0.0
}; };
@ -72,11 +72,11 @@ impl GPUSpectrumMixTexture {
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
let amt = self.amount.get().map(|t| t.evaluate(&ctx)).unwrap_or(0.0); let amt = self.amount.get().map(|t| t.evaluate(ctx)).unwrap_or(0.0);
let t1 = if amt != 1.0 { let t1 = if amt != 1.0 {
self.tex1 self.tex1
.get() .get()
.map(|t| t.evaluate(&ctx, &lambda)) .map(|t| t.evaluate(ctx, lambda))
.unwrap_or(SampledSpectrum::new(0.)) .unwrap_or(SampledSpectrum::new(0.))
} else { } else {
SampledSpectrum::new(0.) SampledSpectrum::new(0.)
@ -85,7 +85,7 @@ impl GPUSpectrumMixTexture {
let t2 = if amt != 0.0 { let t2 = if amt != 0.0 {
self.tex2 self.tex2
.get() .get()
.map(|t| t.evaluate(&ctx, &lambda)) .map(|t| t.evaluate(ctx, lambda))
.unwrap_or(SampledSpectrum::new(0.)) .unwrap_or(SampledSpectrum::new(0.))
} else { } else {
SampledSpectrum::new(0.) SampledSpectrum::new(0.)
@ -113,7 +113,7 @@ impl GPUSpectrumDirectionMixTexture {
let t1 = if amt != 1.0 { let t1 = if amt != 1.0 {
self.tex1 self.tex1
.get() .get()
.map(|t| t.evaluate(&ctx, &lambda)) .map(|t| t.evaluate(ctx, lambda))
.unwrap_or(SampledSpectrum::new(0.)) .unwrap_or(SampledSpectrum::new(0.))
} else { } else {
SampledSpectrum::new(0.) SampledSpectrum::new(0.)
@ -122,7 +122,7 @@ impl GPUSpectrumDirectionMixTexture {
let t2 = if amt != 0.0 { let t2 = if amt != 0.0 {
self.tex2 self.tex2
.get() .get()
.map(|t| t.evaluate(&ctx, &lambda)) .map(|t| t.evaluate(ctx, lambda))
.unwrap_or(SampledSpectrum::new(0.)) .unwrap_or(SampledSpectrum::new(0.))
} else { } else {
SampledSpectrum::new(0.) SampledSpectrum::new(0.)

View file

@ -1,10 +1,10 @@
use crate::Float; use crate::Float;
use crate::core::color::RGB; use crate::core::color::{ColorEncoding, RGB};
use crate::core::spectrum::{SpectrumTrait, StandardSpectra}; use crate::core::spectrum::{SpectrumTrait, StandardSpectra};
use crate::core::texture::{SpectrumType, TextureEvalContext}; use crate::core::texture::{SpectrumType, TextureEvalContext};
use crate::spectra::{ use crate::spectra::{
RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, DeviceStandardColorSpaces, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum,
SampledWavelengths, StandardColorSpaces, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
}; };
use crate::utils::Ptr; use crate::utils::Ptr;
@ -23,10 +23,10 @@ impl GPUFloatPtexTexture {
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug, Copy)]
pub struct GPUSpectrumPtexTexture { pub struct GPUSpectrumPtexTexture {
pub face_values: *const RGB, pub face_values: Ptr<RGB>,
pub n_faces: u32, pub n_faces: u32,
pub spectrum_type: SpectrumType, pub spectrum_type: SpectrumType,
pub colorspaces: StandardColorSpaces, pub colorspaces: DeviceStandardColorSpaces,
} }
impl GPUSpectrumPtexTexture { impl GPUSpectrumPtexTexture {
@ -35,7 +35,7 @@ impl GPUSpectrumPtexTexture {
ctx: &TextureEvalContext, ctx: &TextureEvalContext,
lambda: &SampledWavelengths, lambda: &SampledWavelengths,
) -> SampledSpectrum { ) -> SampledSpectrum {
let index = ctx.face_index.clamp(0, self.n_faces.saturating_sub(1)); let index = (ctx.face_index as u32).clamp(0, self.n_faces.saturating_sub(1));
let rgb = unsafe { &*self.face_values.add(index as usize) }; let rgb = unsafe { &*self.face_values.add(index as usize) };
let s_rgb = self.colorspaces.srgb; let s_rgb = self.colorspaces.srgb;

View file

@ -750,13 +750,13 @@ pub fn inverse_radical_inverse(mut inverse: u64, base: u64, n_digits: u64) -> u6
// Digit scrambling // Digit scrambling
#[repr(C)] #[repr(C)]
#[derive(Default, Debug, Copy, Clone)] #[derive(Default, Debug, Copy, Clone)]
pub struct DigitPermutation { pub struct DeviceDigitPermutation {
pub base: u32, pub base: i32,
pub n_digits: u32, pub n_digits: u32,
pub permutations: Ptr<u16>, pub permutations: Ptr<u16>,
} }
impl DigitPermutation { impl DeviceDigitPermutation {
#[inline(always)] #[inline(always)]
pub fn permute(&self, digit_index: i32, digit_value: i32) -> i32 { pub fn permute(&self, digit_index: i32, digit_value: i32) -> i32 {
let idx = (digit_index * self.base as i32 + digit_value) as usize; let idx = (digit_index * self.base as i32 + digit_value) as usize;
@ -765,7 +765,11 @@ impl DigitPermutation {
} }
} }
pub fn scrambled_radical_inverse(base_index: u32, mut a: u64, perm: &DigitPermutation) -> Float { pub fn scrambled_radical_inverse(
base_index: u32,
mut a: u64,
perm: &DeviceDigitPermutation,
) -> Float {
let base = PRIMES[base_index as usize] as u64; let base = PRIMES[base_index as usize] as u64;
let limit = (u64::MAX / base).saturating_sub(base); let limit = (u64::MAX / base).saturating_sub(base);

View file

@ -7,14 +7,14 @@ use crate::utils::sampling::DevicePiecewiseConstant2D;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct DeviceTriangleMesh { pub struct DeviceTriangleMesh {
pub n_triangles: u32,
pub n_vertices: u32,
pub vertex_indices: Ptr<u32>,
pub p: Ptr<Point3f>, pub p: Ptr<Point3f>,
pub n: Ptr<Normal3f>, pub n: Ptr<Normal3f>,
pub s: Ptr<Vector3f>, pub s: Ptr<Vector3f>,
pub uv: Ptr<Point2f>, pub uv: Ptr<Point2f>,
pub face_indices: Ptr<u32>, pub vertex_indices: Ptr<i32>,
pub face_indices: Ptr<i32>,
pub n_triangles: u32,
pub n_vertices: u32,
pub reverse_orientation: bool, pub reverse_orientation: bool,
pub transform_swaps_handedness: bool, pub transform_swaps_handedness: bool,
} }
@ -22,15 +22,15 @@ pub struct DeviceTriangleMesh {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct DeviceBilinearPatchMesh { pub struct DeviceBilinearPatchMesh {
pub n_patches: u32, pub image_distribution: Ptr<DevicePiecewiseConstant2D>,
pub n_vertices: u32,
pub vertex_indices: Ptr<u32>,
pub p: Ptr<Point3f>, pub p: Ptr<Point3f>,
pub n: Ptr<Normal3f>, pub n: Ptr<Normal3f>,
pub uv: Ptr<Point2f>, pub uv: Ptr<Point2f>,
pub vertex_indices: Ptr<i32>,
pub n_patches: u32,
pub n_vertices: u32,
pub reverse_orientation: bool, pub reverse_orientation: bool,
pub transform_swaps_handedness: bool, pub transform_swaps_handedness: bool,
pub image_distribution: Ptr<DevicePiecewiseConstant2D>,
} }
unsafe impl Send for DeviceTriangleMesh {} unsafe impl Send for DeviceTriangleMesh {}

View file

@ -772,12 +772,15 @@ impl DevicePiecewiseConstant1D {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct DevicePiecewiseConstant2D { pub struct DevicePiecewiseConstant2D {
pub conditionals: *const DevicePiecewiseConstant1D, // Array of n_v conditionals pub conditionals: Ptr<DevicePiecewiseConstant1D>, // Array of n_v conditionals
pub marginal: DevicePiecewiseConstant1D, pub marginal: DevicePiecewiseConstant1D,
pub n_u: u32, pub n_u: u32,
pub n_v: u32, pub n_v: u32,
} }
unsafe impl Send for DevicePiecewiseConstant2D {}
unsafe impl Sync for DevicePiecewiseConstant2D {}
impl DevicePiecewiseConstant2D { impl DevicePiecewiseConstant2D {
// pub fn resolution(&self) -> Point2i { // pub fn resolution(&self) -> Point2i {
// Point2i::new( // Point2i::new(
@ -792,7 +795,7 @@ impl DevicePiecewiseConstant2D {
pub fn sample(&self, u: Point2f) -> (Point2f, f32, Point2i) { pub fn sample(&self, u: Point2f) -> (Point2f, f32, Point2i) {
let (d1, pdf1, off_y) = self.marginal.sample(u.y()); let (d1, pdf1, off_y) = self.marginal.sample(u.y());
let (d0, pdf0, off_x) = (unsafe { &*self.conditionals.add(off_y) }).sample(u.x()); let (d0, pdf0, off_x) = (unsafe { self.conditionals.add(off_y) }).sample(u.x());
let pdf = pdf0 * pdf1; let pdf = pdf0 * pdf1;
let offset = Point2i::new(off_x as i32, off_y as i32); let offset = Point2i::new(off_x as i32, off_y as i32);
(Point2f::new(d0, d1), pdf, offset) (Point2f::new(d0, d1), pdf, offset)
@ -817,11 +820,11 @@ impl DevicePiecewiseConstant2D {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct SummedAreaTable { pub struct DeviceSummedAreaTable {
pub sum: Array2D<f64>, pub sum: Array2D<f64>,
} }
impl SummedAreaTable { impl DeviceSummedAreaTable {
// pub fn new(values: &Array2D<Float>) -> Self { // pub fn new(values: &Array2D<Float>) -> Self {
// let width = values.x_size(); // let width = values.x_size();
// let height = values.y_size(); // let height = values.y_size();
@ -897,17 +900,12 @@ impl SummedAreaTable {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct WindowedPiecewiseConstant2D { pub struct DeviceWindowedPiecewiseConstant2D {
sat: SummedAreaTable, pub sat: DeviceSummedAreaTable,
func: Array2D<Float>, pub func: Array2D<Float>,
} }
impl WindowedPiecewiseConstant2D { impl DeviceWindowedPiecewiseConstant2D {
// 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)> { pub fn sample(&self, u: Point2f, b: Bounds2f) -> Option<(Point2f, Float)> {
let b_int = self.sat.integral(b); let b_int = self.sat.integral(b);
if b_int == 0.0 { if b_int == 0.0 {

View file

@ -16,7 +16,7 @@ use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Debug)]
pub struct CameraBaseParameters { pub struct CameraBaseParameters {
pub camera_transform: CameraTransform, pub camera_transform: CameraTransform,
pub shutter_open: Float, pub shutter_open: Float,

View file

@ -1,4 +1,5 @@
use crate::utils::read_float_file; use crate::utils::read_float_file;
use anyhow::Error;
use shared::Float; use shared::Float;
use shared::core::color::{Coeffs, RES, RGBToSpectrumTable}; use shared::core::color::{Coeffs, RES, RGBToSpectrumTable};
use std::ops::Deref; use std::ops::Deref;
@ -25,7 +26,8 @@ impl RGBToSpectrumTableData {
let view = RGBToSpectrumTable { let view = RGBToSpectrumTable {
z_nodes: z_nodes.as_ptr(), z_nodes: z_nodes.as_ptr(),
coeffs: coeffs.as_ptr() as *const Coeffs, coeffs: coeffs.as_ptr().into(),
n_nodes: z_nodes.len(),
}; };
Self { Self {

View file

@ -1,6 +1,7 @@
use super::{Image, ImageAndMetadata, ImageMetadata}; use super::{Image, ImageAndMetadata, ImageMetadata};
use crate::core::image::{PixelStorage, WrapMode}; use crate::core::image::{PixelStorage, WrapMode};
use crate::utils::error::ImageError; use crate::utils::error::ImageError;
use anyhow::Error;
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use exr::prelude::{read_first_rgba_layer_from_file, write_rgba_file}; use exr::prelude::{read_first_rgba_layer_from_file, write_rgba_file};
use image_rs::{DynamicImage, ImageReader}; use image_rs::{DynamicImage, ImageReader};
@ -37,7 +38,7 @@ impl ImageIO for Image {
} }
} }
fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<(), ImageError> { fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<(), Error> {
let path = Path::new(filename); let path = Path::new(filename);
let ext = path.extension().and_then(|s| s.to_str()).unwrap_or(""); let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
let res = match ext.to_lowercase().as_str() { let res = match ext.to_lowercase().as_str() {

View file

@ -48,3 +48,9 @@ pub struct ImageMetadata {
pub strings: HashMap<String, String>, pub strings: HashMap<String, String>,
pub string_vectors: HashMap<String, Vec<String>>, pub string_vectors: HashMap<String, Vec<String>>,
} }
impl ImageMetadata {
pub fn get_colorspace(&self) -> Option<RGBColorSpace> {
self.colorspace
}
}

View file

@ -1,3 +1,4 @@
use anyhow::Result;
use half::f16; use half::f16;
use shared::Float; use shared::Float;
use shared::core::color::ColorEncoding; use shared::core::color::ColorEncoding;
@ -98,12 +99,21 @@ impl PixelStorage {
} }
} }
#[derive(Debug, Clone)]
pub struct Image { pub struct Image {
storage: PixelStorage, storage: PixelStorage,
channel_names: Vec<String>, channel_names: Vec<String>,
device: DeviceImage, pub device: DeviceImage,
} }
// impl Deref for Image {
// type Target = DeviceImage;
// #[inline]
// fn deref(&self) -> &Self::Target {
// &self.device
// }
// }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ImageAndMetadata { pub struct ImageAndMetadata {
pub image: Image, pub image: Image,
@ -115,7 +125,7 @@ impl Image {
fn from_storage( fn from_storage(
storage: PixelStorage, storage: PixelStorage,
resolution: Point2i, resolution: Point2i,
channel_names: Vec<String>, channel_names: &[&str],
encoding: ColorEncoding, encoding: ColorEncoding,
) -> Self { ) -> Self {
let n_channels = channel_names.len() as i32; let n_channels = channel_names.len() as i32;
@ -142,7 +152,7 @@ impl Image {
pub fn from_u8( pub fn from_u8(
data: Vec<u8>, data: Vec<u8>,
resolution: Point2i, resolution: Point2i,
channel_names: Vec<String>, channel_names: &[&str],
encoding: ColorEncoding, encoding: ColorEncoding,
) -> Self { ) -> Self {
Self::from_storage( Self::from_storage(
@ -156,7 +166,7 @@ impl Image {
pub fn from_u8( pub fn from_u8(
data: Vec<u8>, data: Vec<u8>,
resolution: Point2i, resolution: Point2i,
channel_names: Vec<String>, channel_names: &[&str],
encoding: ColorEncoding, encoding: ColorEncoding,
) -> Self { ) -> Self {
Self::from_storage( Self::from_storage(
@ -167,7 +177,7 @@ impl Image {
) )
} }
pub fn from_f16(data: Vec<half::f16>, resolution: Point2i, channel_names: Vec<String>) -> Self { pub fn from_f16(data: Vec<half::f16>, resolution: Point2i, channel_names: &[&str]) -> Self {
Self::from_storage( Self::from_storage(
PixelStorage::F16(data.into_boxed_slice()), PixelStorage::F16(data.into_boxed_slice()),
resolution, resolution,
@ -176,7 +186,7 @@ impl Image {
) )
} }
pub fn from_f32(data: Vec<f32>, resolution: Point2i, channel_names: Vec<String>) -> Self { pub fn from_f32(data: Vec<f32>, resolution: Point2i, channel_names: &[&str]) -> Self {
Self::from_storage( Self::from_storage(
PixelStorage::F32(data.into_boxed_slice()), PixelStorage::F32(data.into_boxed_slice()),
resolution, resolution,
@ -189,7 +199,7 @@ impl Image {
format: PixelFormat, format: PixelFormat,
resolution: Point2i, resolution: Point2i,
channel_names: &[&str], channel_names: &[&str],
encoding: *const ColorEncoding, encoding: Arc<ColorEncoding>,
) -> Self { ) -> Self {
let n_channels = channel_names.len(); let n_channels = channel_names.len();
let pixel_count = (resolution.x * resolution.y) as usize * n_channels; let pixel_count = (resolution.x * resolution.y) as usize * n_channels;
@ -204,6 +214,29 @@ impl Image {
Self::from_storage(storage, resolution, owned_names, encoding) Self::from_storage(storage, resolution, owned_names, encoding)
} }
pub fn new_constant(resolution: Point2i, channel_names: &[&str], values: &[f32]) -> Self {
let n_channels = channel_names.len();
if values.len() != n_channels {
panic!(
"Image::new_constant: values length ({}) must match channel count ({})",
values.len(),
n_channels
);
}
let n_pixels = (resolution.x * resolution.y) as usize;
let mut data = Vec::with_capacity(n_pixels * n_channels);
for _ in 0..n_pixels {
data.extend_from_slice(values);
}
let owned_names: Vec<String> = channel_names.iter().map(|s| s.to_string()).collect();
Self::from_f32(data, resolution, owned_names)
}
// Access // Access
pub fn device_image(&self) -> &DeviceImage { pub fn device_image(&self) -> &DeviceImage {
&self.device &self.device
@ -365,10 +398,7 @@ impl Image {
.collect() .collect()
} }
pub fn get_channel_desc( pub fn get_channel_desc(&self, requested_channels: &[&str]) -> Result<ImageChannelDesc> {
&self,
requested_channels: &[&str],
) -> Result<ImageChannelDesc, String> {
let mut offset = Vec::with_capacity(requested_channels.len()); let mut offset = Vec::with_capacity(requested_channels.len());
for &req in requested_channels.iter() { for &req in requested_channels.iter() {
@ -562,6 +592,23 @@ impl Image {
} }
return false; return false;
} }
pub fn has_any_nan_pixels(&self) -> bool {
if self.format() == PixelFormat::Float {
return false;
}
for y in 0..self.resolution().y() {
for x in 0..self.resolution().x() {
for c in 0..self.n_channels() {
if self.get_channel(Point2i::new(x, y), c).is_nan() {
return true;
}
}
}
}
return false;
}
} }
impl std::ops::Deref for Image { impl std::ops::Deref for Image {

View file

@ -1,11 +1,11 @@
use crate::Arena; use crate::Arena;
use crate::utils::{FileLoc, ParameterDictionary}; use crate::utils::{FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow};
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
use shared::core::sampler::{ use shared::core::sampler::{
HaltonSampler, IndependentSampler, PaddedSobolSampler, Sampler, SobolSampler, HaltonSampler, IndependentSampler, PaddedSobolSampler, Sampler, SobolSampler,
StratifiedSampler, ZSobolSampler, StratifiedSampler, ZSobolSampler,
}; };
use std::fmt::Error;
pub trait CreateSampler { pub trait CreateSampler {
fn create( fn create(
@ -13,7 +13,7 @@ pub trait CreateSampler {
full_res: Point2i, full_res: Point2i,
loc: &FileLoc, loc: &FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Self, Error>; ) -> Result<Self>;
} }
pub trait SamplerFactory { pub trait SamplerFactory {
@ -23,7 +23,7 @@ pub trait SamplerFactory {
full_res: Point2i, full_res: Point2i,
loc: &FileLoc, loc: &FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Self, Error>; ) -> Result<Self>;
} }
impl SamplerFactory for Sampler { impl SamplerFactory for Sampler {
@ -33,7 +33,7 @@ impl SamplerFactory for Sampler {
full_res: Point2i, full_res: Point2i,
loc: &FileLoc, loc: &FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Self, Error> { ) -> Result<Self> {
match name { match name {
"zsobol" => { "zsobol" => {
let sampler = ZSobolSampler::create(params, full_res, loc, arena)?; let sampler = ZSobolSampler::create(params, full_res, loc, arena)?;
@ -59,7 +59,7 @@ impl SamplerFactory for Sampler {
let sampler = StratifiedSampler::create(params, full_res, loc, arena)?; let sampler = StratifiedSampler::create(params, full_res, loc, arena)?;
Ok(Sampler::Stratified(sampler)) Ok(Sampler::Stratified(sampler))
} }
_ => Err(format!("Film type '{}' unknown at {}", name, loc)), _ => Err(anyhow!("Film type '{}' unknown at {}", name, loc)),
} }
} }
} }

File diff suppressed because it is too large Load diff

615
src/core/scene/builder.rs Normal file
View file

@ -0,0 +1,615 @@
use super::BasicScene;
use super::entities::*;
use crate::utils::error::FileLoc;
use crate::utils::normalize_utf8;
use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector};
use crate::utils::parser::ParserTarget;
use shared::Float;
use shared::core::camera::CameraTransform;
use shared::core::options::RenderingCoordinateSystem;
use shared::spectra::RGBColorSpace;
use shared::utils::transform::{AnimatedTransform, Transform, look_at};
use std::collections::{HashMap, HashSet};
use std::ops::{Index, IndexMut};
use std::sync::Arc;
const MAX_TRANSFORMS: usize = 2;
#[derive(Debug, Default, Clone, Copy)]
struct TransformSet {
t: [Transform; MAX_TRANSFORMS],
}
impl TransformSet {
fn is_animated(&self) -> bool {
self.t[0] != self.t[1]
}
fn inverse(&self) -> Self {
Self {
t: [self.t[0].inverse(), self.t[1].inverse()],
}
}
}
impl Index<usize> for TransformSet {
type Output = Transform;
fn index(&self, i: usize) -> &Self::Output {
&self.t[i]
}
}
impl IndexMut<usize> for TransformSet {
fn index_mut(&mut self, i: usize) -> &mut Self::Output {
&mut self.t[i]
}
}
#[derive(Default, Debug, Clone)]
struct GraphicsState {
pub current_inside_medium: String,
pub current_outside_medium: String,
pub current_material_name: String,
pub current_material_index: Option<usize>,
pub area_light_name: String,
pub area_light_params: ParsedParameterVector,
pub area_light_loc: FileLoc,
pub shape_attributes: ParsedParameterVector,
pub light_attributes: ParsedParameterVector,
pub material_attributes: ParsedParameterVector,
pub medium_attributes: ParsedParameterVector,
pub texture_attributes: ParsedParameterVector,
pub reverse_orientation: bool,
pub color_space: Option<Arc<RGBColorSpace>>,
pub ctm: TransformSet,
pub active_transform_bits: u32,
pub transform_start_time: Float,
pub transform_end_time: Float,
}
#[derive(PartialEq, Eq)]
enum BlockState {
OptionsBlock,
WorldBlock,
}
pub struct BasicSceneBuilder {
scene: Arc<BasicScene>,
current_block: BlockState,
graphics_state: GraphicsState,
pushed_graphics_states: Vec<GraphicsState>,
push_stack: Vec<(char, FileLoc)>,
render_from_world: Transform,
named_coordinate_systems: HashMap<String, TransformSet>,
active_instance_definition: Option<InstanceDefinitionSceneEntity>,
float_texture_names: HashSet<String>,
spectrum_texture_names: HashSet<String>,
named_material_names: HashSet<String>,
medium_names: HashSet<String>,
current_camera: Option<CameraSceneEntity>,
current_film: Option<SceneEntity>,
current_integrator: Option<SceneEntity>,
current_sampler: Option<SceneEntity>,
current_filter: Option<SceneEntity>,
current_accelerator: Option<SceneEntity>,
}
impl BasicSceneBuilder {
pub const START_TRANSFORM_BITS: u32 = 1 << 0;
pub const END_TRANSFORM_BITS: u32 = 1 << 1;
pub const ALL_TRANSFORM_BITS: u32 = (1 << MAX_TRANSFORMS) - 1;
pub fn new(scene: Arc<BasicScene>) -> Self {
Self {
scene,
current_block: BlockState::OptionsBlock,
graphics_state: GraphicsState {
active_transform_bits: Self::ALL_TRANSFORM_BITS,
..Default::default()
},
pushed_graphics_states: Vec::new(),
push_stack: Vec::new(),
render_from_world: Transform::identity(),
named_coordinate_systems: HashMap::new(),
active_instance_definition: None,
float_texture_names: HashSet::new(),
spectrum_texture_names: HashSet::new(),
named_material_names: HashSet::new(),
medium_names: HashSet::new(),
current_camera: Some(CameraSceneEntity {
base: SceneEntity {
name: "perspective".into(),
..Default::default()
},
camera_transform: CameraTransform::from_world(
AnimatedTransform::default(),
RenderingCoordinateSystem::World,
),
medium: String::new(),
}),
current_sampler: Some(SceneEntity {
name: "zsobol".into(),
..Default::default()
}),
current_filter: Some(SceneEntity {
name: "gaussian".into(),
..Default::default()
}),
current_integrator: Some(SceneEntity {
name: "volpath".into(),
..Default::default()
}),
current_accelerator: Some(SceneEntity {
name: "bvh".into(),
..Default::default()
}),
current_film: Some(SceneEntity {
name: "rgb".into(),
..Default::default()
}),
}
}
fn for_active_transforms<F>(&mut self, f: F)
where
F: Fn(&Transform) -> Transform,
{
let bits = self.graphics_state.active_transform_bits;
if (bits & 1) != 0 {
self.graphics_state.ctm.t[0] = f(&self.graphics_state.ctm.t[0]);
}
if (bits & 2) != 0 {
self.graphics_state.ctm.t[1] = f(&self.graphics_state.ctm.t[1]);
}
}
fn verify_world(&self, name: &str, loc: &FileLoc) {
if self.current_block != BlockState::WorldBlock {
log::error!("{}: {} not allowed outside WorldBlock", loc, name);
}
}
fn verify_options(&self, name: &str, loc: &FileLoc) {
if self.current_block != BlockState::OptionsBlock {
log::error!("{}: {} not allowed inside WorldBlock", loc, name);
}
}
}
impl ParserTarget for BasicSceneBuilder {
fn reverse_orientation(&mut self, loc: FileLoc) {
self.verify_world("ReverseOrientation", &loc);
self.graphics_state.reverse_orientation = !self.graphics_state.reverse_orientation;
}
fn color_space(&mut self, name: &str, loc: FileLoc) {
let _ = match RGBColorSpace::get_named(name) {
Ok(cs) => {
self.graphics_state.color_space = Some(cs);
}
Err(_) => {
eprintln!("Error: Color space '{}' unknown at {}", name, loc);
}
};
}
fn identity(&mut self, _loc: FileLoc) {
self.for_active_transforms(|_| Transform::identity());
}
fn translate(&mut self, dx: Float, dy: Float, dz: Float, _loc: FileLoc) {
let t = Transform::translate(Vector3f::new(dx, dy, dz));
self.for_active_transforms(|cur| cur * &t);
}
fn rotate(&mut self, angle: Float, ax: Float, ay: Float, az: Float, _loc: FileLoc) {
let t = Transform::rotate_around_axis(angle, Vector3f::new(ax, ay, az));
self.for_active_transforms(|cur| cur * &t);
}
fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) {
let t = Transform::scale(sx, sy, sz);
self.for_active_transforms(|cur| cur * &t);
}
fn look_at(
&mut self,
ex: Float,
ey: Float,
ez: Float,
lx: Float,
ly: Float,
lz: Float,
ux: Float,
uy: Float,
uz: Float,
loc: FileLoc,
) {
let result = look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz));
match result {
Ok(t) => {
self.for_active_transforms(|cur| cur * &t);
}
Err(e) => {
eprintln!("Error: {} at {}", e, loc);
}
}
}
fn concat_transform(&mut self, m: &[Float; 16], loc: FileLoc) {
let result = Transform::from_flat(m);
match result {
Ok(t) => {
self.for_active_transforms(|cur| cur * &t);
}
Err(e) => {
eprintln!("Error: {} at {}", e, loc);
}
}
}
fn transform(&mut self, m: &[Float; 16], loc: FileLoc) {
let result = Transform::from_flat(m);
match result {
Ok(t) => {
self.for_active_transforms(|_| t);
}
Err(e) => {
eprintln!("Error: {} at {}", e, loc);
}
}
}
fn coordinate_system(&mut self, name: &str, _loc: FileLoc) {
self.named_coordinate_systems
.insert(name.to_string(), self.graphics_state.ctm);
}
fn coord_sys_transform(&mut self, name: &str, loc: FileLoc) {
if let Some(saved_ctm) = self.named_coordinate_systems.get(name) {
self.graphics_state.ctm = *saved_ctm;
} else {
eprintln!(
"Warning: Couldn't find named coordinate system \"{}\" at {}",
name, loc
);
}
}
fn camera(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
self.verify_options("Camera", &loc);
let camera_from_world = self.graphics_state.ctm;
let world_from_camera = camera_from_world.inverse();
self.named_coordinate_systems
.insert("camera".to_string(), world_from_camera);
let animated_world_from_cam = AnimatedTransform::new(
&world_from_camera.t[0],
self.graphics_state.transform_start_time,
&world_from_camera.t[1],
self.graphics_state.transform_end_time,
);
let rendering_space = RenderingCoordinateSystem::CameraWorld;
let camera_transform =
CameraTransform::from_world(animated_world_from_cam, rendering_space);
self.render_from_world = camera_from_world.t[0];
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.current_camera = Some(CameraSceneEntity {
base: SceneEntity {
name: name.to_string(),
loc,
parameters,
},
camera_transform,
medium: self.graphics_state.current_outside_medium.clone(),
});
}
fn active_transform_all(&mut self, _loc: FileLoc) {
self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS;
}
fn active_transform_end_time(&mut self, _loc: FileLoc) {
self.graphics_state.active_transform_bits = Self::END_TRANSFORM_BITS;
}
fn active_transform_start_time(&mut self, _loc: FileLoc) {
self.graphics_state.active_transform_bits = Self::START_TRANSFORM_BITS;
}
fn transform_times(&mut self, start: Float, end: Float, loc: FileLoc) {
self.verify_options("TransformTimes", &loc);
self.graphics_state.transform_start_time = start;
self.graphics_state.transform_end_time = end;
}
fn option(&mut self, _name: &str, _value: &str, _loc: FileLoc) {
todo!()
}
fn pixel_filter(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.verify_options("PixelFilter", &loc);
self.current_filter = Some(SceneEntity {
name: name.to_string(),
loc,
parameters,
});
}
fn film(&mut self, type_name: &str, params: &ParsedParameterVector, loc: FileLoc) {
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.verify_options("Film", &loc);
self.current_filter = Some(SceneEntity {
name: type_name.to_string(),
loc,
parameters,
});
}
fn accelerator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.verify_options("PixelFilter", &loc);
self.current_filter = Some(SceneEntity {
name: name.to_string(),
loc,
parameters,
});
}
fn integrator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.verify_options("PixelFilter", &loc);
self.current_filter = Some(SceneEntity {
name: name.to_string(),
loc,
parameters,
});
}
fn make_named_medium(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
let curr_name = normalize_utf8(name);
self.verify_world("MakeNamedMaterial", &loc);
if !self.named_material_names.insert(curr_name.to_string()) {
eprintln!("Error: {}: named material '{}' redefined.", loc, name);
return;
}
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
let entity = SceneEntity {
name: name.to_string(),
loc,
parameters,
};
self.scene
.named_materials
.lock()
.push((curr_name.to_string(), entity));
}
fn medium_interface(&mut self, inside_name: &str, outside_name: &str, _loc: FileLoc) {
let inside = normalize_utf8(inside_name);
let outside = normalize_utf8(outside_name);
self.graphics_state.current_inside_medium = inside;
self.graphics_state.current_outside_medium = outside;
}
fn sampler(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
let parameters =
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
self.verify_options("Sampler", &loc);
self.current_sampler = Some(SceneEntity {
name: name.to_string(),
loc,
parameters,
})
}
fn world_begin(&mut self, loc: FileLoc) {
self.verify_options("WorldBegin", &loc);
self.current_block = BlockState::WorldBlock;
for i in 0..MAX_TRANSFORMS {
self.graphics_state.ctm[i] = Transform::default();
}
self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS;
self.named_coordinate_systems
.insert("world".to_string(), self.graphics_state.ctm);
let scene = Arc::clone(&self.scene);
scene.set_options(
self.current_filter
.take()
.expect("Filter not set before WorldBegin"),
self.current_film
.take()
.expect("Film not set before WorldBegin"),
self.current_camera
.take()
.expect("Camera not set before WorldBegin"),
self.current_sampler
.take()
.expect("Sampler not set before WorldBegin"),
self.current_integrator
.take()
.expect("Integrator not set before WorldBegin"),
self.current_accelerator
.take()
.expect("Accelerator not set before WorldBegin"),
);
}
fn attribute_begin(&mut self, loc: FileLoc) {
self.verify_world("AttributeBegin", &loc);
self.pushed_graphics_states
.push(self.graphics_state.clone());
self.push_stack.push(('a', loc));
}
fn attribute_end(&mut self, loc: FileLoc) {
self.verify_world("AttributeEnd", &loc);
if self.pushed_graphics_states.is_empty() {
log::error!(
"[{:?}] Unmatched AttributeEnd encountered. Ignoring it.",
loc
);
return;
}
if let Some(state) = self.pushed_graphics_states.pop() {
self.graphics_state = state;
}
if let Some((kind, start_loc)) = self.push_stack.pop() {
if kind == 'o' {
log::error!(
"[{:?}] Mismatched nesting: open ObjectBegin from {} at AttributeEnd",
loc,
start_loc
);
std::process::exit(1);
} else {
debug_assert_eq!(kind, 'a', "Expected AttributeBegin on the stack");
}
}
}
fn attribute(&mut self, target: &str, params: ParsedParameterVector, loc: FileLoc) {
let current_attributes = match target {
"shape" => &mut self.graphics_state.shape_attributes,
"light" => &mut self.graphics_state.light_attributes,
"material" => &mut self.graphics_state.material_attributes,
"medium" => &mut self.graphics_state.medium_attributes,
"texture" => &mut self.graphics_state.texture_attributes,
_ => {
log::error!(
"[{:?}] Unknown attribute target \"{}\". Must be \"shape\", \"light\", \
\"material\", \"medium\", or \"texture\".",
loc,
target
);
return;
}
};
let active_color_space = self.graphics_state.color_space.clone();
for mut p in params {
p.may_be_unused = true;
p.color_space = active_color_space.clone();
current_attributes.push(p.clone());
}
}
fn texture(
&mut self,
orig_name: &str,
type_name: &str,
tex_name: &str,
params: &ParsedParameterVector,
loc: FileLoc,
) {
let name = normalize_utf8(orig_name);
self.verify_world("Texture", &loc);
let dict = ParameterDictionary::from_array(
params.clone(),
&self.graphics_state.texture_attributes,
self.graphics_state.color_space.clone(),
);
if type_name != "float" && type_name != "spectrum" {
self.error_exit_deferred(
&loc,
&format!(
"{}: texture type unknown. Must be \"float\" or \"spectrum\".",
tex_name
),
);
return;
}
{
let names = if type_name == "float" {
&mut self.float_texture_names
} else {
&mut self.spectrum_texture_names
};
if names.contains(name) {
self.error_exit_deferred(&loc, &format!("Redefining texture \"{}\".", name));
return;
}
names.insert(name.to_string());
}
let base = SceneEntity {
name: tex_name,
parameters: dict,
loc,
};
let entity = TextureSceneEntity {
base,
render_from_object: self.graphics_state.render_from_object.clone(),
};
if type_name == "float" {
self.scene.add_float_texture(name.to_string(), entity);
} else {
self.scene.add_spectrum_texture(name.to_string(), entity);
}
}
fn material(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
self.verify_world("material", loc);
let entity = SceneEntity {
name,
loc,
parameters: params,
};
self.graphics_state.current_material_name = self.scene.add_material(name);
}
fn make_named_material(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
todo!()
}
fn named_material(&mut self, _name: &str, _loc: FileLoc) {
todo!()
}
fn light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
todo!()
}
fn area_light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
todo!()
}
fn shape(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
todo!()
}
fn object_begin(&mut self, _name: &str, _loc: FileLoc) {
todo!()
}
fn object_end(&mut self, _loc: FileLoc) {
todo!()
}
fn object_instance(&mut self, _name: &str, _loc: FileLoc) {
todo!()
}
fn end_of_files(&mut self) {
todo!()
}
}

View file

@ -0,0 +1,81 @@
#[derive(Clone, Debug)]
pub enum MaterialRef {
Index(usize),
Name(String),
None,
}
#[derive(Clone, Default, Debug)]
pub struct SceneEntity {
pub name: String,
pub loc: FileLoc,
pub parameters: ParameterDictionary,
}
#[derive(Clone, Debug)]
pub struct TransformedSceneEntity {
pub base: SceneEntity,
pub render_from_object: AnimatedTransform,
}
pub type MediumSceneEntity = TransformedSceneEntity;
pub type TextureSceneEntity = TransformedSceneEntity;
#[derive(Clone, Debug)]
pub struct CameraSceneEntity {
pub base: SceneEntity,
pub camera_transform: CameraTransform,
pub medium: String,
}
#[derive(Clone, Debug)]
pub struct ShapeSceneEntity {
pub base: SceneEntity,
pub render_from_object: Arc<Transform>,
pub object_from_render: Arc<Transform>,
pub reverse_orientation: bool,
pub material: MaterialRef,
pub light_index: Option<usize>,
pub inside_medium: String,
pub outside_medium: String,
}
#[derive(Clone, Debug)]
pub struct AnimatedShapeSceneEntity {
pub transformed_base: TransformedSceneEntity,
pub identity: Arc<Transform>,
pub reverse_orientation: bool,
pub material: MaterialRef,
pub light_index: Option<usize>,
pub inside_medium: String,
pub outside_medium: String,
}
#[derive(Clone, Debug)]
pub struct InstanceDefinitionSceneEntity {
pub name: String,
pub loc: FileLoc,
pub shapes: Vec<ShapeSceneEntity>,
pub animated_shapes: Vec<AnimatedShapeSceneEntity>,
}
#[derive(Clone, Debug)]
pub struct LightSceneEntity {
pub transformed_base: TransformedSceneEntity,
pub medium: String,
}
#[derive(Clone, Debug)]
pub enum InstanceTransform {
Animated(AnimatedTransform),
Static(Arc<Transform>),
}
#[derive(Clone, Debug)]
pub struct InstanceSceneEntity {
pub name: String,
pub loc: FileLoc,
pub transform: InstanceTransform,
}

9
src/core/scene/mod.rs Normal file
View file

@ -0,0 +1,9 @@
mod builder;
mod entities;
mod scene;
mod state;
pub use builder::BasicSceneBuilder;
pub use entities::*;
pub use scene::{BasicScene, SceneLookup};
pub use state::*;

732
src/core/scene/scene.rs Normal file
View file

@ -0,0 +1,732 @@
use super::entities::*;
use super::state::*;
use crate::core::filter::FilterFactory;
use crate::core::image::{Image, io::ImageIO};
use crate::core::material::MaterialFactory;
use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::utils::arena::Arena;
use crate::utils::error::FileLoc;
use crate::utils::parallel::{AsyncJob, run_async};
use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
use crate::utils::{Upload, resolve_filename};
use parking_lot::Mutex;
use rayon::prelude::*;
use shared::core::camera::Camera;
use shared::core::color::ColorEncoding;
use shared::core::film::Film;
use shared::core::filter::Filter;
use shared::core::light::Light;
use shared::core::material::Material;
use shared::core::medium::{Medium, MediumInterface};
use shared::core::primitive::{GeometricPrimitive, Primitive, PrimitiveTrait, SimplePrimitive};
use shared::core::sampler::Sampler;
use shared::core::shape::Shape;
use shared::core::texture::SpectrumType;
use shared::spectra::RGBColorSpace;
use shared::textures::FloatConstantTexture;
use std::collections::HashMap;
use std::sync::Arc;
pub struct SceneLookup<'a> {
pub textures: &'a NamedTextures,
pub media: &'a HashMap<String, Arc<Medium>>,
pub named_materials: &'a HashMap<String, Material>,
pub materials: &'a Vec<Material>,
pub shape_lights: &'a HashMap<usize, Vec<Light>>,
}
impl<'a> SceneLookup<'a> {
pub fn find_medium(&self, name: &str, loc: &FileLoc) -> Option<Arc<Medium>> {
if name.is_empty() {
return None;
}
self.media.get(name).cloned().or_else(|| {
panic!("{}: medium '{}' not defined", loc, name);
})
}
pub fn resolve_material(&self, mat_ref: &MaterialRef, _loc: &FileLoc) -> Option<Material> {
match mat_ref {
MaterialRef::Name(name) => {
Some(*self.named_materials.get(name).expect("Material not found"))
}
MaterialRef::Index(idx) => Some(self.materials[*idx]),
MaterialRef::None => None,
}
}
}
pub struct BasicScene {
pub integrator: Mutex<Option<SceneEntity>>,
pub accelerator: Mutex<Option<SceneEntity>>,
pub film_colorspace: Mutex<Option<Arc<RGBColorSpace>>>,
pub shapes: Mutex<Vec<ShapeSceneEntity>>,
pub animated_shapes: Mutex<Vec<AnimatedShapeSceneEntity>>,
pub instances: Mutex<Vec<InstanceSceneEntity>>,
pub instance_definitions: Mutex<HashMap<String, Arc<InstanceDefinitionSceneEntity>>>,
pub media_state: Mutex<MediaState>,
pub material_state: Mutex<MaterialState>,
pub light_state: Mutex<LightState>,
pub texture_state: Mutex<TextureState>,
pub camera_state: Mutex<SingletonState<Camera>>,
pub sampler_state: Mutex<SingletonState<Sampler>>,
pub film_state: Mutex<SingletonState<Film>>,
}
impl BasicScene {
pub fn new() -> Self {
Self {
integrator: Mutex::new(None),
accelerator: Mutex::new(None),
film_colorspace: Mutex::new(None),
shapes: Mutex::new(Vec::new()),
animated_shapes: Mutex::new(Vec::new()),
instances: Mutex::new(Vec::new()),
instance_definitions: Mutex::new(HashMap::new()),
media_state: Mutex::new(MediaState::default()),
material_state: Mutex::new(MaterialState::default()),
light_state: Mutex::new(LightState::default()),
texture_state: Mutex::new(TextureState::default()),
camera_state: Mutex::new(SingletonState::default()),
sampler_state: Mutex::new(SingletonState::default()),
film_state: Mutex::new(SingletonState::default()),
}
}
// ========================================================================
// Options
// ========================================================================
pub fn set_options(
self: &Arc<Self>,
filter: SceneEntity,
film: SceneEntity,
camera: CameraSceneEntity,
sampler: SceneEntity,
integ: SceneEntity,
accel: SceneEntity,
) {
*self.integrator.lock() = Some(integ);
*self.accelerator.lock() = Some(accel);
if let Some(cs) = film.parameters.color_space.as_ref() {
*self.film_colorspace.lock() = Some(Arc::clone(cs));
}
let filt = Filter::create(&filter.name, &filter.parameters, &filter.loc);
let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.);
let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.);
let exposure_time = shutter_close - shutter_open;
let film_instance = Arc::new(
Film::create(
&film.name,
&film.parameters,
exposure_time,
filt.expect("Must have a filter"),
Some(camera.camera_transform.clone()),
&film.loc,
)
.expect("Must have a film"),
);
*self.film_state.lock() = SingletonState {
result: Some(Arc::clone(&film_instance)),
job: None,
};
let sampler_film = Arc::clone(&film_instance);
let sampler_job = run_async(move || {
let res = sampler_film.as_ref().base().full_resolution;
Sampler::create(&sampler.name, &sampler.parameters, res, &sampler.loc)
.expect("Sampler was not correctly created")
});
self.sampler_state.lock().job = Some(sampler_job);
let camera_film = Arc::clone(&film_instance);
let scene_ptr = Arc::clone(self);
let camera_job = run_async(move || {
let medium = scene_ptr.get_medium(&camera.medium, &camera.base.loc);
Camera::create(
&camera.base.name,
&camera.base.parameters,
medium,
&camera.camera_transform,
camera_film,
&camera.base.loc,
)
.expect("Failed to create camera")
});
self.camera_state.lock().job = Some(camera_job);
}
// ========================================================================
// Add methods
// ========================================================================
pub fn add_named_material(&self, name: &str, material: SceneEntity) {
let mut state = self.material_state.lock();
self.start_loading_normal_maps(&mut state, &material.parameters);
state.named_materials.push((name.to_string(), material));
}
pub fn add_material(&self, material: SceneEntity) -> usize {
let mut state = self.material_state.lock();
self.start_loading_normal_maps(&mut state, &material.parameters);
state.materials.push(material);
state.materials.len() - 1
}
pub fn add_float_texture(&self, name: String, texture: TextureSceneEntity) {
if texture.render_from_object.is_animated() {
log::info!(
"{}: Animated world to texture not supported, using start",
texture.base.loc
);
}
let mut state = self.texture_state.lock();
if texture.base.name != "imagemap" && texture.base.name != "ptex" {
state.serial_float_textures.push((name, texture));
return;
}
let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", ""));
if filename.is_empty() {
log::error!(
"[{:?}] \"string filename\" not provided for image texture.",
texture.base.loc
);
state.n_missing_textures += 1;
return;
}
if !std::path::Path::new(&filename).exists() {
log::error!("[{:?}] {}: file not found.", texture.base.loc, filename);
state.n_missing_textures += 1;
return;
}
if state.loading_texture_filenames.contains(&filename) {
state.serial_float_textures.push((name, texture));
return;
}
state.loading_texture_filenames.insert(filename.clone());
let texture_clone = texture.clone();
let job = run_async(move || {
let render_from_texture = texture_clone.render_from_object.start_transform();
let tex_dict = TextureParameterDictionary::new(&texture_clone.base.parameters, None);
Arc::new(FloatTexture::create(
&texture_clone.base.name,
&render_from_texture,
&tex_dict,
&texture_clone.base.loc,
))
});
state.float_texture_jobs.insert(name, job);
}
pub fn add_spectrum_texture(&self, name: String, texture: TextureSceneEntity) {
if texture.render_from_object.is_animated() {
log::info!(
"{}: Animated world to texture not supported, using start",
texture.base.loc
);
}
let mut state = self.texture_state.lock();
if texture.base.name != "imagemap" && texture.base.name != "ptex" {
state.serial_spectrum_textures.push((name, texture));
return;
}
let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", ""));
if filename.is_empty() {
log::error!(
"[{:?}] \"string filename\" not provided for image texture.",
texture.base.loc
);
state.n_missing_textures += 1;
return;
}
if !std::path::Path::new(&filename).exists() {
log::error!("[{:?}] {}: file not found.", texture.base.loc, filename);
state.n_missing_textures += 1;
return;
}
if state.loading_texture_filenames.contains(&filename) {
state.serial_spectrum_textures.push((name, texture));
return;
}
state.loading_texture_filenames.insert(filename.clone());
let texture_clone = texture.clone();
let job = run_async(move || {
let render_from_texture = texture_clone.render_from_object.start_transform();
let tex_dict = TextureParameterDictionary::new(&texture_clone.base.parameters, None);
Arc::new(SpectrumTexture::create(
&texture_clone.base.name,
&render_from_texture,
&tex_dict,
SpectrumType::Albedo,
&texture_clone.base.loc,
))
});
state.spectrum_texture_jobs.insert(name, job);
}
pub fn add_area_light(&self, light: SceneEntity) -> usize {
let mut state = self.light_state.lock();
state.area_lights.push(light);
state.area_lights.len() - 1
}
pub fn add_shapes(&self, new_shapes: Vec<ShapeSceneEntity>) {
self.shapes.lock().extend(new_shapes);
}
pub fn add_animated_shapes(&self, new_shapes: Vec<AnimatedShapeSceneEntity>) {
self.animated_shapes.lock().extend(new_shapes);
}
pub fn add_instance_definition(&self, instance: InstanceDefinitionSceneEntity) {
let name = instance.name.clone();
self.instance_definitions
.lock()
.insert(name, Arc::new(instance));
}
pub fn add_instance_uses(&self, uses: Vec<InstanceSceneEntity>) {
self.instances.lock().extend(uses);
}
// ========================================================================
// Finalization: Textures
// ========================================================================
pub fn create_textures(&self) -> NamedTextures {
let mut state = self.texture_state.lock();
let mut float_textures: HashMap<String, Arc<FloatTexture>> = HashMap::new();
let mut spectrum_textures: HashMap<String, Arc<SpectrumTexture>> = HashMap::new();
// Collect async jobs
for (name, job) in state.float_texture_jobs.drain() {
float_textures.insert(name, job.wait());
}
for (name, job) in state.spectrum_texture_jobs.drain() {
spectrum_textures.insert(name, job.wait());
}
// Create serial textures (need access to already-loaded textures)
let named = NamedTextures {
float_textures: float_textures.clone(),
spectrum_textures: spectrum_textures.clone(),
};
for (name, entity) in state.serial_float_textures.drain(..) {
let render_from_texture = entity.render_from_object.start_transform();
let tex_dict = TextureParameterDictionary::new(&entity.base.parameters, Some(&named));
let tex = FloatTexture::create(
&entity.base.name,
&render_from_texture,
&tex_dict,
&entity.base.loc,
);
float_textures.insert(name, Arc::new(tex));
}
for (name, entity) in state.serial_spectrum_textures.drain(..) {
let render_from_texture = entity.render_from_object.start_transform();
let tex_dict = TextureParameterDictionary::new(&entity.base.parameters, Some(&named));
let tex = SpectrumTexture::create(
&entity.base.name,
&render_from_texture,
&tex_dict,
SpectrumType::Albedo,
&entity.base.loc,
);
spectrum_textures.insert(name, Arc::new(tex));
}
NamedTextures {
float_textures,
spectrum_textures,
}
}
// ========================================================================
// Finalization: Materials
// ========================================================================
pub fn create_materials(
&self,
textures: &NamedTextures,
) -> (HashMap<String, Material>, Vec<Material>) {
let mut state = self.material_state.lock();
// Resolve normal map jobs
let jobs: Vec<_> = state.normal_map_jobs.drain().collect();
for (filename, job) in jobs {
let image = job.wait();
state.normal_maps.insert(filename, image);
}
let mut named_materials: HashMap<String, Material> = HashMap::new();
for (name, entity) in &state.named_materials {
if named_materials.contains_key(name) {
log::error!(
"{}: trying to redefine named material '{}'.",
entity.loc,
name
);
continue;
}
let mat_type = entity.parameters.get_one_string("type", "");
if mat_type.is_empty() {
log::error!(
"{}: \"string type\" not provided in named material's parameters.",
entity.loc
);
continue;
}
let normal_map = self.get_normal_map(&state, &entity.parameters);
let tex_dict = TextureParameterDictionary::new(&entity.parameters, Some(textures));
let mat = Material::create(
&mat_type,
&tex_dict,
normal_map,
&named_materials,
&entity.loc,
);
named_materials.insert(name.clone(), mat);
}
let mut materials: Vec<Material> = Vec::with_capacity(state.materials.len());
for entity in &state.materials {
let normal_map = self.get_normal_map(&state, &entity.parameters);
let tex_dict = TextureParameterDictionary::new(&entity.parameters, Some(textures));
let mat = Material::create(
&entity.name,
&tex_dict,
normal_map,
&named_materials,
&entity.loc,
);
materials.push(mat);
}
(named_materials, materials)
}
// ========================================================================
// Finalization: Aggregate
// ========================================================================
pub fn create_aggregate(
&self,
arena: &mut Arena,
textures: &NamedTextures,
named_materials: &HashMap<String, Material>,
materials: &Vec<Material>,
shape_lights: &HashMap<usize, Vec<Light>>,
) -> Vec<Primitive> {
let shapes = self.shapes.lock();
let animated_shapes = self.animated_shapes.lock();
let media = self.media_state.lock();
let lookup = SceneLookup {
textures,
media: &media.map,
named_materials,
materials,
shape_lights,
};
let mut primitives = Vec::new();
// Static shapes: parallel load, serial upload
let loaded = self.load_shapes_parallel(&shapes, &lookup);
primitives.extend(self.upload_shapes(arena, &shapes, loaded, &lookup));
// Animated shapes
let loaded_anim = self.load_animated_shapes_parallel(&animated_shapes, &lookup);
primitives.extend(self.upload_animated_shapes(
arena,
&animated_shapes,
loaded_anim,
&lookup,
));
primitives
}
fn load_shapes_parallel(
&self,
entities: &[ShapeSceneEntity],
lookup: &SceneLookup,
) -> Vec<Vec<Shape>> {
entities
.par_iter()
.map(|sh| {
Shape::create(
&sh.base.name,
sh.render_from_object.as_ref(),
sh.object_from_render.as_ref(),
sh.reverse_orientation,
&sh.base.parameters,
&lookup.textures.float_textures,
&sh.base.loc,
)
})
.collect()
}
fn load_animated_shapes_parallel(
&self,
entities: &[AnimatedShapeSceneEntity],
lookup: &SceneLookup,
) -> Vec<Vec<Shape>> {
entities
.par_iter()
.map(|sh| {
Shape::create(
&sh.transformed_base.base.name,
sh.identity.as_ref(),
sh.identity.as_ref(),
sh.reverse_orientation,
&sh.transformed_base.base.parameters,
&lookup.textures.float_textures,
&sh.transformed_base.base.loc,
)
})
.collect()
}
fn upload_shapes(
&self,
arena: &mut Arena,
entities: &[ShapeSceneEntity],
loaded: Vec<Vec<Shape>>,
lookup: &SceneLookup,
) -> Vec<Primitive> {
let mut primitives = Vec::new();
for (i, (entity, shapes)) in entities.iter().zip(loaded).enumerate() {
if shapes.is_empty() {
continue;
}
let alpha_tex = self.get_alpha_texture(
&entity.base.parameters,
&entity.base.loc,
&lookup.textures.float_textures,
);
let mtl = lookup.resolve_material(&entity.material, &entity.base.loc);
let mi = MediumInterface::new(
lookup.find_medium(&entity.inside_medium, &entity.base.loc),
lookup.find_medium(&entity.outside_medium, &entity.base.loc),
);
let shape_lights_opt = lookup.shape_lights.get(&i);
for (j, shape) in shapes.into_iter().enumerate() {
let mut area_light = None;
if entity.light_index.is_some() {
if let Some(lights) = shape_lights_opt {
if j < lights.len() {
area_light = Some(lights[j].clone());
}
}
}
let shape_ptr = shape.upload(arena);
let prim =
if area_light.is_none() && !mi.is_medium_transition() && alpha_tex.is_none() {
let p = SimplePrimitive::new(shape_ptr, mtl);
Primitive::Simple(arena.alloc(p))
} else {
let p = GeometricPrimitive::new(
shape_ptr,
mtl,
area_light,
mi.clone(),
alpha_tex.clone(),
);
Primitive::Geometric(arena.alloc(p))
};
primitives.push(prim);
}
}
primitives
}
fn upload_animated_shapes(
&self,
_arena: &mut Arena,
_entities: &[AnimatedShapeSceneEntity],
_loaded: Vec<Vec<Shape>>,
_lookup: &SceneLookup,
) -> Vec<Primitive> {
// TODO: implement animated shape upload
Vec::new()
}
// ========================================================================
// Getters
// ========================================================================
pub fn get_camera(&self) -> Arc<Camera> {
self.get_singleton(&self.camera_state, "Camera")
}
pub fn get_sampler(&self) -> Arc<Sampler> {
self.get_singleton(&self.sampler_state, "Sampler")
}
pub fn get_film(&self) -> Arc<Film> {
self.get_singleton(&self.film_state, "Film")
}
// ========================================================================
// Private helpers
// ========================================================================
fn get_singleton<T: Send + 'static>(
&self,
state: &Mutex<SingletonState<T>>,
name: &str,
) -> Arc<T> {
let mut guard = state.lock();
if let Some(ref res) = guard.result {
return res.clone();
}
if let Some(job) = guard.job.take() {
let res = Arc::new(job.wait());
guard.result = Some(res.clone());
return res;
}
panic!("{} requested but not initialized!", name);
}
fn start_loading_normal_maps(&self, state: &mut MaterialState, params: &ParameterDictionary) {
let filename = resolve_filename(&params.get_one_string("normalmap", ""));
if filename.is_empty() {
return;
}
if state.normal_map_jobs.contains_key(&filename)
|| state.normal_maps.contains_key(&filename)
{
return;
}
let filename_clone = filename.clone();
let job = run_async(move || {
let path = std::path::Path::new(&filename_clone);
let immeta = Image::read(path, Some(ColorEncoding::Linear))
.unwrap_or_else(|e| panic!("{}: unable to read normal map: {}", filename_clone, e));
let rgb_desc = immeta
.image
.get_channel_desc(&["R", "G", "B"])
.unwrap_or_else(|| {
panic!(
"{}: normal map must contain R, G, B channels",
filename_clone
)
});
Arc::new(immeta.image.select_channels(&rgb_desc))
});
state.normal_map_jobs.insert(filename, job);
}
fn get_normal_map(
&self,
state: &MaterialState,
params: &ParameterDictionary,
) -> Option<Arc<Image>> {
let filename = resolve_filename(&params.get_one_string("normalmap", ""));
if filename.is_empty() {
return None;
}
state.normal_maps.get(&filename).cloned()
}
fn get_alpha_texture(
&self,
params: &ParameterDictionary,
loc: &FileLoc,
textures: &HashMap<String, Arc<FloatTexture>>,
) -> Option<Arc<FloatTexture>> {
if let Some(name) = params.get_texture("alpha") {
match textures.get(&name) {
Some(tex) => Some(tex.clone()),
None => panic!("{:?}: Alpha texture '{}' not found", loc, name),
}
} else {
let alpha_val = params.get_one_float("alpha", 1.0);
if alpha_val < 1.0 {
Some(Arc::new(FloatTexture::Constant(FloatConstantTexture::new(
alpha_val,
))))
} else {
None
}
}
}
pub fn get_medium(&self, name: &str, loc: &FileLoc) -> Option<Arc<Medium>> {
if name.is_empty() {
return None;
}
let mut state = self.media_state.lock();
if let Some(medium) = state.map.get(name) {
return Some(medium.clone());
}
if let Some(job) = state.jobs.remove(name) {
let medium = Arc::new(job.wait());
state.map.insert(name.to_string(), medium.clone());
return Some(medium);
}
log::error!("{}: Medium \"{}\" is not defined.", loc, name);
None
}
}

36
src/core/scene/state.rs Normal file
View file

@ -0,0 +1,36 @@
#[derive(Default)]
pub struct TextureState {
pub serial_float_textures: Vec<(String, TextureSceneEntity)>,
pub serial_spectrum_textures: Vec<(String, TextureSceneEntity)>,
pub async_spectrum_textures: Vec<(String, TextureSceneEntity)>,
pub loading_texture_filenames: HashSet<String>,
pub float_texture_jobs: HashMap<String, AsyncJob<Arc<FloatTexture>>>,
pub spectrum_texture_jobs: HashMap<String, AsyncJob<Arc<SpectrumTexture>>>,
pub n_missing_textures: i32,
}
#[derive(Default)]
pub struct MaterialState {
pub named_materials: Vec<(String, SceneEntity)>,
pub materials: Vec<SceneEntity>,
pub normal_map_jobs: HashMap<String, AsyncJob<Arc<Image>>>,
pub normal_maps: HashMap<String, Arc<Image>>,
}
#[derive(Default)]
pub struct LightState {
pub light_jobs: Vec<AsyncJob<Light>>,
pub area_lights: Vec<SceneEntity>,
}
#[derive(Default)]
pub struct MediaState {
pub jobs: HashMap<String, AsyncJob<Medium>>,
pub map: HashMap<String, Arc<Medium>>,
}
#[derive(Default)]
pub struct SingletonState<T> {
pub result: Option<Arc<T>>,
pub job: Option<AsyncJob<T>>,
}

View file

@ -42,6 +42,12 @@ pub enum FloatTexture {
Wrinkled(WrinkledTexture), Wrinkled(WrinkledTexture),
} }
impl FloatTextureTrait for Arc<FloatTexture> {
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
self.as_ref().evaluate(ctx)
}
}
impl FloatTexture { impl FloatTexture {
pub fn create( pub fn create(
name: &str, name: &str,
@ -119,10 +125,16 @@ pub enum SpectrumTexture {
Mix(SpectrumMixTexture), Mix(SpectrumMixTexture),
DirectionMix(SpectrumDirectionMixTexture), DirectionMix(SpectrumDirectionMixTexture),
Dots(SpectrumDotsTexture), Dots(SpectrumDotsTexture),
Ptex(SpectrumPtexTexture), // Ptex(SpectrumPtexTexture),
Scaled(SpectrumScaledTexture), Scaled(SpectrumScaledTexture),
} }
impl SpectrumTextureTrait for Arc<SpectrumTexture> {
fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
self.as_ref().evaluate(ctx, lambda)
}
}
pub trait CreateTextureMapping { pub trait CreateTextureMapping {
fn create( fn create(
params: &TextureParameterDictionary, params: &TextureParameterDictionary,

View file

@ -1,320 +0,0 @@
use bytemuck::{Pod, Zeroable};
use cust::context::{CacheConfig, CurrentContext, ResourceLimit};
use cust::device::DeviceAttribute;
use cust::memory::DeviceCopy;
use cust::prelude::*;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use std::error::Error;
use std::sync::Arc;
use shared::Float;
use shared::core::geometry::{Normal, Point, Vector};
use shared::core::options::get_options;
use shared::spectra::{SampledSpectrum, SampledWavelengths};
use shared::utils::interval::Interval;
#[macro_export]
macro_rules! impl_gpu_traits {
($name:ty) => {
unsafe impl DeviceCopy for $name {}
unsafe impl Zeroable for $name {}
unsafe impl Pod for $name {}
};
}
#[macro_export]
macro_rules! impl_math_gpu_traits {
($Struct:ident) => {
#[cfg(feature = "use_gpu")]
unsafe impl<T, const N: usize> DeviceCopy for $Struct<T, N> where T: DeviceCopy + Copy {}
unsafe impl<T, const N: usize> Zeroable for $Struct<T, N> where T: Zeroable {}
unsafe impl<T, const N: usize> Pod for $Struct<T, N> where T: Pod {}
};
}
impl_math_gpu_traits!(Vector);
impl_math_gpu_traits!(Normal);
impl_math_gpu_traits!(Point);
impl_gpu_traits!(Interval);
impl_gpu_traits!(Float4);
impl_gpu_traits!(SampledSpectrum);
impl_gpu_traits!(SampledWavelengths);
#[repr(C, align(16))]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Float4 {
pub v: [f32; 4],
}
pub type Vec4 = Vector<Float, 4>;
impl From<Vec4> for Float4 {
#[inline]
fn from(vec: Vector<f32, 4>) -> Self {
Self { v: vec.0 }
}
}
impl From<Float4> for Vec4 {
#[inline]
fn from(storage: Float4) -> Self {
Vector(storage.v)
}
}
struct KernelStats {
description: String,
num_launches: usize,
sum_ms: f32,
min_ms: f32,
max_ms: f32,
}
impl KernelStats {
fn new(description: &str) -> Self {
Self {
description: description.to_string(),
num_launches: 0,
sum_ms: 0.0,
min_ms: 0.0,
max_ms: 0.0,
}
}
}
struct ProfilerEvent {
start: Event,
stop: Event,
active: bool,
stats: Option<Arc<Mutex<KernelStats>>>,
}
impl ProfilerEvent {
fn new() -> Result<Self, cust::error::CudaError> {
let start = Event::new(EventFlags::DEFAULT)?;
let stop = Event::new(EventFlags::DEFAULT)?;
Ok(Self {
start,
stop,
active: false,
stats: None,
})
}
fn sync(&mut self) {
if !self.active {
return;
}
if self.stop.synchronize().is_ok() {
match self.stop.elapsed_time_f32(&self.start) {
Ok(ms) => {
if let Some(stats_arc) = &self.stats {
let mut stats = stats_arc.lock();
stats.num_launches += 1;
if stats.num_launches == 1 {
stats.sum_ms = ms;
stats.min_ms = ms;
stats.max_ms = ms;
} else {
stats.sum_ms += ms;
stats.min_ms = stats.min_ms.min(ms);
stats.max_ms = stats.max_ms.max(ms);
}
}
}
Err(e) => log::error!("Failed to get elapsed time: {:?}", e),
}
}
self.active = false;
}
}
struct Profiler {
kernel_stats: Vec<Arc<Mutex<KernelStats>>>,
event_pool: Vec<ProfilerEvent>,
pool_offset: usize,
}
impl Profiler {
fn new() -> Self {
Self {
kernel_stats: Vec::new(),
event_pool: Vec::new(),
pool_offset: 0,
}
}
fn prepare<'a>(&'a mut self, description: &str) -> &'a mut ProfilerEvent {
if self.event_pool.is_empty() {
for _ in 0..128 {
if let Ok(e) = ProfilerEvent::new() {
self.event_pool.push(e);
}
}
}
if self.pool_offset >= self.event_pool.len() {
self.pool_offset = 0;
}
let idx = self.pool_offset;
self.pool_offset += 1;
let pe = &mut self.event_pool[idx];
if pe.active {
pe.sync();
}
pe.active = true;
pe.stats = None;
let mut found = None;
for s in &self.kernel_stats {
if s.lock().description == description {
found = Some(s.clone());
break;
}
}
if found.is_none() {
let new_stats = Arc::new(Mutex::new(KernelStats::new(description)));
self.kernel_stats.push(new_stats.clone());
found = Some(new_stats);
}
pe.stats = found;
pe
}
}
pub struct GpuState {
pub context: Context,
pub stream: Stream,
profiler: Profiler,
}
impl GpuState {
fn init(device_index: u32) -> Result<Self, Box<dyn Error>> {
match cust::init(CudaFlags::empty()) {
Ok(_) => {}
Err(cust::error::CudaError::SharedObjectInitFailed) => {}
Err(e) => return Err(Box::new(e)),
}
let device = Device::get_device(device_index)?;
let name = device.name().unwrap_or_else(|_| "Unknown".into());
log::info!("Selected GPU: {}", name);
let has_unified = device
.get_attribute(DeviceAttribute::UnifiedAddressing)
.unwrap_or(0);
if has_unified == 0 {
panic!("Selected GPU does not support unified addressing.");
}
let context = Context::new(device)?;
CurrentContext::set_resource_limit(ResourceLimit::StackSize, 8192)?;
CurrentContext::set_resource_limit(ResourceLimit::PrintfFifoSize, 32 * 1024 * 1024)?;
CurrentContext::set_cache_config(CacheConfig::PreferL1)?;
let stream = Stream::new(StreamFlags::DEFAULT, None)?;
Ok(Self {
context,
stream,
profiler: Profiler::new(),
})
}
}
lazy_static! {
pub static ref GPU_STATE: Mutex<Option<GpuState>> = Mutex::new(None);
}
pub fn gpu_init() {
if !get_options().use_gpu {
return;
}
let device_id = get_options().gpu_device.unwrap_or(0);
log::info!("Initializing GPU Device {}", device_id);
match GpuState::init(device_id) {
Ok(state) => {
#[cfg(feature = "use_nvtx")]
nvtx::name_thread("MAIN_THREAD");
*GPU_STATE.lock() = Some(state);
}
Err(e) => {
panic!("Failed to initialize GPU: {:?}", e);
}
}
}
pub fn gpu_thread_init() {
if let Some(state) = GPU_STATE.lock().as_ref() {
if let Err(e) = CurrentContext::set_current(&state.context) {
log::error!("Failed to set CUDA context for thread: {:?}", e);
}
}
}
const SANITY_PTX: &str = r#"
.version 6.5
.target sm_30
.address_size 64
.visible .entry scale_array_kernel(.param .u64 ptr, .param .u32 n, .param .f32 scale) {
.reg .u64 %ptr; .reg .u32 %n; .reg .f32 %scale;
.reg .u32 %tid; .reg .u32 %ntid; .reg .u32 %ctid; .reg .u32 %idx;
.reg .pred %p; .reg .u64 %addr; .reg .f32 %val;
ld.param.u64 %ptr, [ptr];
ld.param.u32 %n, [n];
ld.param.f32 %scale, [scale];
mov.u32 %tid, %tid.x;
mov.u32 %ntid, %ntid.x;
mov.u32 %ctid, %ctaid.x;
mad.lo.s32 %idx, %ctid, %ntid, %tid;
setp.ge.u32 %p, %idx, %n;
@%p ret;
mul.wide.u32 %addr, %idx, 4;
add.u64 %addr, %ptr, %addr;
ld.global.f32 %val, [%addr];
mul.f32 %val, %val, %scale;
st.global.f32 [%addr], %val;
}
"#;
pub fn launch_scale_kernel(ptr: *mut f32, len: usize, scale: f32) -> Result<(), Box<dyn Error>> {
// Note: quick_init works best for isolated tests.
// If the main engine runs, this might conflict, but for the sanity test it is correct.
let _ctx = cust::quick_init()?;
let module = Module::from_str(SANITY_PTX)?;
let stream = Stream::new(StreamFlags::NON_BLOCKING, None)?;
let mut kernel = module.get_function("scale_array_kernel")?;
let block_size = 256;
let grid_size = (len as u32 + block_size - 1) / block_size;
unsafe {
launch!(
kernel<<<grid_size, block_size, 0, stream>>>(
ptr as u64,
len as u32,
scale
)
)?;
}
stream.synchronize()?;
Ok(())
}

View file

@ -1,57 +0,0 @@
use cudarc::driver::{CudaDevice, CudaSlice, DeviceSlice};
use super::{GpuError, gpu_unwrap};
/// Device-only buffer (faster, but not CPU-accessible)
pub struct DeviceBuffer<T: cudarc::driver::DeviceRepr> {
inner: CudaSlice<T>,
}
impl<T: cudarc::driver::DeviceRepr + Clone> DeviceBuffer<T> {
/// Allocate uninitialized
pub fn new(len: usize) -> Result<Self, GpuError> {
let ctx = gpu_unwrap();
let inner = unsafe { ctx.device.alloc::<T>(len)? };
Ok(Self { inner })
}
/// Allocate and copy from host slice
pub fn from_slice(data: &[T]) -> Result<Self, GpuError> {
let ctx = gpu_unwrap();
let inner = ctx.device.htod_sync_copy(data)?;
Ok(Self { inner })
}
/// Copy back to host
pub fn to_vec(&self) -> Result<Vec<T>, GpuError> {
let ctx = gpu_unwrap();
Ok(ctx.device.dtoh_sync_copy(&self.inner)?)
}
/// Raw device pointer (for kernel params)
pub fn as_ptr(&self) -> *mut T {
*self.inner.device_ptr() as *mut T
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.len() == 0
}
/// Get the underlying CudaSlice (for cudarc APIs)
pub fn as_cuda_slice(&self) -> &CudaSlice<T> {
&self.inner
}
pub fn as_cuda_slice_mut(&mut self) -> &mut CudaSlice<T> {
&mut self.inner
}
}
/// Unified memory buffer (CPU + GPU accessible)
/// Note: cudarc doesn't have built-in unified memory,
/// so we use raw CUDA calls or just use DeviceBuffer + explicit copies
pub type UnifiedBuffer<T> = DeviceBuffer<T>;

View file

@ -1,11 +1,19 @@
#[cfg(feature = "use_gpu")]
mod context; mod context;
#[cfg(feature = "use_gpu")]
mod memory; pub use context::{GpuState, gpu_init, gpu_state, gpu_state_or_panic, gpu_thread_init};
#[cfg(feature = "use_gpu")]
pub mod wavefront; pub mod wavefront;
#[cfg(feature = "use_gpu")] use thiserror::Error;
pub use context::{GpuContext, GpuError, gpu, gpu_init, gpu_unwrap};
#[cfg(feature = "use_gpu")] #[derive(Error, Debug)]
pub use memory::UnifiedBuffer; pub enum GpuError {
#[error("CUDA driver error: {0}")]
Driver(#[from] cudarc::driver::DriverError),
#[error("No GPU context initialized")]
NoContext,
}
pub fn gpu_unwrap() -> &'static GpuContext {
context::GPU_CONTEXT.get().expect("GPU not initialized")
}

View file

@ -1,31 +1,32 @@
use std::path::Path;
use crate::core::image::{Image, ImageIO}; use crate::core::image::{Image, ImageIO};
use crate::core::light::{CreateLight, lookup_spectrum}; use crate::core::light::{CreateLight, lookup_spectrum};
use crate::core::spectrum::spectrum_to_photometric; use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::{FloatTexture, FloatTextureTrait}; use crate::core::texture::FloatTexture;
use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename}; use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename};
use log::error; use anyhow::{Result, anyhow};
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
use shared::core::light::{Light, LightBase, LightType}; use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::{Medium, MediumInterface}; use shared::core::medium::{Medium, MediumInterface};
use shared::core::shape::Shape; use shared::core::shape::{Shape, ShapeTrait};
use shared::core::spectrum::{Spectrum, SpectrumTrait}; use shared::core::spectrum::Spectrum;
use shared::core::texture::{SpectrumType, TextureEvalContext}; use shared::core::texture::{SpectrumType, TextureEvalContext};
use shared::lights::DiffuseAreaLight; use shared::lights::DiffuseAreaLight;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::{Ptr, Transform}; use shared::utils::{Ptr, Transform};
use shared::{Float, PI}; use shared::{Float, PI};
use std::fmt::Error;
pub trait CreateDiffuseLight { pub trait CreateDiffuseLight {
fn new( fn new(
render_from_light: shared::utils::Transform, render_from_light: Transform,
medium_interface: MediumInterface, medium_interface: MediumInterface,
le: Spectrum, le: Spectrum,
scale: Float, scale: Float,
shape: Ptr<Shape>, shape: Ptr<Shape>,
alpha: Ptr<FloatTexture>, alpha: Ptr<GPUFloatTexture>,
image: Ptr<Image>, image: Ptr<DeviceImage>,
colorspace: Option<RGBColorSpace>, colorspace: Ptr<RGBColorSpace>,
two_sided: bool, two_sided: bool,
fov: Float, fov: Float,
) -> Self; ) -> Self;
@ -33,19 +34,19 @@ pub trait CreateDiffuseLight {
impl CreateDiffuseLight for DiffuseAreaLight { impl CreateDiffuseLight for DiffuseAreaLight {
fn new( fn new(
render_from_light: shared::utils::Transform, render_from_light: Transform,
medium_interface: MediumInterface, medium_interface: MediumInterface,
le: Spectrum, le: Spectrum,
scale: Float, scale: Float,
shape: Ptr<Shape>, shape: Ptr<Shape>,
alpha: Ptr<FloatTexture>, alpha: Ptr<GPUFloatTexture>,
image: Ptr<Image>, image: Ptr<DeviceImage>,
colorspace: Option<RGBColorSpace>, colorspace: Ptr<RGBColorSpace>,
two_sided: bool, two_sided: bool,
fov: Float, fov: Float,
) -> Self { ) -> Self {
let is_constant_zero = match &alpha { let is_constant_zero = match &alpha {
FloatTexture::FloatConstant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0, FloatTexture::Constant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0,
_ => false, _ => false,
}; };
@ -55,11 +56,12 @@ impl CreateDiffuseLight for DiffuseAreaLight {
(LightType::Area, Some(alpha)) (LightType::Area, Some(alpha))
}; };
let base = LightBase::new(light_type, &render_from_light, &medium_interface); let base = LightBase::new(light_type, render_from_light, medium_interface);
let lemit = lookup_spectrum(&le); let lemit = Ptr::from(&lookup_spectrum(&le));
if let Some(im) = &image {
let desc = im if !image.is_null() {
let desc = image
.get_channel_desc(&["R", "G", "B"]) .get_channel_desc(&["R", "G", "B"])
.expect("Image used for DiffuseAreaLight doesn't have R, G, B channels"); .expect("Image used for DiffuseAreaLight doesn't have R, G, B channels");
@ -70,11 +72,13 @@ impl CreateDiffuseLight for DiffuseAreaLight {
); );
assert!( assert!(
colorspace.is_some(), !colorspace.is_null(),
"Image provided but ColorSpace is missing" "Image provided but ColorSpace is missing"
); );
} }
let is_triangle_or_bilinear = matches!(shape, Shape::Triangle(_) | Shape::BilinearPatch(_));
let is_triangle_or_bilinear =
matches!(*shape, Shape::Triangle(_) | Shape::BilinearPatch(_));
if render_from_light.has_scale(None) && !is_triangle_or_bilinear { if render_from_light.has_scale(None) && !is_triangle_or_bilinear {
println!( println!(
"Scaling detected in rendering to light space transformation! \ "Scaling detected in rendering to light space transformation! \
@ -108,44 +112,49 @@ impl CreateLight for DiffuseAreaLight {
shape: &Shape, shape: &Shape,
alpha: &FloatTexture, alpha: &FloatTexture,
colorspace: Option<&RGBColorSpace>, colorspace: Option<&RGBColorSpace>,
) -> Result<Light, Error> { ) -> Result<Light> {
let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant); let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant);
let illum_spec = Spectrum::Dense(colorspace.unwrap().illuminant);
let mut scale = params.get_one_float("scale", 1.); let mut scale = params.get_one_float("scale", 1.);
let two_sided = params.get_one_bool("twosided", false); let two_sided = params.get_one_bool("twosided", false);
let filename = resolve_filename(params.get_one_string("filename", "")); let filename = resolve_filename(&params.get_one_string("filename", ""));
let (image, image_color_space) = if !filename.is_empty() { let (image, image_color_space) = if !filename.is_empty() {
if l.is_some() { if l.is_some() {
return Err(error!(loc, "both \"L\" and \"filename\" specified")); return Err(anyhow!(loc, "both \"L\" and \"filename\" specified"));
} }
let im = Image::read(&filename, None)?; let im = Image::read(Path::new(&filename), None)?;
if im.image.has_any_infinite_pixels() { if im.image.has_any_infinite_pixels() {
return Err(error!(loc, "{}: image has infinite pixel values", filename)); return Err(anyhow!(
loc,
"{}: image has infinite pixel values",
filename
));
} }
if im.image.has_any_nan_pixels() { if im.image.has_any_nan_pixels() {
return Err(error!(loc, "{}: image has NaN pixel values", filename)); return Err(anyhow!(loc, "{}: image has NaN pixel values", filename));
} }
let channel_desc = im let channel_desc = im
.image .image
.get_channel_desc(&["R", "G", "B"]) .get_channel_desc(&["R", "G", "B"])
.ok_or_else(|| error!(loc, "{}: image must have R, G, B channels", filename))?; .map_err(|_| anyhow!(loc, "{}: image must have R, G, B channels", filename))?;
let image = im.image.select_channels(&channel_desc); let image = im.image.select_channels(&channel_desc);
let cs = im.metadata.get_color_space(); let cs = im.metadata.get_colorspace();
(Some(image), Some(cs)) (Some(image), cs)
} else { } else {
if l.is_none() { if l.is_none() {
l = Some(colorspace.illuminant.clone()); l = Some(illum_spec);
} }
(None, None) (None, None)
}; };
let l_for_scale = l.as_ref().unwrap_or(&colorspace.illuminant); let l_for_scale = l.as_ref().unwrap_or(&illum_spec);
scale /= spectrum_to_photometric(l_for_scale); scale /= spectrum_to_photometric(*l_for_scale);
let phi_v = params.get_one_float("power", -1.0); let phi_v = params.get_one_float("power", -1.0);
if phi_v > 0.0 { if phi_v > 0.0 {
@ -158,13 +167,13 @@ impl CreateLight for DiffuseAreaLight {
if let Some(ref img) = image { if let Some(ref img) = image {
// Get the appropriate luminance vector from the image colour space // Get the appropriate luminance vector from the image colour space
let lum_vec = image_color_space.luminance_vector(); let lum_vec = image_color_space.unwrap().luminance_vector();
let mut sum_k_e = 0.0; let mut sum_k_e = 0.0;
let res = img.resolution; let res = img.resolution();
for y in 0..res.y { for y in 0..res.y() {
for x in 0..res.x { for x in 0..res.x() {
let r = img.get_channel(Point2i::new(x, y), 0); let r = img.get_channel(Point2i::new(x, y), 0);
let g = img.get_channel(Point2i::new(x, y), 1); let g = img.get_channel(Point2i::new(x, y), 1);
let b = img.get_channel(Point2i::new(x, y), 2); let b = img.get_channel(Point2i::new(x, y), 2);
@ -172,7 +181,7 @@ impl CreateLight for DiffuseAreaLight {
sum_k_e += r * lum_vec[0] + g * lum_vec[1] + b * lum_vec[2]; sum_k_e += r * lum_vec[0] + g * lum_vec[1] + b * lum_vec[2];
} }
} }
k_e = sum_k_e / (res.x * res.y) as Float; k_e = sum_k_e / (res.x() * res.y()) as Float;
} }
let side_factor = if two_sided { 2.0 } else { 1.0 }; let side_factor = if two_sided { 2.0 } else { 1.0 };
@ -185,7 +194,7 @@ impl CreateLight for DiffuseAreaLight {
let specific = DiffuseAreaLight::new( let specific = DiffuseAreaLight::new(
render_from_light, render_from_light,
medium.into(), medium.into(),
l.as_ref(), *l_for_scale,
scale, scale,
shape.upload(arena), shape.upload(arena),
alpha.upload(arena), alpha.upload(arena),

View file

@ -3,7 +3,7 @@ use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::utils::{Arena, FileLoc, ParameterDictionary}; use crate::utils::{Arena, FileLoc, ParameterDictionary};
use shared::Float; use shared::Float;
use shared::core::geometry::{Point3f, Vector3f, VectorLike}; use shared::core::geometry::{Point3, Point3f, Vector3f, VectorLike};
use shared::core::light::{Light, LightBase, LightType}; use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::{Medium, MediumInterface}; use shared::core::medium::{Medium, MediumInterface};
use shared::core::shape::Shape; use shared::core::shape::Shape;
@ -25,12 +25,12 @@ impl CreateDistantLight for DistantLight {
render_from_light, render_from_light,
MediumInterface::empty(), MediumInterface::empty(),
); );
let lemit = lookup_spectrum(le); let lemit = Ptr::from(&lookup_spectrum(&le));
Self { Self {
base, base,
lemit, lemit,
scale, scale,
scene_center: Vector3f::default(), scene_center: Point3f::default(),
scene_radius: 0., scene_radius: 0.,
} }
} }
@ -38,23 +38,23 @@ impl CreateDistantLight for DistantLight {
impl CreateLight for DistantLight { impl CreateLight for DistantLight {
fn create( fn create(
arena: &mut Arena, _arena: &mut Arena,
render_from_light: Transform, render_from_light: Transform,
medium: Medium, _medium: Medium,
parameters: &ParameterDictionary, parameters: &ParameterDictionary,
loc: &FileLoc, _loc: &FileLoc,
shape: &Shape, _shape: &Shape,
alpha_text: &FloatTexture, _alpha_text: &FloatTexture,
colorspace: Option<&RGBColorSpace>, colorspace: Option<&RGBColorSpace>,
) -> Result<Light, Error> { ) -> Result<Light, Error> {
let l = parameters let l = parameters
.get_one_spectrum( .get_one_spectrum(
"L", "L",
colorspace.unwrap().illuminant, Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
SpectrumType::Illuminant, SpectrumType::Illuminant,
) )
.unwrap(); .unwrap();
let mut scale = parameters.get_one_float("scale", 1); let mut scale = parameters.get_one_float("scale", 1.);
let from = parameters.get_one_point3f("from", Point3f::new(0., 0., 0.)); let from = parameters.get_one_point3f("from", Point3f::new(0., 0., 0.));
let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.)); let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.));
@ -78,12 +78,10 @@ impl CreateLight for DistantLight {
0., 0.,
1., 1.,
]; ];
let t = Transform::from_flat(m); let t = Transform::from_flat(&m).expect("Could not create transform for DistantLight");
let final_render = render_from_light * t; let final_render = render_from_light * t;
scale /= spectrum_to_photometric(l.unwrap()); scale /= spectrum_to_photometric(l);
// Adjust scale to meet target illuminance value // Adjust scale to meet target illuminance value
// Like for IBLs we measure illuminance as incident on an upward-facing
// patch.
let e_v = parameters.get_one_float("illuminance", -1.); let e_v = parameters.get_one_float("illuminance", -1.);
if e_v > 0. { if e_v > 0. {
scale *= e_v; scale *= e_v;

View file

@ -1,10 +1,12 @@
use std::path::Path;
use crate::core::image::{Image, ImageIO}; use crate::core::image::{Image, ImageIO};
use crate::core::light::{CreateLight, lookup_spectrum}; use crate::core::light::{CreateLight, lookup_spectrum};
use crate::core::spectrum::spectrum_to_photometric; use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::{Arena, FileLoc, ParameterDictionary, resolve_filename}; use crate::utils::{Arena, FileLoc, ParameterDictionary, resolve_filename};
use log::error; use anyhow::{Result, anyhow};
use shared::core::color::ColorEncoding; use shared::core::color::ColorEncoding;
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
use shared::core::image::PixelFormat; use shared::core::image::PixelFormat;
@ -17,7 +19,6 @@ use shared::lights::GoniometricLight;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::{Ptr, Transform}; use shared::utils::{Ptr, Transform};
use shared::{Float, PI}; use shared::{Float, PI};
use std::fmt::Error;
pub trait CreateGoniometricLight { pub trait CreateGoniometricLight {
fn new( fn new(
@ -43,15 +44,15 @@ impl CreateGoniometricLight for GoniometricLight {
medium_interface, medium_interface,
); );
let iemit = lookup_spectrum(le); let iemit = lookup_spectrum(&le);
let d = image.unwrap().get_sampling_distribution_uniform(); // let d = image.get_sampling_distribution_uniform();
let distrib = PiecewiseConstant2D::new_with_data(d); let distrib = PiecewiseConstant2D::from_image(&image);
Self { Self {
base, base,
iemit, iemit,
scale, scale,
image, image: Ptr::from(image.device_image()),
distrib, distrib: Ptr::from(&distrib.device),
} }
} }
} }
@ -67,18 +68,19 @@ impl CreateLight for GoniometricLight {
alpha_text: &FloatTexture, alpha_text: &FloatTexture,
colorspace: Option<&RGBColorSpace>, colorspace: Option<&RGBColorSpace>,
) -> Result<Light, Error> { ) -> Result<Light, Error> {
let i = params.get_one_spectrum( let i = params
.get_one_spectrum(
"I", "I",
colorspace.unwrap().illuminant, Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
SpectrumType::Illuminant, SpectrumType::Illuminant,
); )
.expect("Could not retrieve spectrum");
let mut scale = params.get_one_float("scale", 1.); let mut scale = params.get_one_float("scale", 1.);
let filename = resolve_filename(params.get_one_string("filename", "")); let filename = resolve_filename(&params.get_one_string("filename", ""));
let mut image: Option<Image> = None; let image: Ptr<Image> = if filename.is_empty() {
let image = if filename.is_empty() { Ptr::null()
None
} else { } else {
let im = Image::read(&filename, None) let im = Image::read(Path::new(&filename), None)
.map_err(|e| error!(loc, "could not load image '{}': {}", filename, e))?; .map_err(|e| error!(loc, "could not load image '{}': {}", filename, e))?;
let loaded = im.image; let loaded = im.image;
@ -100,23 +102,22 @@ impl CreateLight for GoniometricLight {
)); ));
} }
Some(convert_to_luminance_image(&loaded, &filename, loc)?) Ptr::from(&convert_to_luminance_image(&loaded, &filename, loc)?)
}; };
scale /= spectrum_to_photometric(&i); scale /= spectrum_to_photometric(i);
let phi_v = params.get_one_float("power", -1.0); let phi_v = params.get_one_float("power", -1.0);
if phi_v > 0.0 { if phi_v > 0.0 {
if let Some(ref img) = image { let k_e = compute_emissive_power(&image);
let k_e = compute_emissive_power(image);
scale *= phi_v / k_e; scale *= phi_v / k_e;
} }
}
let swap_yz: [Float; 16] = [ let swap_yz: [Float; 16] = [
1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1.,
]; ];
let t = Transform::from_flat(swap_yz); let t = Transform::from_flat(&swap_yz)
.expect("Could not create transform for GoniometricLight");
let final_render_from_light = render_from_light * t; let final_render_from_light = render_from_light * t;
let specific = let specific =
@ -126,11 +127,7 @@ impl CreateLight for GoniometricLight {
} }
} }
fn convert_to_luminance_image( fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> Result<Image> {
image: &Image,
filename: &str,
loc: &FileLoc,
) -> Result<Image, Error> {
let res = image.resolution(); let res = image.resolution();
let rgb_desc = image.get_channel_desc(&["R", "G", "B"]); let rgb_desc = image.get_channel_desc(&["R", "G", "B"]);
let y_desc = image.get_channel_desc(&["Y"]); let y_desc = image.get_channel_desc(&["Y"]);
@ -143,10 +140,10 @@ fn convert_to_luminance_image(
(Ok(_), Err(_)) => { (Ok(_), Err(_)) => {
// Convert RGB to Y (luminance) // Convert RGB to Y (luminance)
let mut y_pixels = Vec::with_capacity((res.x * res.y) as usize); let mut y_pixels = Vec::with_capacity((res.x() * res.y()) as usize);
for y in 0..res.y { for y in 0..res.y() {
for x in 0..res.x { for x in 0..res.x() {
let r = image.get_channel(Point2i::new(x, y), 0); let r = image.get_channel(Point2i::new(x, y), 0);
let g = image.get_channel(Point2i::new(x, y), 1); let g = image.get_channel(Point2i::new(x, y), 1);
let b = image.get_channel(Point2i::new(x, y), 2); let b = image.get_channel(Point2i::new(x, y), 2);
@ -154,17 +151,11 @@ fn convert_to_luminance_image(
} }
} }
Ok(Image::from_pixels( Ok(Image::from_f32(y_pixels, res, &["Y"].to_vec()))
PixelFormat::F32,
res,
&["Y"],
ColorEncoding::Linear,
&y_pixels,
))
} }
(Err(_), Ok(_)) => { (Err(_), Ok(_)) => {
// Already has Y channel, use as-is // Already has Y channel
Ok(image.clone()) Ok(image.clone())
} }
@ -179,11 +170,11 @@ fn compute_emissive_power(image: &Image) -> Float {
let res = image.resolution(); let res = image.resolution();
let mut sum_y = 0.0; let mut sum_y = 0.0;
for y in 0..res.y { for y in 0..res.y() {
for x in 0..res.x { for x in 0..res.x() {
sum_y += image.get_channel(Point2i::new(x, y), 0); sum_y += image.get_channel(Point2i::new(x, y), 0);
} }
} }
4.0 * PI * sum_y / (res.x * res.y) as Float 4.0 * PI * sum_y / (res.x() * res.y()) as Float
} }

View file

@ -1,10 +1,15 @@
use crate::Arena; use crate::Arena;
use crate::core::image::Image; use crate::core::image::{Image, ImageIO};
use crate::core::spectrum::spectrum_to_photometric; use crate::core::spectrum::spectrum_to_photometric;
use crate::utils::{FileLoc, ParameterDictionary, resolve_filename}; use crate::spectra::{get_colorspace_context, get_spectra_context};
use log::error; use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D};
use crate::utils::{FileLoc, ParameterDictionary, Upload, resolve_filename};
use anyhow::{Result, anyhow};
use rayon::iter::{IndexedParallelIterator, ParallelIterator};
use rayon::prelude::ParallelSliceMut;
use shared::core::camera::CameraTransform; use shared::core::camera::CameraTransform;
use shared::core::geometry::{Bounds2f, Frame, Point2f, Point2i, Point3f, cos_theta}; use shared::core::geometry::{Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike, cos_theta};
use shared::core::image::{PixelFormat, WrapMode}; use shared::core::image::{PixelFormat, WrapMode};
use shared::core::light::{Light, LightBase, LightType}; use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::MediumInterface; use shared::core::medium::MediumInterface;
@ -14,10 +19,9 @@ use shared::lights::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLig
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::hash::hash_float; use shared::utils::hash::hash_float;
use shared::utils::math::{equal_area_sphere_to_square, equal_area_square_to_sphere}; use shared::utils::math::{equal_area_sphere_to_square, equal_area_square_to_sphere};
use shared::utils::sampling::{DevicePiecewiseConstant2D, WindowedPiecewiseConstant2D};
use shared::utils::{Ptr, Transform}; use shared::utils::{Ptr, Transform};
use shared::{Float, PI}; use shared::{Float, PI};
use std::fmt::Error; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use crate::core::light::lookup_spectrum; use crate::core::light::lookup_spectrum;
@ -27,8 +31,8 @@ pub trait CreateImageInfiniteLight {
render_from_light: Transform, render_from_light: Transform,
medium_interface: MediumInterface, medium_interface: MediumInterface,
scale: Float, scale: Float,
image: Ptr<Image>, image: Arc<DeviceImage>,
image_color_space: Ptr<RGBColorSpace>, image_color_space: Arc<RGBColorSpace>,
) -> Self; ) -> Self;
} }
@ -37,13 +41,13 @@ impl CreateImageInfiniteLight for ImageInfiniteLight {
render_from_light: Transform, render_from_light: Transform,
medium_interface: MediumInterface, medium_interface: MediumInterface,
scale: Float, scale: Float,
image: Ptr<Image>, image: Arc<Image>,
image_color_space: Ptr<RGBColorSpace>, image_color_space: Arc<RGBColorSpace>,
) -> Self { ) -> Self {
let base = LightBase::new( let base = LightBase::new(
LightType::Infinite, LightType::Infinite,
&render_from_light, render_from_light,
&MediumInterface::default(), MediumInterface::default(),
); );
let desc = image let desc = image
@ -52,52 +56,62 @@ impl CreateImageInfiniteLight for ImageInfiniteLight {
assert_eq!(3, desc.size()); assert_eq!(3, desc.size());
assert!(desc.is_identity()); assert!(desc.is_identity());
if image.resolution().x() != image.resolution().y() {
let into = hash_float(hashee);
panic!(
"Image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.",
image.resolution.x(),
image.resolution.y()
);
}
let mut d = image.get_sampling_distribution_uniform();
let domain = Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.));
let distrib = DevicePiecewiseConstant2D::new_with_bounds(&d, domain);
let slice = &mut d.values; // or d.as_slice_mut()
let count = slice.len() as Float;
let sum: Float = slice.iter().sum();
let average = sum / count;
let res = image.resolution();
assert_eq!(
res.x(),
res.y(),
"Image resolution ({}, {}) is non-square. Unlikely to be an equal area environment map.",
res.x(),
res.y()
);
let n_u = res.x() as usize;
let n_v = res.y() as usize;
let mut data: Vec<Float> = (0..n_v)
.flat_map(|v| {
(0..n_u).map(move |u| {
image
.get_channels(Point2i::new(u as i32, v as i32))
.average()
})
})
.collect();
let distrib = PiecewiseConstant2D::new(&data, n_u, n_v);
let slice = d.as_mut_slice();
let average = slice.iter().sum::<Float>() / slice.len() as Float;
let mut all_zero = true;
for v in slice.iter_mut() { for v in slice.iter_mut() {
*v = (*v - average).max(0.0); *v = (*v - average).max(0.0);
all_zero &= *v == 0.0;
} }
let all_zero = slice.iter().all(|&v| v == 0.0);
if all_zero { if all_zero {
for v in slice.iter_mut() { data.fill(1.0);
*v = 1.0;
}
} }
let compensated_distrib = DevicePiecewiseConstant2D::new_with_bounds(&d, domain); let compensated_distrib = PiecewiseConstant2D::new(&data, n_u, n_v);
ImageInfiniteLight { ImageInfiniteLight {
base, base,
image: &image, image: Ptr::from(image.device_image()),
image_color_space, image_color_space: Ptr::from(image_color_space.as_ref()),
scene_center: Point3f::default(), scene_center: Point3f::default(),
scene_radius: 0., scene_radius: 0.,
scale, scale,
distrib: &distrib, distrib: Ptr::from(&*distrib),
compensated_distrib: &compensated_distrib, compensated_distrib: Ptr::from(&*compensated_distrib),
}; }
} }
} }
#[derive(Debug)] #[derive(Debug)]
struct InfinitePortalLightStorage { struct InfinitePortalLightStorage {
image: Image, image: Image,
distribution: WindowedPiecewiseConstant2D, distribution: DeviceWindowedPiecewiseConstant2D,
image_color_space: RGBColorSpace, image_color_space: RGBColorSpace,
} }
@ -112,9 +126,9 @@ pub trait CreatePortalInfiniteLight {
fn new( fn new(
render_from_light: Transform, render_from_light: Transform,
scale: Float, scale: Float,
image: Ptr<Image>, image: Arc<Image>,
image_color_space: Ptr<RGBColorSpace>, image_color_space: Arc<RGBColorSpace>,
points: Ptr<Point3f>, points: Vec<Point3f>,
) -> Self; ) -> Self;
} }
@ -122,14 +136,14 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight {
fn new( fn new(
render_from_light: Transform, render_from_light: Transform,
scale: Float, scale: Float,
image: Ptr<Image>, image: Arc<Image>,
image_color_space: Ptr<RGBColorSpace>, image_color_space: Arc<RGBColorSpace>,
points: Ptr<Point3f>, points: Vec<Point3f>,
) -> Self { ) -> Self {
let base = LightBase::new( let base = LightBase::new(
LightType::Infinite, LightType::Infinite,
&render_from_light, render_from_light,
&MediumInterface::default(), MediumInterface::default(),
); );
let desc = image let desc = image
@ -139,7 +153,7 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight {
}); });
assert_eq!(3, desc.offset.len()); assert_eq!(3, desc.offset.len());
let src_res = image.resolution; let src_res = image.resolution();
if src_res.x() != src_res.y() { if src_res.x() != src_res.y() {
panic!( panic!(
"Image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.", "Image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.",
@ -205,12 +219,17 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight {
c, c,
WrapMode::OctahedralSphere.into(), WrapMode::OctahedralSphere.into(),
); );
row_pixels[pixel_idx + c] = val; row_pixels[pixel_idx + c as usize] = val;
} }
} }
}); });
let img = Image::new(PixelFormat::F32, src_res, &["R", "G", "B"], image.encoding); let img = Image::new(
PixelFormat::F32,
src_res,
&["R", "G", "B"],
image.encoding().into(),
);
let duv_dw_closure = |p: Point2f| -> Float { let duv_dw_closure = |p: Point2f| -> Float {
let (_, jacobian) = Self::render_from_image(portal_frame, p); let (_, jacobian) = Self::render_from_image(portal_frame, p);
@ -222,12 +241,12 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight {
Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.)), Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.)),
); );
let distribution = WindowedPiecewiseConstant2D::new(d); let distribution = DeviceWindowedPiecewiseConstant2D::new(d);
PortalInfiniteLight { PortalInfiniteLight {
base, base,
image: img, image: Ptr::from(img.device_image()),
image_color_space: &image_color_space, image_color_space: Ptr::from(&*image_color_space),
scale, scale,
scene_center: Point3f::default(), scene_center: Point3f::default(),
scene_radius: 0., scene_radius: 0.,
@ -246,10 +265,10 @@ impl CreateUniformInfiniteLight for UniformInfiniteLight {
fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self { fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self {
let base = LightBase::new( let base = LightBase::new(
LightType::Infinite, LightType::Infinite,
&render_from_light, render_from_light,
&MediumInterface::default(), MediumInterface::default(),
); );
let lemit = lookup_spectrum(le); let lemit = Ptr::from(&lookup_spectrum(&le));
Self { Self {
base, base,
lemit, lemit,
@ -268,11 +287,11 @@ pub fn create(
parameters: &ParameterDictionary, parameters: &ParameterDictionary,
colorspace: &RGBColorSpace, colorspace: &RGBColorSpace,
loc: &FileLoc, loc: &FileLoc,
) -> Result<Light, Error> { ) -> Result<Light> {
let l = parameters.get_spectrum_array("L", SpectrumType::Illuminant); let l = parameters.get_spectrum_array("L", SpectrumType::Illuminant);
let mut scale = parameters.get_one_float("scale", 1.0); let mut scale = parameters.get_one_float("scale", 1.0);
let portal = parameters.get_point3f_array("portal"); let portal = parameters.get_point3f_array("portal");
let filename = resolve_filename(parameters.get_one_string("filename", "")); let filename = resolve_filename(&parameters.get_one_string("filename", ""));
let e_v = parameters.get_one_float("illuminance", -1.0); let e_v = parameters.get_one_float("illuminance", -1.0);
let has_spectrum = !l.is_empty(); let has_spectrum = !l.is_empty();
@ -280,22 +299,22 @@ pub fn create(
let has_portal = !portal.is_empty(); let has_portal = !portal.is_empty();
if has_spectrum && has_file { if has_spectrum && has_file {
return Err(error!(loc, "cannot specify both \"L\" and \"filename\"")); return Err(anyhow!(loc, "cannot specify both \"L\" and \"filename\""));
} }
if !has_file && !has_portal { if !has_file && !has_portal {
let spectrum = if has_spectrum { let spectrum = if has_spectrum {
scale /= spectrum_to_photometric(&l[0]); scale /= spectrum_to_photometric(l[0]);
&l[0] l[0]
} else { } else {
&colorspace.illuminant Spectrum::Dense(colorspace.illuminant)
}; };
if e_v > 0.0 { if e_v > 0.0 {
scale *= e_v / PI; scale *= e_v / PI;
} }
let light = UniformInfiniteLight::new(render_from_light, lookup_spectrum(spectrum), scale); let light = UniformInfiniteLight::new(render_from_light, spectrum, scale);
return Ok(Light::InfiniteUniform(light)); return Ok(Light::InfiniteUniform(light));
} }
@ -303,32 +322,28 @@ pub fn create(
let (image, image_cs) = load_image_or_constant(&filename, &l, colorspace, loc)?; let (image, image_cs) = load_image_or_constant(&filename, &l, colorspace, loc)?;
scale /= spectrum_to_photometric(&image_cs.illuminant); scale /= spectrum_to_photometric(Spectrum::Dense(image_cs.illuminant));
if e_v > 0.0 { if e_v > 0.0 {
let k_e = compute_hemisphere_illuminance(&image, &image_cs); let k_e = compute_hemisphere_illuminance(&image, &image_cs);
scale *= e_v / k_e; scale *= e_v / k_e;
} }
let image_ptr = image.upload(arena); // let image_ptr = image.upload(arena);
let cs_ptr = image_cs.upload(arena); // let cs_ptr = image_cs.upload(arena);
if has_portal { if has_portal {
let portal_render: Vec<Point3f> = portal let portal_render: Vec<Point3f> = portal
.iter() .iter()
.map(|p| camera_transform.render_from_world(*p)) .map(|p| camera_transform.camera_from_world(0.0).apply_to_point(*p))
.collect(); .collect();
let light = PortalInfiniteLight::new( let (portal_ptr, portal_len) = arena.alloc_slice(&portal_render);
render_from_light, let light =
scale, PortalInfiniteLight::new(render_from_light, scale, image.into(), cs, portal_render);
image_ptr,
cs_ptr,
arena.alloc_slice(&portal_render),
);
Ok(Light::InfinitePortal(light)) Ok(Light::InfinitePortal(light))
} else { } else {
let light = ImageInfiniteLight::new(render_from_light, medium, scale, image_ptr, cs_ptr); let light = ImageInfiniteLight::new(render_from_light, medium, scale, image, cs);
Ok(Light::InfiniteImage(light)) Ok(Light::InfiniteImage(light))
} }
} }
@ -338,28 +353,28 @@ fn load_image_or_constant(
l: &[Spectrum], l: &[Spectrum],
colorspace: &RGBColorSpace, colorspace: &RGBColorSpace,
loc: &FileLoc, loc: &FileLoc,
) -> Result<(Image, RGBColorSpace), Error> { ) -> Result<(Image, RGBColorSpace)> {
if filename.is_empty() { if filename.is_empty() {
let rgb = &l[0].to_rgb(); let stdspec = get_spectra_context();
let image = Image::new_constant(Point2i::new(1, 1), &["R", "G", "B"], &rgb); let rgb = &l[0].to_rgb(colorspace, &stdspec);
let rgb_values = [rgb.r, rgb.g, rgb.b];
let image = Image::new_constant(Point2i::new(1, 1), &["R", "G", "B"], &rgb_values);
Ok((image, colorspace.clone())) Ok((image, colorspace.clone()))
} else { } else {
let im = Image::read(filename, None) let im = Image::read(Path::new(&filename), None)
.map_err(|e| error!(loc, "failed to load '{}': {}", filename, e))?; .map_err(|e| anyhow!(loc, "failed to load '{}': {}", filename, e))?;
if im.image.has_any_infinite_pixels() || im.image.has_any_nan_pixels() { if im.image.has_any_infinite_pixels() || im.image.has_any_nan_pixels() {
return Err(error!(loc, "image '{}' has invalid pixels", filename)); return Err(anyhow!(loc, "image '{}' has invalid pixels", filename));
} }
im.image im.image
.get_channel_desc(&["R", "G", "B"]) .get_channel_desc(&["R", "G", "B"])
.map_err(|_| error!(loc, "image '{}' must have R, G, B channels", filename))?; .map_err(|_| anyhow!(loc, "image '{}' must have R, G, B channels", filename))?;
let cs = im let cs = im.metadata.colorspace.unwrap_or_else(|| colorspace.clone());
.metadata let image_desc = im.image.get_channel_desc(&["R", "G", "B"])?;
.color_space let selected = im.image.select_channels(&image_desc);
.unwrap_or_else(|| colorspace.clone());
let selected = im.image.select_channels(&["R", "G", "B"])?;
Ok((selected, cs)) Ok((selected, cs))
} }
@ -370,13 +385,13 @@ fn compute_hemisphere_illuminance(image: &Image, cs: &RGBColorSpace) -> Float {
let res = image.resolution(); let res = image.resolution();
let mut sum = 0.0; let mut sum = 0.0;
for y in 0..res.y { for y in 0..res.y() {
let v = (y as Float + 0.5) / res.y as Float; let v = (y as Float + 0.5) / res.y() as Float;
for x in 0..res.x { for x in 0..res.x() {
let u = (x as Float + 0.5) / res.x as Float; let u = (x as Float + 0.5) / res.x() as Float;
let w = equal_area_square_to_sphere(Point2f::new(u, v)); let w = equal_area_square_to_sphere(Point2f::new(u, v));
if w.z <= 0.0 { if w.z() <= 0.0 {
continue; continue;
} }
@ -384,9 +399,9 @@ fn compute_hemisphere_illuminance(image: &Image, cs: &RGBColorSpace) -> Float {
let g = image.get_channel(Point2i::new(x, y), 1); let g = image.get_channel(Point2i::new(x, y), 1);
let b = image.get_channel(Point2i::new(x, y), 2); let b = image.get_channel(Point2i::new(x, y), 2);
sum += (r * lum[0] + g * lum[1] + b * lum[2]) * cos_theta(&w); sum += (r * lum[0] + g * lum[1] + b * lum[2]) * cos_theta(w);
} }
} }
sum * 2.0 * PI / (res.x * res.y) as Float sum * 2.0 * PI / (res.x() * res.y()) as Float
} }

View file

@ -35,7 +35,7 @@ impl CreatePointLight for PointLight {
render_from_light, render_from_light,
medium_interface, medium_interface,
); );
let i = lookup_spectrum(le); let i = Ptr::from(lookup_spectrum(&le));
Self { base, scale, i } Self { base, scale, i }
} }
@ -43,24 +43,24 @@ impl CreatePointLight for PointLight {
impl CreateLight for PointLight { impl CreateLight for PointLight {
fn create( fn create(
arena: &mut Arena, _arena: &mut Arena,
render_from_light: Transform, render_from_light: Transform,
medium: Medium, medium: Medium,
parameters: &ParameterDictionary, parameters: &ParameterDictionary,
loc: &FileLoc, _loc: &FileLoc,
shape: &Shape, _shape: &Shape,
alpha_text: &FloatTexture, _alpha: &FloatTexture,
colorspace: Option<&RGBColorSpace>, colorspace: Option<&RGBColorSpace>,
) -> Result<Light, Error> { ) -> Result<Light, Error> {
let l = parameters let l = parameters
.get_one_spectrum( .get_one_spectrum(
"L", "L",
colorspace.unwrap().illuminant, Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
SpectrumType::Illuminant, SpectrumType::Illuminant,
) )
.unwrap(); .unwrap();
let mut scale = parameters.get_one_float("scale", 1); let mut scale = parameters.get_one_float("scale", 1.);
scale /= spectrum_to_photometric(l.unwrap()); scale /= spectrum_to_photometric(l);
let phi_v = parameters.get_one_float("power", 1.); let phi_v = parameters.get_one_float("power", 1.);
if phi_v > 0. { if phi_v > 0. {
let k_e = 4. * PI; let k_e = 4. * PI;

View file

@ -4,7 +4,7 @@ use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename}; use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename};
use log::error; use anyhow::{Result, anyhow};
use shared::Float; use shared::Float;
use shared::core::geometry::{ use shared::core::geometry::{
Bounds2f, Point2f, Point2i, Point3f, Vector3f, VectorLike, cos_theta, Bounds2f, Point2f, Point2i, Point3f, Vector3f, VectorLike, cos_theta,
@ -12,18 +12,19 @@ use shared::core::geometry::{
use shared::core::light::{Light, LightBase, LightType}; use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::{Medium, MediumInterface}; use shared::core::medium::{Medium, MediumInterface};
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::core::spectrum::Spectrum;
use shared::lights::ProjectionLight; use shared::lights::ProjectionLight;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::math::{radians, square}; use shared::utils::math::{radians, square};
use shared::utils::{Ptr, Transform}; use shared::utils::{Ptr, Transform};
use std::fmt::Error; use std::path::Path;
pub trait CreateProjectionLight { pub trait CreateProjectionLight {
fn new( fn new(
render_from_light: Transform, render_from_light: Transform,
medium_interface: MediumInterface, medium_interface: MediumInterface,
scale: Float, scale: Float,
image: Ptr<Image>, image: Ptr<DeviceImage>,
image_color_space: Ptr<RGBColorSpace>, image_color_space: Ptr<RGBColorSpace>,
fov: Float, fov: Float,
) -> Self; ) -> Self;
@ -34,7 +35,7 @@ impl CreateProjectionLight for ProjectionLight {
render_from_light: Transform, render_from_light: Transform,
medium_interface: MediumInterface, medium_interface: MediumInterface,
scale: Float, scale: Float,
image: Ptr<Image>, image: Ptr<DeviceImage>,
image_color_space: Ptr<RGBColorSpace>, image_color_space: Ptr<RGBColorSpace>,
fov: Float, fov: Float,
) -> Self { ) -> Self {
@ -54,9 +55,9 @@ impl CreateProjectionLight for ProjectionLight {
}; };
let hither = 1e-3; let hither = 1e-3;
let screen_from_light = Transform::perspective(fov.unwrap(), hither, 1e30).unwrap(); let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap();
let light_from_screen = screen_from_light.inverse(); let light_from_screen = screen_from_light.inverse();
let opposite = (radians(fov.unwrap()) / 2.).tan(); let opposite = (radians(fov) / 2.).tan();
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect }; let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
let a = 4. * square(opposite) * aspect_ratio; let a = 4. * square(opposite) * aspect_ratio;
let dwda = |p: Point2f| { let dwda = |p: Point2f| {
@ -66,7 +67,7 @@ impl CreateProjectionLight for ProjectionLight {
}; };
let d = image.get_sampling_distribution(dwda, screen_bounds); let d = image.get_sampling_distribution(dwda, screen_bounds);
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds); let distrib = Ptr::from(&PiecewiseConstant2D::from_image(image).device);
Self { Self {
base, base,
@ -93,47 +94,49 @@ impl CreateLight for ProjectionLight {
_shape: &Shape, _shape: &Shape,
_alpha_text: &FloatTexture, _alpha_text: &FloatTexture,
colorspace: Option<&RGBColorSpace>, colorspace: Option<&RGBColorSpace>,
) -> Result<Light, Error> { ) -> Result<Light> {
let mut scale = parameters.get_one_float("scale", 1.); let mut scale = parameters.get_one_float("scale", 1.);
let power = parameters.get_one_float("power", -1.); let power = parameters.get_one_float("power", -1.);
let fov = parameters.get_one_float("fov", 90.); let fov = parameters.get_one_float("fov", 90.);
let filename = resolve_filename(parameters.get_one_string("filename", "")); let filename = resolve_filename(&parameters.get_one_string("filename", ""));
if filename.is_empty() { if filename.is_empty() {
return Err(error!(loc, "must provide filename for projection light")); return Err(error!(loc, "must provide filename for projection light"));
} }
let im = Image::read(&filename, None) let im = Image::read(Path::new(&filename), None)
.map_err(|e| error!(loc, "could not load image '{}': {}", filename, e))?; .map_err(|e| error!(loc, "could not load image '{}': {}", filename, e))?;
if im.image.has_any_infinite_pixels() { if im.image.has_any_infinite_pixels() {
return Err(error!( return Err(anyhow!(
loc, loc,
"image '{}' has infinite pixels, not suitable for light", filename "image '{}' has infinite pixels, not suitable for light",
filename
)); ));
} }
if im.image.has_any_nan_pixels() { if im.image.has_any_nan_pixels() {
return Err(error!( return Err(anyhow!(
loc, loc,
"image '{}' has NaN pixels, not suitable for light", filename "image '{}' has NaN pixels, not suitable for light",
filename
)); ));
} }
let channel_desc = im let channel_desc = im
.image .image
.get_channel_desc(&["R", "G", "B"]) .get_channel_desc(&["R", "G", "B"])
.map_err(|_| error!(loc, "image '{}' must have R, G, B channels", filename))?; .map_err(|_| anyhow!(loc, "image '{}' must have R, G, B channels", filename))?;
let image = im.image.select_channels(&channel_desc); let image = im.image.select_channels(&channel_desc);
let colorspace = im let colorspace = im
.metadata .metadata
.colorspace .colorspace
.ok_or_else(|| error!(loc, "image '{}' missing colorspace metadata", filename))?; .ok_or_else(|| anyhow!(loc, "image '{}' missing colorspace metadata", filename))?;
scale /= spectrum_to_photometric(colorspace.illuminant); scale /= spectrum_to_photometric(Spectrum::Dense(colorspace.illuminant));
if power > 0. { if power > 0. {
let k_e = compute_emissive_power(&image, colorspace, fov); let k_e = compute_emissive_power(&image, &colorspace, fov);
} }
let flip = Transform::scale(1., -1., 1.); let flip = Transform::scale(1., -1., 1.);

View file

@ -1,13 +1,13 @@
use crate::utils::sampling::AliasTableHost; use crate::utils::sampling::AliasTableHost;
use shared::core::light::Light; use shared::core::light::{Light, LightTrait};
use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::spectra::{SampledSpectrum, SampledWavelengths};
use shared::utils::sampling::AliasTable; use shared::utils::sampling::AliasTable;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
pub struct PowerSamplerHost { pub struct PowerSamplerHost {
pub lights: Vec<Light>, pub lights: Vec<Arc<Light>>,
pub light_to_index: HashMap<Light, usize>, pub light_to_index: HashMap<usize, usize>,
pub alias_table: AliasTableHost, pub alias_table: AliasTableHost,
} }
@ -37,7 +37,7 @@ impl PowerSamplerHost {
light_power.push(phi.average()); light_power.push(phi.average());
} }
let alias_table = AliasTable::new(&light_power); let alias_table = AliasTableHost::new(&light_power);
Self { Self {
lights: lights_vec, lights: lights_vec,

View file

@ -42,7 +42,7 @@ impl CreateSpotLight for SpotLight {
MediumInterface::empty(), MediumInterface::empty(),
); );
let iemit = lookup_spectrum(le); let iemit = Ptr::from(&lookup_spectrum(&le));
Self { Self {
base, base,
iemit, iemit,
@ -59,15 +59,15 @@ impl CreateLight for SpotLight {
render_from_light: Transform, render_from_light: Transform,
medium: Medium, medium: Medium,
parameters: &ParameterDictionary, parameters: &ParameterDictionary,
loc: &FileLoc, _loc: &FileLoc,
shape: &Shape, _shape: &Shape,
alpha_tex: &FloatTexture, _alpha_tex: &FloatTexture,
colorspace: Option<&RGBColorSpace>, colorspace: Option<&RGBColorSpace>,
) -> Result<Light, Error> { ) -> Result<Light, Error> {
let i = parameters let i = parameters
.get_one_spectrum( .get_one_spectrum(
"I", "I",
colorspace.unwrap().illuminant, Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
SpectrumType::Illuminant, SpectrumType::Illuminant,
) )
.expect("No spectrum"); .expect("No spectrum");
@ -86,7 +86,7 @@ impl CreateLight for SpotLight {
let cos_falloff_end = radians(coneangle).cos(); let cos_falloff_end = radians(coneangle).cos();
let cos_falloff_start = radians(coneangle - conedelta).cos(); let cos_falloff_start = radians(coneangle - conedelta).cos();
let k_e = let k_e =
2. * PI * ((1. - cos_falloff_start) + (cos_falloff_start - cos_falloff_end) / 2); 2. * PI * ((1. - cos_falloff_start) + (cos_falloff_start - cos_falloff_end) / 2.);
scale *= phi_v / k_e; scale *= phi_v / k_e;
} }

View file

@ -3,6 +3,7 @@ use crate::core::material::CreateMaterial;
use crate::core::texture::SpectrumTexture; use crate::core::texture::SpectrumTexture;
use crate::spectra::data::get_named_spectrum; use crate::spectra::data::get_named_spectrum;
use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload, parameters::error_exit}; use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload, parameters::error_exit};
use anyhow::Result;
use shared::core::material::Material; use shared::core::material::Material;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType; use shared::core::texture::SpectrumType;
@ -10,17 +11,16 @@ use shared::materials::coated::*;
use shared::spectra::ConstantSpectrum; use shared::spectra::ConstantSpectrum;
use shared::textures::SpectrumConstantTexture; use shared::textures::SpectrumConstantTexture;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Error;
use std::sync::Arc; use std::sync::Arc;
impl CreateMaterial for CoatedDiffuseMaterial { impl CreateMaterial for CoatedDiffuseMaterial {
fn create( fn create(
parameters: &TextureParameterDictionary, parameters: &TextureParameterDictionary,
normal_map: Option<Arc<Image>>, normal_map: Option<Arc<Image>>,
named_materials: &HashMap<String, Material>, _named_materials: &HashMap<String, Material>,
loc: &FileLoc, _loc: &FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Material, Error> { ) -> Result<Material> {
let reflectance = parameters let reflectance = parameters
.get_spectrum_texture("reflectance", None, SpectrumType::Albedo) .get_spectrum_texture("reflectance", None, SpectrumType::Albedo)
.unwrap_or(Arc::new(SpectrumTexture::Constant( .unwrap_or(Arc::new(SpectrumTexture::Constant(
@ -29,18 +29,18 @@ impl CreateMaterial for CoatedDiffuseMaterial {
let u_roughness = parameters let u_roughness = parameters
.get_float_texture_or_null("uroughness") .get_float_texture_or_null("uroughness")
.or_else(|| parameters.get_float_texture("roughness", 0.5)); .unwrap_or_else(|| parameters.get_float_texture("roughness", 0.5));
let v_roughness = parameters let v_roughness = parameters
.get_float_texture_or_null("vroughness") .get_float_texture_or_null("vroughness")
.unwap_or_else(|| parameters.get_float("roughness", 0.5)); .unwrap_or_else(|| parameters.get_float_texture("roughness", 0.5));
let thickness = parameters.get_float_texture("thickness", 0.01); let thickness = parameters.get_float_texture("thickness", 0.01);
let eta = parameters let eta = parameters
.get_float_array("eta") .get_float_array("eta")
.first() .first()
.map(|&v| ConstantSpectrum::new(v)) .map(|&v| Spectrum::Constant(ConstantSpectrum::new(v)))
.or_else(|| parameters.get_one_spectrum("eta", None, SpectrumType::Unbounded)) .or_else(|| parameters.get_one_spectrum("eta", None, SpectrumType::Unbounded))
.unwrap_or_else(|| ConstantSpectrum::new(1.5)); .unwrap_or_else(|| Spectrum::Constant(ConstantSpectrum::new(1.5)));
let max_depth = parameters.get_one_int("maxdepth", 10); let max_depth = parameters.get_one_int("maxdepth", 10);
let n_samples = parameters.get_one_int("nsamples", 1); let n_samples = parameters.get_one_int("nsamples", 1);
@ -49,7 +49,7 @@ impl CreateMaterial for CoatedDiffuseMaterial {
.get_spectrum_texture("albedo", None, SpectrumType::Albedo) .get_spectrum_texture("albedo", None, SpectrumType::Albedo)
.unwrap_or_else(|| { .unwrap_or_else(|| {
let default_spectrum = Spectrum::Constant(ConstantSpectrum::new(0.)); let default_spectrum = Spectrum::Constant(ConstantSpectrum::new(0.));
SpectrumTexture::Constant(SpectrumConstantTexture::new(default_spectrum)) SpectrumTexture::Constant(SpectrumConstantTexture::new(default_spectrum)).into()
}); });
let displacement = parameters.get_float_texture("displacement", 0.); let displacement = parameters.get_float_texture("displacement", 0.);
let remap_roughness = parameters.get_one_bool("remaproughness", true); let remap_roughness = parameters.get_one_bool("remaproughness", true);
@ -60,12 +60,12 @@ impl CreateMaterial for CoatedDiffuseMaterial {
thickness.upload(arena), thickness.upload(arena),
albedo.upload(arena), albedo.upload(arena),
g.upload(arena), g.upload(arena),
eta, eta.upload(arena),
displacement.upload(arena), displacement.upload(arena),
normal_map.upload(arena), normal_map.upload(arena),
remap_roughness, remap_roughness,
max_depth, max_depth as u32,
n_samples, n_samples as u32,
); );
Ok(Material::CoatedDiffuse(specific)) Ok(Material::CoatedDiffuse(specific))
@ -76,49 +76,59 @@ impl CreateMaterial for CoatedConductorMaterial {
fn create( fn create(
parameters: &TextureParameterDictionary, parameters: &TextureParameterDictionary,
normal_map: Option<Arc<Image>>, normal_map: Option<Arc<Image>>,
named_materials: &HashMap<String, Material>, _named_materials: &HashMap<String, Material>,
loc: &FileLoc, loc: &FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Material, Error> { ) -> Result<Material> {
let interface_u_roughness = parameters let interface_u_roughness = parameters
.get_float_texture_or_null("interface.uroughness") .get_float_texture_or_null("interface.uroughness")
.r_else(|| parameters.get_float_texture("interface.roughness", 0.)); .unwrap_or_else(|| parameters.get_float_texture("interface.roughness", 0.));
let interface_v_roughness = parameters let interface_v_roughness = parameters
.GetFloatTextureOrNull("interface.vroughness") .get_float_texture_or_null("interface.vroughness")
.unwrap_or_else(|| parameters.get_float_texture("interface.vroughness", 0.)); .unwrap_or_else(|| parameters.get_float_texture("interface.vroughness", 0.));
let thickness = parameters.get_float_texture("interface.thickness", 0.01); let thickness = parameters.get_float_texture("interface.thickness", 0.01);
let interface_eta = parameters let interface_eta: Spectrum = parameters
.get_float_array("interface.eta") .get_float_array("interface.eta")
.first() .first()
.map(|&v| ConstantSpectrum::new(v)) .map(|&v| Spectrum::Constant(ConstantSpectrum::new(v)))
.or_else(|| parameters.get_one_spectrum("interface.eta", None, SpectrumType::Unbounded)) .or_else(|| parameters.get_one_spectrum("interface.eta", None, SpectrumType::Unbounded))
.unwrap_or_else(|| ConstantSpectrum::new(1.5)); .unwrap_or_else(|| Spectrum::Constant(ConstantSpectrum::new(1.5)));
let conductor_u_roughness = parameters let conductor_u_roughness = parameters
.get_float_texture_or_null("conductor.uroughness") .get_float_texture_or_null("conductor.uroughness")
.unwrap_or_else(|| parameters.get_float_texture("conductor.roughness", 0.)); .unwrap_or_else(|| parameters.get_float_texture("conductor.roughness", 0.));
let conductor_v_roughness = parameters let conductor_v_roughness = parameters
.GetFloatTextureOrNull("conductor.vroughness") .get_float_texture_or_null("conductor.vroughness")
.unwrap_or_else(|| parameters.get_float_texture("conductor.vroughness", 0.)); .unwrap_or_else(|| parameters.get_float_texture("conductor.vroughness", 0.));
let reflectance = let reflectance =
parameters.get_spectrum_texture_or_null("reflectance", SpectrumType::Albedo); parameters.get_spectrum_texture_or_null("reflectance", SpectrumType::Albedo);
let conductor_eta = let conductor_eta =
parameters.get_spectrum_texture_or_null("conductor.eta", SpectrumType::Unbounded); parameters.get_spectrum_texture_or_null("conductor.eta", SpectrumType::Unbounded);
let k = parameters.get_spectrum_texture_or_null("conductor.k", SpectrumType::Unbounded); let k = parameters.get_spectrum_texture_or_null("conductor.k", SpectrumType::Unbounded);
let (conductor_eta, k) = match (reflectance, conductor_eta, k) {
let (conductor_eta, k) = match (&reflectance, conductor_eta, k) {
(Some(_), Some(_), _) | (Some(_), _, Some(_)) => { (Some(_), Some(_), _) | (Some(_), _, Some(_)) => {
return Err(error_exit( return Err(error_exit(
loc, Some(loc),
"For the coated conductor material, both \"reflectance\" \ "For the coated conductor material, both \"reflectance\" \
and \"eta\" and \"k\" can't be provided.", and \"eta\" and \"k\" can't be provided.",
)); ));
} }
(None, eta, k) => (
eta.unwrap_or_else(|| { (None, eta, k) => {
SpectrumConstantTexture::new(get_named_spectrum("metal-Cu-eta")) let final_eta = eta.unwrap_or_else(|| {
}), let s = get_named_spectrum("metal-Cu-eta").expect("Missing copper spectrum");
k.unwrap_or_else(|| SpectrumConstantTexture::new(get_named_spectrum("metal-Cu-k"))), Arc::new(SpectrumTexture::Constant(SpectrumConstantTexture::new(s)))
), });
(Some(_), None, None) => (conductor_eta, k),
let final_k = k.unwrap_or_else(|| {
let s = get_named_spectrum("metal-Cu-k").expect("Missing copper spectrum");
Arc::new(SpectrumTexture::Constant(SpectrumConstantTexture::new(s)))
});
(Some(final_eta), Some(final_k))
}
(Some(_), None, None) => (None, None),
}; };
let max_depth = parameters.get_one_int("maxdepth", 10); let max_depth = parameters.get_one_int("maxdepth", 10);
@ -127,27 +137,28 @@ impl CreateMaterial for CoatedConductorMaterial {
let albedo = parameters let albedo = parameters
.get_spectrum_texture("albedo", None, SpectrumType::Albedo) .get_spectrum_texture("albedo", None, SpectrumType::Albedo)
.unwrap_or_else(|| { .unwrap_or_else(|| {
SpectrumConstantTexture::new(Spectrum::Constant(ConstantSpectrum::new(0.))) let spectrum = Spectrum::Constant(ConstantSpectrum::new(0.));
SpectrumTexture::Constant(SpectrumConstantTexture::new(spectrum)).into()
}); });
let displacement = parameters.get_float_texture_or_null("displacement"); let displacement = parameters.get_float_texture_or_null("displacement");
let remap_roughness = parameters.get_one_bool("remaproughness", true); let remap_roughness = parameters.get_one_bool("remaproughness", true);
let material = Self::new( let material = Self::new(
displacement.upload(arena),
normal_map.upload(arena), normal_map.upload(arena),
interface_u_roughness, displacement.upload(arena),
interface_v_roughness, interface_u_roughness.upload(arena),
thickness, interface_v_roughness.upload(arena),
interface_eta, thickness.upload(arena),
g, interface_eta.upload(arena),
albedo, g.upload(arena),
conductor_u_roughness, albedo.upload(arena),
conductor_v_roughness, conductor_u_roughness.upload(arena),
conductor_eta, conductor_v_roughness.upload(arena),
k, conductor_eta.upload(arena),
reflectance, k.upload(arena),
reflectance.upload(arena),
max_depth as u32,
n_samples as u32,
remap_roughness, remap_roughness,
max_depth,
n_samples,
); );
arena.alloc(material); arena.alloc(material);
Ok(Material::CoatedConductor(material)) Ok(Material::CoatedConductor(material))

View file

@ -2,19 +2,16 @@ use crate::core::image::Image;
use crate::core::material::CreateMaterial; use crate::core::material::CreateMaterial;
use crate::spectra::get_colorspace_context; use crate::spectra::get_colorspace_context;
use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload}; use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload};
use shared::bxdfs::HairBxDF; use shared::core::material::Material;
use shared::core::bsdf::BSDF;
use shared::core::bssrdf::BSSRDF;
use shared::core::material::{Material, MaterialEvalContext, MaterialTrait};
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::core::texture::{GPUFloatTexture, SpectrumType, TextureEvaluator}; use shared::core::texture::SpectrumType;
use shared::materials::complex::*; use shared::materials::complex::*;
use shared::spectra::RGBUnboundedSpectrum; use shared::spectra::RGBUnboundedSpectrum;
use shared::spectra::SampledWavelengths; // use shared::spectra::SampledWavelengths;
use shared::textures::SpectrumConstantTexture; use shared::textures::SpectrumConstantTexture;
use shared::utils::Ptr; // use shared::utils::Ptr;
use anyhow::Result;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Error;
use std::sync::Arc; use std::sync::Arc;
impl CreateMaterial for HairMaterial { impl CreateMaterial for HairMaterial {
@ -24,7 +21,7 @@ impl CreateMaterial for HairMaterial {
_named_materials: &HashMap<String, Material>, _named_materials: &HashMap<String, Material>,
loc: &FileLoc, loc: &FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Material, Error> { ) -> Result<Material> {
let sigma_a = parameters.get_spectrum_texture_or_null("sigma_a", SpectrumType::Unbounded); let sigma_a = parameters.get_spectrum_texture_or_null("sigma_a", SpectrumType::Unbounded);
let reflectance = parameters let reflectance = parameters
.get_spectrum_texture_or_null("reflectance", SpectrumType::Albedo) .get_spectrum_texture_or_null("reflectance", SpectrumType::Albedo)
@ -37,17 +34,15 @@ impl CreateMaterial for HairMaterial {
let sigma_a = if sigma_a.is_none() && !reflectance.is_none() && !has_melanin { let sigma_a = if sigma_a.is_none() && !reflectance.is_none() && !has_melanin {
let stdcs = get_colorspace_context(); let stdcs = get_colorspace_context();
let default_rgb = HairBxDF::sigma_a_from_concentration(1.3, 0.0, stdcs); let default_rgb = HairBxDF::sigma_a_from_concentration(1.3, 0.0, stdcs);
Some(Arc::new(SpectrumConstantTexture::new( let spectrum =
Spectrum::RGBUnbounded(RGBUnboundedSpectrum::new( Spectrum::RGBUnbounded(RGBUnboundedSpectrum::new(stdc.srgb, default_rgb));
reflectance.to_rgb(), let texture = SpectrumTexture::Constant(SpectrumConstantTexture::new(spectrum));
default_rgb, Some(Arc::new(texture))
)),
)))
} else { } else {
sigma_a sigma_a
}; };
let eta = parameters.get_flot_texture("eta", 1.55); let eta = parameters.get_float_texture("eta", 1.55);
let beta_m = parameters.get_float_texture("beta_m", 0.3); let beta_m = parameters.get_float_texture("beta_m", 0.3);
let beta_n = parameters.get_float_texture("beta_n", 0.3); let beta_n = parameters.get_float_texture("beta_n", 0.3);
let alpha = parameters.get_float_texture("alpha", 2.); let alpha = parameters.get_float_texture("alpha", 2.);
@ -66,38 +61,6 @@ impl CreateMaterial for HairMaterial {
} }
} }
impl MaterialTrait for MeasuredMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> BSDF {
todo!()
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
todo!()
}
fn can_evaluate_textures(&self, _tex_eval: &dyn TextureEvaluator) -> bool {
todo!()
}
fn get_normal_map(&self) -> *const Image {
todo!()
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
todo!()
}
fn has_subsurface_scattering(&self) -> bool {
todo!()
}
}
impl CreateMaterial for SubsurfaceMaterial { impl CreateMaterial for SubsurfaceMaterial {
fn create( fn create(
_parameters: &TextureParameterDictionary, _parameters: &TextureParameterDictionary,
@ -105,7 +68,7 @@ impl CreateMaterial for SubsurfaceMaterial {
_named_materials: &HashMap<String, Material>, _named_materials: &HashMap<String, Material>,
_loc: &FileLoc, _loc: &FileLoc,
_arena: &mut Arena, _arena: &mut Arena,
) -> Result<Material, Error> { ) -> Result<Material> {
todo!() todo!()
} }
} }

View file

@ -1,79 +1,23 @@
// use crate::core::image::Image;
// use crate::core::material::CreateMaterial;
// use shared::core::bsdf::BSDF;
// use shared::core::bssrdf::BSSRDF;
// use shared::core::material::{Material, MaterialEvalContext, MaterialTrait};
// use shared::core::texture::{GPUFloatTexture, TextureEvaluator};
// use shared::spectra::SampledWavelengths;
// use shared::utils::Ptr;
use crate::core::image::Image; use crate::core::image::Image;
use shared::core::bsdf::BSDF; use crate::core::material::CreateMaterial;
use shared::core::bssrdf::BSSRDF; use shared::materials::MixMaterial;
use shared::core::material::{Material, MaterialEvalContext, MaterialTrait};
use shared::core::texture::{GPUFloatTexture, TextureEvaluator};
use shared::spectra::SampledWavelengths;
use shared::utils::Ptr;
use shared::utils::hash::hash_float;
#[repr(C)] impl CreateMaterial for MixMaterial {
#[derive(Clone, Copy, Debug)] fn create(
pub struct MixMaterial { parameters: &crate::utils::TextureParameterDictionary,
pub amount: Ptr<GPUFloatTexture>, normal_map: Option<std::sync::Arc<Image>>,
pub materials: [Ptr<Material>; 2], named_materials: &std::collections::HashMap<String, Material>,
} loc: &crate::utils::FileLoc,
arena: &mut crate::Arena,
impl MixMaterial { ) -> Result<Material, std::fmt::Error> {
pub fn choose_material<T: TextureEvaluator>( todo!()
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
) -> Option<&Material> {
let amt = tex_eval.evaluate_float(&self.amount, ctx);
let index = if amt <= 0.0 {
0
} else if amt >= 1.0 {
1
} else {
let u = hash_float(&(ctx.p, ctx.wo));
if amt < u { 0 } else { 1 }
};
self.materials[index].get()
}
}
impl MaterialTrait for MixMaterial {
fn get_bsdf<T: TextureEvaluator>(
&self,
tex_eval: &T,
ctx: &MaterialEvalContext,
lambda: &SampledWavelengths,
) -> BSDF {
if let Some(mat) = self.choose_material(tex_eval, ctx) {
mat.get_bsdf(tex_eval, ctx, lambda)
} else {
BSDF::empty()
}
}
fn get_bssrdf<T>(
&self,
_tex_eval: &T,
_ctx: &MaterialEvalContext,
_lambda: &SampledWavelengths,
) -> Option<BSSRDF> {
None
}
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool {
tex_eval.can_evaluate(&[&self.amount], &[])
}
fn get_normal_map(&self) -> *const Image {
core::ptr::null()
}
fn get_displacement(&self) -> Ptr<GPUFloatTexture> {
panic!(
"MixMaterial::get_displacement() shouldn't be called. \
Displacement is not supported on Mix materials directly."
);
}
fn has_subsurface_scattering(&self) -> bool {
false
} }
} }

View file

@ -1,18 +1,76 @@
use crate::Arena; use crate::Arena;
use crate::core::sampler::CreateSampler; use crate::core::sampler::CreateSampler;
use crate::utils::{FileLoc, ParameterDictionary}; use crate::utils::{FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow};
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
use shared::core::options::get_options; use shared::core::options::get_options;
use shared::core::sampler::{HaltonSampler, RandomizeStrategy}; use shared::core::sampler::{HaltonSampler, RandomizeStrategy};
use std::fmt::Error;
pub trait CreateHaltonSampler {
fn new(
samples_per_pixel: i32,
full_res: Point2i,
randomize: RandomizeStrategy,
seed: u64,
) -> Self;
}
impl CreateHaltonSampler for HaltonSampler {
fn new(
samples_per_pixel: i32,
full_res: Point2i,
randomize: RandomizeStrategy,
seed: u64,
) -> Self {
let digit_permutations = compute_radical_inverse_permutations(seed);
let mut base_scales = [0u64; 2];
let mut base_exponents = [0u64; 2];
let bases = [2, 3];
let res_coords = [full_res.x(), full_res.y()];
for i in 0..2 {
let base = bases[i] as u64;
let mut scale = 1u64;
let mut exp = 0u64;
let limit = std::cmp::min(res_coords[i], MAX_HALTON_RESOLUTION) as u64;
while scale < limit {
scale *= base;
exp += 1;
}
base_scales[i] = scale;
base_exponents[i] = exp;
}
let mut mult_inverse = [0u64; 2];
mult_inverse[0] =
Self::multiplicative_inverse(base_scales[0] as i64, base_scales[0] as i64);
mult_inverse[1] =
Self::multiplicative_inverse(base_scales[1] as i64, base_scales[1] as i64);
Self {
samples_per_pixel,
randomize,
digit_permutations,
base_scales,
base_exponents,
mult_inverse,
halton_index: 0,
dim: 0,
}
}
}
impl CreateSampler for HaltonSampler { impl CreateSampler for HaltonSampler {
fn create( fn create(
params: &ParameterDictionary, params: &ParameterDictionary,
full_res: Point2i, full_res: Point2i,
loc: &FileLoc, _loc: &FileLoc,
arena: &mut Arena, _arena: &mut Arena,
) -> Result<Self, Error> { ) -> Result<Self> {
let options = get_options(); let options = get_options();
let nsamp = options let nsamp = options
.quick_render .quick_render
@ -29,15 +87,15 @@ impl CreateSampler for HaltonSampler {
"fastowen" => RandomizeStrategy::FastOwen, "fastowen" => RandomizeStrategy::FastOwen,
"owen" => RandomizeStrategy::Owen, "owen" => RandomizeStrategy::Owen,
_ => { _ => {
return Err(format!( return Err(anyhow!(
"{}: Unknown randomization strategy for Halton", "{}: Unknown randomization strategy for Halton",
loc loc
)); ));
} }
}; };
let sampler = HaltonSampler::new(nsamp as u32, full_res, s, seed as u64); let sampler = HaltonSampler::new(nsamp, full_res, s, seed as u64);
arena.alloc(sampler); // arena.alloc(sampler);
Ok(sampler) Ok(sampler)
} }
} }

View file

@ -1,6 +1,7 @@
use crate::Arena; use crate::Arena;
use crate::core::sampler::CreateSampler; use crate::core::sampler::CreateSampler;
use crate::utils::{FileLoc, ParameterDictionary}; use crate::utils::{FileLoc, ParameterDictionary};
use anyhow::Result;
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
use shared::core::options::get_options; use shared::core::options::get_options;
use shared::core::sampler::IndependentSampler; use shared::core::sampler::IndependentSampler;
@ -12,7 +13,7 @@ impl CreateSampler for IndependentSampler {
_full_res: Point2i, _full_res: Point2i,
_loc: &FileLoc, _loc: &FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Self, Error> { ) -> Result<Self> {
let options = get_options(); let options = get_options();
let nsamp = options let nsamp = options
.quick_render .quick_render
@ -20,7 +21,7 @@ impl CreateSampler for IndependentSampler {
.or(options.pixel_samples) .or(options.pixel_samples)
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16)); .unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
let seed = params.get_one_int("seed", options.seed); let seed = params.get_one_int("seed", options.seed);
let sampler = Self::new(nsamp as usize, seed as u64); let sampler = Self::new(nsamp, seed as u64);
arena.alloc(sampler); arena.alloc(sampler);
Ok(sampler) Ok(sampler)
} }

View file

@ -1,18 +1,18 @@
use crate::Arena; use crate::Arena;
use crate::core::sampler::CreateSampler; use crate::core::sampler::CreateSampler;
use crate::utils::{FileLoc, ParameterDictionary}; use crate::utils::{FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow};
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
use shared::core::options::get_options; use shared::core::options::get_options;
use shared::core::sampler::{PaddedSobolSampler, RandomizeStrategy, SobolSampler, ZSobolSampler}; use shared::core::sampler::{PaddedSobolSampler, RandomizeStrategy, SobolSampler, ZSobolSampler};
use std::fmt::Error;
impl CreateSampler for SobolSampler { impl CreateSampler for SobolSampler {
fn create( fn create(
params: &ParameterDictionary, params: &ParameterDictionary,
full_res: Point2i, full_res: Point2i,
loc: &FileLoc, loc: &FileLoc,
arena: &mut Arena, _arena: &mut Arena,
) -> Result<Self, Error> { ) -> Result<Self> {
let options = get_options(); let options = get_options();
let nsamp = options let nsamp = options
.quick_render .quick_render
@ -26,12 +26,12 @@ impl CreateSampler for SobolSampler {
"fastowen" => RandomizeStrategy::FastOwen, "fastowen" => RandomizeStrategy::FastOwen,
"owen" => RandomizeStrategy::Owen, "owen" => RandomizeStrategy::Owen,
_ => { _ => {
return Err(format!("{}: Unknown randomization strategy for Sobol", loc)); return Err(anyhow!("{}: Unknown randomization strategy for Sobol", loc));
} }
}; };
let sampler = Self::new(nsamp as usize, full_res, s, Some(seed as u64)); let sampler = Self::new(nsamp, full_res, s, Some(seed as u64));
arena.alloc(sampler); // arena.alloc(sampler);
Ok(sampler) Ok(sampler)
} }
} }
@ -42,7 +42,7 @@ impl CreateSampler for PaddedSobolSampler {
_full_res: Point2i, _full_res: Point2i,
loc: &FileLoc, loc: &FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Self, Error> { ) -> Result<Self> {
let options = get_options(); let options = get_options();
let nsamp = options let nsamp = options
.quick_render .quick_render
@ -56,14 +56,14 @@ impl CreateSampler for PaddedSobolSampler {
"fastowen" => RandomizeStrategy::FastOwen, "fastowen" => RandomizeStrategy::FastOwen,
"owen" => RandomizeStrategy::Owen, "owen" => RandomizeStrategy::Owen,
_ => { _ => {
return Err(format!( return Err(anyhow!(
"{}: Unknown randomization strategy for ZSobol", "{}: Unknown randomization strategy for ZSobol",
loc loc
)); ));
} }
}; };
let sampler = Self::new(nsamp as u32, s, Some(seed as u64)); let sampler = Self::new(nsamp, s, Some(seed as u64));
arena.alloc(sampler); arena.alloc(sampler);
Ok(sampler) Ok(sampler)
} }
@ -74,8 +74,8 @@ impl CreateSampler for ZSobolSampler {
params: &ParameterDictionary, params: &ParameterDictionary,
full_res: Point2i, full_res: Point2i,
loc: &FileLoc, loc: &FileLoc,
arena: &mut Arena, _arena: &mut Arena,
) -> Result<Self, Error> { ) -> Result<Self> {
let options = get_options(); let options = get_options();
let nsamp = options let nsamp = options
.quick_render .quick_render
@ -89,14 +89,14 @@ impl CreateSampler for ZSobolSampler {
"fastowen" => RandomizeStrategy::FastOwen, "fastowen" => RandomizeStrategy::FastOwen,
"owen" => RandomizeStrategy::Owen, "owen" => RandomizeStrategy::Owen,
_ => { _ => {
return Err(format!( return Err(anyhow!(
"{}: Unknown randomization strategy for ZSobol", "{}: Unknown randomization strategy for ZSobol",
loc loc
)); ));
} }
}; };
let sampler = ZSobolSampler::new(nsamp as u32, full_res, s, Some(seed as u64)); let sampler = ZSobolSampler::new(nsamp, full_res, s, Some(seed as u64));
arena.alloc(sampler); arena.alloc(sampler);
Ok(sampler) Ok(sampler)
} }

View file

@ -1,18 +1,18 @@
use crate::Arena; use crate::Arena;
use crate::core::sampler::CreateSampler; use crate::core::sampler::CreateSampler;
use crate::utils::{FileLoc, ParameterDictionary}; use crate::utils::{FileLoc, ParameterDictionary};
use anyhow::Result;
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
use shared::core::options::get_options; use shared::core::options::get_options;
use shared::core::sampler::StratifiedSampler; use shared::core::sampler::StratifiedSampler;
use std::fmt::Error;
impl CreateSampler for StratifiedSampler { impl CreateSampler for StratifiedSampler {
fn create( fn create(
params: &ParameterDictionary, params: &ParameterDictionary,
_full_res: Point2i, _full_res: Point2i,
_loc: &FileLoc, _loc: &FileLoc,
arena: &mut Arena, _arena: &mut Arena,
) -> Result<Self, Error> { ) -> Result<Self> {
let options = get_options(); let options = get_options();
let jitter = params.get_one_bool("jitter", true); let jitter = params.get_one_bool("jitter", true);
let (x_samples, y_samples) = if options.quick_render { let (x_samples, y_samples) = if options.quick_render {
@ -29,13 +29,8 @@ impl CreateSampler for StratifiedSampler {
) )
}; };
let seed = params.get_one_int("seed", options.seed); let seed = params.get_one_int("seed", options.seed);
let sampler = Self::new( let sampler = Self::new(x_samples, y_samples, Some(seed as u64), jitter);
x_samples as u32, // arena.aloc(sampler);
y_samples as u32,
Some(seed as u64),
jitter,
);
arena.aloc(sampler);
Ok(sampler) Ok(sampler)
} }

View file

@ -1,4 +1,4 @@
use crate::core::image::Image; use crate::core::image::{Image, ImageIO};
use crate::core::shape::{ALL_BILINEAR_MESHES, CreateShape}; use crate::core::shape::{ALL_BILINEAR_MESHES, CreateShape};
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::shapes::mesh::BilinearPatchMesh; use crate::shapes::mesh::BilinearPatchMesh;
@ -93,15 +93,11 @@ impl CreateShape for BilinearPatchShape {
"\"emissionfilename\" is currently ignored for bilinear patches if \"uv\" coordinates have been provided--sorry!" "\"emissionfilename\" is currently ignored for bilinear patches if \"uv\" coordinates have been provided--sorry!"
); );
} else { } else {
match Image::read(&filename) { match Image::read(Path::new(filename), None) {
Ok(mut im) => { Ok(mut im) => {
im.flip_y(); let img = im.image;
img.flip_y();
let domain = Bounds2f::new(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0)); image_dist = Some(PiecewiseConstant2D::from_image(&img));
let distribution = im.get_sampling_distribution(); // Assuming this returns Array2D<Float>
image_dist =
Some(PiecewiseConstant2D::new_with_bounds(distribution, domain));
} }
Err(e) => { Err(e) => {
warn!("Failed to load emission image \"{}\": {}", filename, e); warn!("Failed to load emission image \"{}\": {}", filename, e);
@ -111,7 +107,7 @@ impl CreateShape for BilinearPatchShape {
} }
let host = BilinearPatchMesh::new( let host = BilinearPatchMesh::new(
render_from_object, &render_from_object,
reverse_orientation, reverse_orientation,
vertex_indices, vertex_indices,
p, p,
@ -121,15 +117,15 @@ impl CreateShape for BilinearPatchShape {
); );
let host_arc = Arc::new(host); let host_arc = Arc::new(host);
let mut global_store = ALL_BILINEAR_MESHES.lock().unwrap(); let mut global_store = ALL_BILINEAR_MESHES.lock();
let mesh_index = global_store.len() as u32; let mesh_index = global_store.len() as u32;
global_store.push(host_arc.clone()); global_store.push(host_arc.clone());
drop(global_store); drop(global_store);
let n_patches = host_arc.view.n_patches; let n_patches = host_arc.device.n_patches;
let mesh_ptr = &host_arc.view as *const _; let mesh_ptr = Ptr::from(&host_arc.device);
let mut shapes = Vec::with_capacity(n_patches as usize); let mut shapes = Vec::with_capacity(n_patches as usize);
for i in 0..n_patches { for i in 0..n_patches {
shapes.push(Shape::Bilinear(BilinearPatchShape { shapes.push(Shape::BilinearPatch(BilinearPatchShape {
mesh: mesh_ptr, mesh: mesh_ptr,
blp_index: i, blp_index: i,
area: 0.0, area: 0.0,

View file

@ -61,9 +61,9 @@ impl CreateShape for CurveShape {
object_from_render: Transform, object_from_render: Transform,
reverse_orientation: bool, reverse_orientation: bool,
parameters: ParameterDictionary, parameters: ParameterDictionary,
float_textures: HashMap<String, FloatTexture>, _float_textures: HashMap<String, FloatTexture>,
loc: FileLoc, _loc: FileLoc,
arena: &mut Arena, _arena: &mut Arena,
) -> Result<Vec<Shape>, String> { ) -> Result<Vec<Shape>, String> {
let width = parameters.get_one_float("width", 1.0); let width = parameters.get_one_float("width", 1.0);
let width0 = parameters.get_one_float("width0", width); let width0 = parameters.get_one_float("width0", width);
@ -85,7 +85,7 @@ impl CreateShape for CurveShape {
)); ));
} }
let cp = parameters.get_point3f_array("P").unwrap_or_default(); let cp = parameters.get_point3f_array("P");
let n_segments; let n_segments;
if basis == "bezier" { if basis == "bezier" {
@ -123,7 +123,7 @@ impl CreateShape for CurveShape {
} }
}; };
let mut n = parameters.get_normal3f_array("N").unwrap_or_default(); let mut n = parameters.get_normal3f_array("N");
if !n.is_empty() { if !n.is_empty() {
if curve_type != CurveType::Ribbon { if curve_type != CurveType::Ribbon {
warn!("Curve normals are only used with \"ribbon\" type curves. Discarding."); warn!("Curve normals are only used with \"ribbon\" type curves. Discarding.");
@ -149,7 +149,7 @@ impl CreateShape for CurveShape {
parameters.get_one_int("splitdepth", 3) parameters.get_one_int("splitdepth", 3)
}; };
let mut curves: Vec<Arc<Shape>> = Vec::new(); let mut curves: Vec<Shape> = Vec::new();
let mut cp_offset = 0; let mut cp_offset = 0;
for seg in 0..n_segments { for seg in 0..n_segments {
@ -194,13 +194,13 @@ impl CreateShape for CurveShape {
w0, w0,
w1, w1,
curve_type, curve_type,
seg_normals, seg_normals.expect("Could not determine normals to curve segments"),
split_depth, split_depth.try_into().unwrap(),
); );
curves.extend(new_curves); curves.extend(new_curves);
} }
arena.alloc(curves); // arena.alloc(curves);
Ok(curves) Ok(curves)
} }
} }

View file

@ -3,9 +3,9 @@ use crate::utils::sampling::PiecewiseConstant2D;
use anyhow::{Context, Result as AnyResult, bail}; use anyhow::{Context, Result as AnyResult, bail};
use ply_rs::parser::Parser; use ply_rs::parser::Parser;
use ply_rs::ply::{DefaultElement, Property}; use ply_rs::ply::{DefaultElement, Property};
use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f}; use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f, VectorLike};
use shared::utils::Transform;
use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh}; use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh};
use shared::utils::{Ptr, Transform};
use std::fs::File; use std::fs::File;
use std::ops::Deref; use std::ops::Deref;
use std::path::Path; use std::path::Path;
@ -17,38 +17,38 @@ pub struct TriQuadMesh {
pub n: Vec<Normal3f>, pub n: Vec<Normal3f>,
pub uv: Vec<Point2f>, pub uv: Vec<Point2f>,
pub face_indices: Vec<i32>, pub face_indices: Vec<i32>,
pub tri_indices: Vec<u32>, pub tri_indices: Vec<i32>,
pub quad_indices: Vec<u32>, pub quad_indices: Vec<i32>,
} }
#[derive(Debug)] #[derive(Debug)]
struct TriangleMeshStorage { struct TriangleMeshStorage {
vertex_indices: Vec<u32>, pub p: Vec<Point3f>,
p: Vec<Point3f>, pub n: Vec<Normal3f>,
n: Vec<Normal3f>, pub s: Vec<Vector3f>,
s: Vec<Vector3f>, pub uv: Vec<Point2f>,
uv: Vec<Point2f>, pub vertex_indices: Vec<i32>,
face_indices: Vec<i32>, pub face_indices: Vec<i32>,
} }
#[derive(Debug)] #[derive(Debug)]
struct BilinearMeshStorage { struct BilinearMeshStorage {
vertex_indices: Vec<u32>, pub vertex_indices: Vec<i32>,
p: Vec<Point3f>, pub p: Vec<Point3f>,
n: Vec<Normal3f>, pub n: Vec<Normal3f>,
uv: Vec<Point2f>, pub uv: Vec<Point2f>,
image_distribution: Option<PiecewiseConstant2D>, pub image_distribution: Option<PiecewiseConstant2D>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct TriangleMesh { pub struct TriangleMesh {
_storage: Box<TriangleMeshStorage>, pub storage: Box<TriangleMeshStorage>,
pub device: DeviceTriangleMesh, pub device: DeviceTriangleMesh,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct BilinearPatchMesh { pub struct BilinearPatchMesh {
_storage: Box<BilinearMeshStorage>, pub storage: Box<BilinearMeshStorage>,
pub device: DeviceBilinearPatchMesh, pub device: DeviceBilinearPatchMesh,
} }
@ -72,7 +72,7 @@ impl TriangleMesh {
pub fn new( pub fn new(
render_from_object: &Transform, render_from_object: &Transform,
reverse_orientation: bool, reverse_orientation: bool,
vertex_indices: Vec<u32>, vertex_indices: Vec<i32>,
mut p: Vec<Point3f>, mut p: Vec<Point3f>,
mut n: Vec<Normal3f>, mut n: Vec<Normal3f>,
mut s: Vec<Vector3f>, mut s: Vec<Vector3f>,
@ -84,14 +84,14 @@ impl TriangleMesh {
// Transform positions to render space // Transform positions to render space
for pt in p.iter_mut() { for pt in p.iter_mut() {
*pt = render_from_object.apply_point(*pt); *pt = render_from_object.apply_to_point(*pt);
} }
// Transform and optionally flip normals // Transform and optionally flip normals
if !n.is_empty() { if !n.is_empty() {
assert_eq!(n_vertices, n.len(), "Normal count mismatch"); assert_eq!(n_vertices, n.len(), "Normal count mismatch");
for nn in n.iter_mut() { for nn in n.iter_mut() {
*nn = render_from_object.apply_normal(*nn); *nn = render_from_object.apply_to_normal(*nn);
if reverse_orientation { if reverse_orientation {
*nn = -*nn; *nn = -*nn;
} }
@ -102,7 +102,7 @@ impl TriangleMesh {
if !s.is_empty() { if !s.is_empty() {
assert_eq!(n_vertices, s.len(), "Tangent count mismatch"); assert_eq!(n_vertices, s.len(), "Tangent count mismatch");
for ss in s.iter_mut() { for ss in s.iter_mut() {
*ss = render_from_object.apply_vector(*ss); *ss = render_from_object.apply_to_vector(*ss);
} }
} }
@ -131,52 +131,49 @@ impl TriangleMesh {
let device = DeviceTriangleMesh { let device = DeviceTriangleMesh {
n_triangles: n_triangles as u32, n_triangles: n_triangles as u32,
n_vertices: n_vertices as u32, n_vertices: n_vertices as u32,
vertex_indices: storage.vertex_indices.as_ptr(), vertex_indices: storage.vertex_indices.as_ptr().into(),
p: storage.p.as_ptr(), p: storage.p.as_ptr().into(),
n: if storage.n.is_empty() { n: if storage.n.is_empty() {
std::ptr::null() Ptr::null()
} else { } else {
storage.n.as_ptr() storage.n.as_ptr().into()
}, },
s: if storage.s.is_empty() { s: if storage.s.is_empty() {
std::ptr::null() Ptr::null()
} else { } else {
storage.s.as_ptr() storage.s.as_ptr().into()
}, },
uv: if storage.uv.is_empty() { uv: if storage.uv.is_empty() {
std::ptr::null() Ptr::null()
} else { } else {
storage.uv.as_ptr() storage.uv.as_ptr().into()
}, },
face_indices: if storage.face_indices.is_empty() { face_indices: if storage.face_indices.is_empty() {
std::ptr::null() Ptr::null()
} else { } else {
storage.face_indices.as_ptr() storage.face_indices.as_ptr().into()
}, },
reverse_orientation, reverse_orientation,
transform_swaps_handedness, transform_swaps_handedness,
}; };
Self { Self { storage, device }
_storage: storage,
device,
}
} }
pub fn positions(&self) -> &[Point3f] { pub fn positions(&self) -> &[Point3f] {
&self._storage.p &self.storage.p
} }
pub fn indices(&self) -> &[u32] { pub fn indices(&self) -> &[i32] {
&self._storage.vertex_indices &self.storage.vertex_indices
} }
pub fn normals(&self) -> &[Normal3f] { pub fn normals(&self) -> &[Normal3f] {
&self._storage.n &self.storage.n
} }
pub fn uvs(&self) -> &[Point2f] { pub fn uvs(&self) -> &[Point2f] {
&self._storage.uv &self.storage.uv
} }
} }
@ -184,7 +181,7 @@ impl BilinearPatchMesh {
pub fn new( pub fn new(
render_from_object: &Transform, render_from_object: &Transform,
reverse_orientation: bool, reverse_orientation: bool,
vertex_indices: Vec<u32>, vertex_indices: Vec<ui2>,
mut p: Vec<Point3f>, mut p: Vec<Point3f>,
mut n: Vec<Normal3f>, mut n: Vec<Normal3f>,
uv: Vec<Point2f>, uv: Vec<Point2f>,
@ -194,13 +191,13 @@ impl BilinearPatchMesh {
let n_vertices = p.len(); let n_vertices = p.len();
for pt in p.iter_mut() { for pt in p.iter_mut() {
*pt = render_from_object.apply_point(*pt); *pt = render_from_object.apply_to_point(*pt);
} }
if !n.is_empty() { if !n.is_empty() {
assert_eq!(n_vertices, n.len(), "Normal count mismatch"); assert_eq!(n_vertices, n.len(), "Normal count mismatch");
for nn in n.iter_mut() { for nn in n.iter_mut() {
*nn = render_from_object.apply_normal(*nn); *nn = render_from_object.apply_to_normal(*nn);
if reverse_orientation { if reverse_orientation {
*nn = -*nn; *nn = -*nn;
} }
@ -224,31 +221,28 @@ impl BilinearPatchMesh {
let device = DeviceBilinearPatchMesh { let device = DeviceBilinearPatchMesh {
n_patches: n_patches as u32, n_patches: n_patches as u32,
n_vertices: n_vertices as u32, n_vertices: n_vertices as u32,
vertex_indices: storage.vertex_indices.as_ptr(), vertex_indices: storage.vertex_indices.as_ptr().into(),
p: storage.p.as_ptr(), p: storage.p.as_ptr().into(),
n: if storage.n.is_empty() { n: if storage.n.is_empty() {
std::ptr::null() Ptr::null()
} else { } else {
storage.n.as_ptr() storage.n.as_ptr().into()
}, },
uv: if storage.uv.is_empty() { uv: if storage.uv.is_empty() {
std::ptr::null() Ptr::null()
} else { } else {
storage.uv.as_ptr() storage.uv.as_ptr().into()
}, },
reverse_orientation, reverse_orientation,
transform_swaps_handedness, transform_swaps_handedness,
image_distribution: storage image_distribution: storage
.image_distribution .image_distribution
.as_ref() .as_ref()
.map(|d| &d.device as *const _) .map(|d| Ptr::from(&d.device))
.unwrap_or(std::ptr::null()), .unwrap_or(Ptr::null()),
}; };
Self { Self { storage, device }
_storage: storage,
device,
}
} }
} }
@ -374,8 +368,8 @@ impl TriQuadMesh {
if let Ok(indices) = get_list_uint(f_elem, "vertex_indices") { if let Ok(indices) = get_list_uint(f_elem, "vertex_indices") {
match indices.len() { match indices.len() {
3 => mesh.tri_indices.extend_from_slice(&indices), 3 => mesh.tri_indices.extend(indices.iter().map(|&i| i as i32)),
4 => mesh.quad_indices.extend_from_slice(&indices), 4 => mesh.quad_indices.extend(indices.iter().map(|&i| i as i32)),
n => { n => {
log::warn!( log::warn!(
"{}: Skipping face with {} vertices (only 3 or 4 supported)", "{}: Skipping face with {} vertices (only 3 or 4 supported)",
@ -393,7 +387,7 @@ impl TriQuadMesh {
} }
// Validate indices // Validate indices
let vertex_count = mesh.p.len() as u32; let vertex_count = mesh.p.len() as i32;
for &idx in &mesh.tri_indices { for &idx in &mesh.tri_indices {
if idx >= vertex_count { if idx >= vertex_count {
bail!( bail!(
@ -457,7 +451,7 @@ impl TriQuadMesh {
let v10 = p1 - p0; let v10 = p1 - p0;
let v20 = p2 - p0; let v20 = p2 - p0;
let face_normal = v10.cross(&v20); let face_normal = v10.cross(v20);
// Accumulate (will normalize later) // Accumulate (will normalize later)
self.n[i0] = self.n[i0] + Normal3f::from(face_normal); self.n[i0] = self.n[i0] + Normal3f::from(face_normal);
@ -475,7 +469,7 @@ impl TriQuadMesh {
let v10 = p1 - p0; let v10 = p1 - p0;
let v20 = p2 - p0; let v20 = p2 - p0;
let face_normal = v10.cross(&v20); let face_normal = v10.cross(v20);
for &idx in &indices { for &idx in &indices {
self.n[idx] = self.n[idx] + Normal3f::from(face_normal); self.n[idx] = self.n[idx] + Normal3f::from(face_normal);
@ -484,7 +478,7 @@ impl TriQuadMesh {
// Normalize all normals // Normalize all normals
for normal in &mut self.n { for normal in &mut self.n {
let len_sq = normal.length_squared(); let len_sq = normal.norm_squared();
if len_sq > 0.0 { if len_sq > 0.0 {
*normal = *normal / len_sq.sqrt(); *normal = *normal / len_sq.sqrt();
} }

View file

@ -73,7 +73,7 @@ impl CreateShape for TriangleShape {
} }
} }
let mut face_indices = parameters.get_int_array("faceIndices").unwrap_or_default(); let mut face_indices = parameters.get_int_array("faceIndices");
let n_triangles = vertex_indices.len() / 3; let n_triangles = vertex_indices.len() / 3;
if !face_indices.is_empty() && face_indices.len() != n_triangles { if !face_indices.is_empty() && face_indices.len() != n_triangles {
@ -86,28 +86,28 @@ impl CreateShape for TriangleShape {
} }
let host = TriangleMesh::new( let host = TriangleMesh::new(
render_from_object, &render_from_object,
reverse_orientation, reverse_orientation,
vertex_indices, vertex_indices,
p, p,
s,
n, n,
s,
uvs, uvs,
face_indices, face_indices,
); );
let host_arc = Arc::new(host); let host_arc = Arc::new(host);
let mut global_store = ALL_TRIANGLE_MESHES.lock().unwrap(); let mut global_store = ALL_TRIANGLE_MESHES.lock();
let mesh_index = global_store.len() as u32; let mesh_index = global_store.len() as u32;
global_store.push(host_arc.clone()); global_store.push(host_arc.clone());
drop(global_store); drop(global_store);
let n_patches = host_arc.view.n_patches; let n_patches = host_arc.device.n_triangles;
let mesh_ptr = &host_arc.view as *const _; let mesh_ptr = Ptr::from(&host_arc.device);
let mut shapes = Vec::with_capacity(n_patches as usize); let mut shapes = Vec::with_capacity(n_patches as usize);
for i in 0..n_patches { for i in 0..n_patches {
shapes.push(Shape::Triangle(TriangleShape { shapes.push(Shape::Triangle(TriangleShape {
mesh: mesh_ptr, mesh: mesh_ptr,
tri_index: i, tri_index: i as i32,
})); }));
} }

View file

@ -5,11 +5,19 @@ use shared::spectra::RGBColorSpace;
use shared::utils::math::SquareMatrix; use shared::utils::math::SquareMatrix;
use shared::utils::ptr::Ptr; use shared::utils::ptr::Ptr;
#[derive(Clone, Debug)]
pub struct RGBColorSpaceData { pub struct RGBColorSpaceData {
_illuminant: DenselySampledSpectrumBuffer, _illuminant: DenselySampledSpectrumBuffer,
pub view: RGBColorSpace, pub view: RGBColorSpace,
} }
impl std::ops::Deref for RGBColorSpaceData {
type Target = RGBColorSpace;
fn deref(&self) -> &Self::Target {
&self.view
}
}
impl RGBColorSpaceData { impl RGBColorSpaceData {
pub fn new( pub fn new(
r: Point2f, r: Point2f,
@ -18,7 +26,7 @@ impl RGBColorSpaceData {
illuminant: DenselySampledSpectrumBuffer, illuminant: DenselySampledSpectrumBuffer,
rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>, rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
) -> Self { ) -> Self {
let w_xyz: XYZ = illuminant.to_xyz(); let w_xyz: XYZ = Spectrum::Dense(illuminant).to_xyz();
let w = w_xyz.xy(); let w = w_xyz.xy();
let r_xyz = XYZ::from_xyy(r, Some(1.0)); let r_xyz = XYZ::from_xyy(r, Some(1.0));
let g_xyz = XYZ::from_xyy(g, Some(1.0)); let g_xyz = XYZ::from_xyy(g, Some(1.0));
@ -29,7 +37,8 @@ impl RGBColorSpaceData {
[r_xyz.z(), g_xyz.z(), b_xyz.z()], [r_xyz.z(), g_xyz.z(), b_xyz.z()],
]; ];
let rgb = SquareMatrix::new(rgb_values); let rgb = SquareMatrix::new(rgb_values);
let c: RGB = rgb.inverse()? * w_xyz; let c_vec: Vector3f = rgb.inverse().unwrap() * w_xyz;
let c = RGB::from(c);
let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]); let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]);
let rgb_from_xyz = xyz_from_rgb let rgb_from_xyz = xyz_from_rgb
.inverse() .inverse()
@ -39,7 +48,7 @@ impl RGBColorSpaceData {
g, g,
b, b,
w, w,
illuminant: illuminant.view, illuminant: *illuminant,
xyz_from_rgb, xyz_from_rgb,
rgb_from_xyz, rgb_from_xyz,
rgb_to_spectrum_table, rgb_to_spectrum_table,

View file

@ -6,10 +6,10 @@ use shared::spectra::cie::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::LazyLock; use std::sync::LazyLock;
pub fn create_cie_buffer(data: &[Float]) -> Spectrum { pub fn create_cie_buffer(data: &[Float]) -> DenselySampledSpectrumBuffer {
let buffer = PiecewiseLinearSpectrum::from_interleaved(data, false); let buffer = PiecewiseLinearSpectrumBuffer::from_interleaved(data, false);
let spec = Spectrum::Piecewise(buffer.view); let spec = Spectrum::Piecewise(buffer.device);
DenselySampledSpectrumBuffer::from_spectrum(spec) DenselySampledSpectrumBuffer::from_spectrum(&spec)
} }
pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(|| { pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(|| {
@ -18,7 +18,8 @@ pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(||
macro_rules! add { macro_rules! add {
($name:expr, $data:expr, $norm:expr) => { ($name:expr, $data:expr, $norm:expr) => {
let buffer = PiecewiseLinearSpectrumBuffer::from_interleaved($data, $norm); let buffer = PiecewiseLinearSpectrumBuffer::from_interleaved($data, $norm);
m.insert($name.to_string(), buffer); let spectrum = Spectrum::Piecewise(*buffer);
m.insert($name.to_string(), spectrum);
}; };
} }
@ -153,6 +154,5 @@ pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(||
}); });
pub fn get_named_spectrum(name: &str) -> Option<Spectrum> { pub fn get_named_spectrum(name: &str) -> Option<Spectrum> {
let buffer = NAMED_SPECTRA.get(name)?; NAMED_SPECTRA.get(name).copied()
Some(Spectrum::PiecewiseLinear(buffer.view))
} }

View file

@ -1,12 +1,12 @@
use shared::Float; use shared::Float;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::{Spectrum, SpectrumTrait};
use shared::spectra::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, N_CIES}; use shared::spectra::cie::{CIE_S_LAMBDA, CIE_S0, CIE_S1, CIE_S2, N_CIES};
use shared::spectra::{ use shared::spectra::{
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, PiecewiseLinearSpectrum, BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, PiecewiseLinearSpectrum,
SampledSpectrum, SampledWavelengths,
}; };
use shared::utils::math::square; use shared::utils::math::square;
#[derive(Clone, Debug)]
pub struct DenselySampledSpectrumBuffer { pub struct DenselySampledSpectrumBuffer {
pub device: DenselySampledSpectrum, pub device: DenselySampledSpectrum,
pub _storage: Vec<Float>, pub _storage: Vec<Float>,
@ -24,7 +24,7 @@ impl DenselySampledSpectrumBuffer {
let view = DenselySampledSpectrum { let view = DenselySampledSpectrum {
lambda_min, lambda_min,
lambda_max, lambda_max,
values: values.as_ptr(), values: values.as_ptr().into(),
}; };
Self { Self {
device: view, device: view,
@ -90,8 +90,8 @@ impl DenselySampledSpectrumBuffer {
.collect(); .collect();
let temp_pls = PiecewiseLinearSpectrum { let temp_pls = PiecewiseLinearSpectrum {
lambdas: CIE_S_LAMBDA.as_ptr(), lambdas: CIE_S_LAMBDA.as_ptr().into(),
values: coarse_values.as_ptr(), values: coarse_values.as_ptr().into(),
count: N_CIES as u32, count: N_CIES as u32,
}; };
@ -100,9 +100,9 @@ impl DenselySampledSpectrumBuffer {
pub fn as_view(&self) -> DenselySampledSpectrum { pub fn as_view(&self) -> DenselySampledSpectrum {
DenselySampledSpectrum { DenselySampledSpectrum {
lambda_min: self.lambda_min, lambda_min: self.device.lambda_min,
lambda_max: self.lambda_max, lambda_max: self.device.lambda_max,
values: self.samples.as_ptr(), values: self._storage.as_ptr().into(),
} }
} }

View file

@ -1,10 +1,11 @@
use crate::globals::{ACES_TABLE, DCI_P3_TABLE, REC2020_TABLE, SRGB_TABLE};
use crate::spectra::colorspace::RGBColorSpaceData; use crate::spectra::colorspace::RGBColorSpaceData;
use shared::core::color::RGBToSpectrumTable; use shared::core::color::RGBToSpectrumTable;
use shared::core::geometry::Point2f; use shared::core::geometry::Point2f;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::core::spectrum::StandardSpectra; use shared::core::spectrum::StandardSpectra;
use shared::spectra::DeviceStandardColorSpaces;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::spectra::StandardColorSpaces;
use shared::spectra::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z}; use shared::spectra::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z};
use std::sync::Arc; use std::sync::Arc;
use std::sync::LazyLock; use std::sync::LazyLock;
@ -14,9 +15,16 @@ pub mod data;
pub mod dense; pub mod dense;
pub mod piecewise; pub mod piecewise;
// pub use data;
pub use dense::DenselySampledSpectrumBuffer; pub use dense::DenselySampledSpectrumBuffer;
fn get_d65_illuminant_buffer() -> DenselySampledSpectrumBuffer {
DenselySampledSpectrumBuffer::new(
CIE_D65_DATA.device.lambda_min,
CIE_D65_DATA.device.lambda_max,
CIE_D65_DATA._storage.clone(),
)
}
static CIE_X_DATA: LazyLock<DenselySampledSpectrumBuffer> = static CIE_X_DATA: LazyLock<DenselySampledSpectrumBuffer> =
LazyLock::new(|| data::create_cie_buffer(&CIE_X)); LazyLock::new(|| data::create_cie_buffer(&CIE_X));
static CIE_Y_DATA: LazyLock<DenselySampledSpectrumBuffer> = static CIE_Y_DATA: LazyLock<DenselySampledSpectrumBuffer> =
@ -27,92 +35,86 @@ static CIE_D65_DATA: LazyLock<DenselySampledSpectrumBuffer> =
LazyLock::new(|| data::create_cie_buffer(&CIE_D65)); LazyLock::new(|| data::create_cie_buffer(&CIE_D65));
pub fn cie_x() -> Spectrum { pub fn cie_x() -> Spectrum {
Spectrum::DenselySampled(CIE_X_DATA.view) Spectrum::Dense(CIE_X_DATA.device)
} }
pub fn cie_y() -> Spectrum { pub fn cie_y() -> Spectrum {
Spectrum::DenselySampled(CIE_Y_DATA.view) Spectrum::Dense(CIE_Y_DATA.device)
} }
pub fn cie_z() -> Spectrum { pub fn cie_z() -> Spectrum {
Spectrum::DenselySampled(CIE_Z_DATA.view) Spectrum::Dense(CIE_Z_DATA.device)
} }
pub fn cie_d65() -> Spectrum { pub fn cie_d65() -> Spectrum {
Spectrum::DenselySampled(CIE_D65_DATA.view) Spectrum::Dense(CIE_D65_DATA.device)
} }
pub fn get_spectra_context() -> StandardSpectra { pub fn get_spectra_context() -> StandardSpectra {
StandardSpectra { StandardSpectra {
x: CIE_X_DATA.view, x: CIE_X_DATA.device,
y: CIE_Y_DATA.view, y: CIE_Y_DATA.device,
z: CIE_Z_DATA.view, z: CIE_Z_DATA.device,
d65: CIE_D65_DATA.view, d65: CIE_D65_DATA.device,
} }
} }
pub static SRGB: LazyLock<RGBColorSpaceData> = LazyLock::new(|| { pub static SRGB: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
let illum = DenselySampledSpectrumBuffer::new( let illum = get_d65_illuminant_buffer();
CIE_D65_DATA.view.lambda_min, let r = Point2f::new(0.64, 0.33);
CIE_D65_DATA.view.lambda_max, let g = Point2f::new(0.3, 0.6);
CIE_D65_DATA._storage.clone(), let b = Point2f::new(0.15, 0.06);
); let table = Ptr::from(SRGB_TABLE.clone());
RGBColorSpaceData::new( Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr))
Point2f::new(0.64, 0.33),
Point2f::new(0.3, 0.6),
Point2f::new(0.15, 0.06),
illum,
RGBToSpectrumTable::srgb(),
)
}); });
pub static DCI_P3: LazyLock<RGBColorSpaceData> = LazyLock::new(|| { pub static DCI_P3: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
let illum = DenselySampledSpectrumBuffer::new( let illum = get_d65_illuminant_buffer();
CIE_D65_DATA.view.lambda_min,
CIE_D65_DATA.view.lambda_max,
CIE_D65_DATA._storage.clone(),
);
let r = Point2f::new(0.680, 0.320); let r = Point2f::new(0.680, 0.320);
let g = Point2f::new(0.265, 0.690); let g = Point2f::new(0.265, 0.690);
let b = Point2f::new(0.150, 0.060); let b = Point2f::new(0.150, 0.060);
RGBColorSpaceData::new(r, g, b, illum, RGBToSpectrumTable) let table_ptr = Ptr::from(DCI_P3_TABLE.clone());
Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr))
}); });
pub static REC2020: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| { pub static REC2020: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
let illum = DenselySampledSpectrumBuffer::new( let illum = get_d65_illuminant_buffer();
CIE_D65_DATA.view.lambda_min,
CIE_D65_DATA.view.lambda_max,
CIE_D65_DATA._storage.clone(),
);
let r = Point2f::new(0.708, 0.292); let r = Point2f::new(0.708, 0.292);
let g = Point2f::new(0.170, 0.797); let g = Point2f::new(0.170, 0.797);
let b = Point2f::new(0.131, 0.046); let b = Point2f::new(0.131, 0.046);
let table = RGBToSpectrumTable::rec2020(); let table_ptr = Ptr::from(REC2020_TABLE.clone());
RGBColorSpace::new(r, g, b, illum, table) Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr))
}); });
pub static ACES: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| { pub static ACES: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
let illum = get_d65_illuminant_buffer();
let r = Point2f::new(0.7347, 0.2653); let r = Point2f::new(0.7347, 0.2653);
let g = Point2f::new(0.0000, 1.0000); let g = Point2f::new(0.0000, 1.0000);
let b = Point2f::new(0.0001, -0.0770); let b = Point2f::new(0.0001, -0.0770);
let illuminant = Spectrum::std_illuminant_d65();
let table = RGBToSpectrumTable::aces2065_1(); let table_ptr = Ptr::from(ACES_TABLE.clone());
RGBColorSpaceData::new(r, g, b, illuminant, table) Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr))
}); });
#[derive(Debug, Clone)]
pub struct StandardColorSpaces {
pub srgb: Arc<RGBColorSpaceData>,
pub dci_p3: Arc<RGBColorSpaceData>,
pub rec2020: Arc<RGBColorSpaceData>,
pub aces2065_1: Arc<RGBColorSpaceData>,
}
pub fn get_colorspace_context() -> StandardColorSpaces { pub fn get_colorspace_context() -> StandardColorSpaces {
StandardColorSpaces { StandardColorSpaces {
srgb: SRGB, srgb: SRGB.clone().into(),
dci_p3: DCI_P3, dci_p3: DCI_P3.clone().into(),
rec2020: REC2020, rec2020: REC2020.clone().into(),
aces2065_1: ACES, aces2065_1: ACES.clone().into(),
} }
} }

View file

@ -1,11 +1,11 @@
use crate::utils::read_float_file; use crate::utils::read_float_file;
use shared::Float; use shared::Float;
use shared::spectra::PiecewiseLinearSpectrum; use shared::spectra::PiecewiseLinearSpectrum;
use std::cmp::Ordering;
use std::ops::Deref; use std::ops::Deref;
use std::sync::atomic::Ordering;
pub struct PiecewiseLinearSpectrumBuffer { pub struct PiecewiseLinearSpectrumBuffer {
pub view: PiecewiseLinearSpectrum, pub device: PiecewiseLinearSpectrum,
_lambdas: Vec<Float>, _lambdas: Vec<Float>,
_values: Vec<Float>, _values: Vec<Float>,
} }
@ -13,7 +13,7 @@ pub struct PiecewiseLinearSpectrumBuffer {
impl Deref for PiecewiseLinearSpectrumBuffer { impl Deref for PiecewiseLinearSpectrumBuffer {
type Target = PiecewiseLinearSpectrum; type Target = PiecewiseLinearSpectrum;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.view &self.device
} }
} }
@ -21,12 +21,12 @@ impl PiecewiseLinearSpectrumBuffer {
pub fn new(lambdas: Vec<Float>, values: Vec<Float>) -> Self { pub fn new(lambdas: Vec<Float>, values: Vec<Float>) -> Self {
assert_eq!(lambdas.len(), values.len()); assert_eq!(lambdas.len(), values.len());
let view = PiecewiseLinearSpectrum { let view = PiecewiseLinearSpectrum {
lambdas: lambdas.as_ptr(), lambdas: lambdas.as_ptr().into(),
values: values.as_ptr(), values: values.as_ptr().into(),
count: lambdas.len() as u32, count: lambdas.len() as u32,
}; };
Self { Self {
view, device: view,
_lambdas: lambdas, _lambdas: lambdas,
_values: values, _values: values,
} }

View file

@ -6,6 +6,7 @@ use shared::core::color::ColorEncoding;
use shared::core::color::RGB; use shared::core::color::RGB;
use shared::core::geometry::Vector2f; use shared::core::geometry::Vector2f;
use shared::core::image::WrapMode; use shared::core::image::WrapMode;
use shared::core::spectrum::SpectrumTrait;
use shared::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D}; use shared::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D};
use shared::spectra::{ use shared::spectra::{
RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum, RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
@ -129,7 +130,7 @@ impl SpectrumTextureTrait for SpectrumImageTexture {
let dst0 = Vector2f::new(c.dsdx, c.dtdx); let dst0 = Vector2f::new(c.dsdx, c.dtdx);
let dst1 = Vector2f::new(c.dsdy, c.dtdy); let dst1 = Vector2f::new(c.dsdy, c.dtdy);
let rgb_unclamp = self.base.scale * self.base.mipmap.filter::<RGB>(c.st, dst0, dst1); let rgb_unclamp = self.base.scale * self.base.mipmap.filter::<RGB>(c.st, dst0, dst1);
let rgb = RGB::clamp_zero(rgb_unclamp); let rgb = RGB::clamp_zero(&rgb_unclamp);
if let Some(cs) = self.base.mipmap.get_rgb_colorspace() { if let Some(cs) = self.base.mipmap.get_rgb_colorspace() {
match self.spectrum_type { match self.spectrum_type {
SpectrumType::Unbounded => { SpectrumType::Unbounded => {

View file

@ -2,7 +2,7 @@ use crate::core::texture::FloatTexture;
use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait}; use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait};
use crate::utils::{Arena, FileLoc, TextureParameterDictionary}; use crate::utils::{Arena, FileLoc, TextureParameterDictionary};
use shared::Float; use shared::Float;
use shared::core::geometry::Vector3f; use shared::core::geometry::{Vector3f, VectorLike};
use shared::core::texture::TextureEvalContext; use shared::core::texture::TextureEvalContext;
use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::spectra::{SampledSpectrum, SampledWavelengths};
use shared::utils::Transform; use shared::utils::Transform;
@ -28,12 +28,13 @@ impl FloatMixTexture {
_render_from_texture: &Transform, _render_from_texture: &Transform,
params: &TextureParameterDictionary, params: &TextureParameterDictionary,
_loc: &FileLoc, _loc: &FileLoc,
arena: &mut Arena, _arena: &mut Arena,
) -> Self { ) -> FloatTexture {
let tex1 = params.get_float_texture("tex1", 0.); let tex1 = params.get_float_texture("tex1", 0.);
let tex2 = params.get_float_texture("tex2", 1.); let tex2 = params.get_float_texture("tex2", 1.);
let amount = params.get_float_texture("amount", 0.5); let amount = params.get_float_texture("amount", 0.5);
arena.alloc(Self::new(tex1, tex2, amount)) // arena.alloc(Self::new(tex1, tex2, amount));
FloatTexture::Mix(Self::new(tex1, tex2, amount))
} }
} }
@ -68,13 +69,14 @@ impl FloatDirectionMixTexture {
render_from_texture: &Transform, render_from_texture: &Transform,
params: &TextureParameterDictionary, params: &TextureParameterDictionary,
_loc: &FileLoc, _loc: &FileLoc,
arena: &mut Arena, _arena: &mut Arena,
) -> Self { ) -> FloatTexture {
let dir_raw = params.get_one_vector3f("dir", Vector3f::new(0., 1., 0.)); let dir_raw = params.get_one_vector3f("dir", Vector3f::new(0., 1., 0.));
let dir = render_from_texture.apply_to_vector(dir_raw).normalize(); let dir = render_from_texture.apply_to_vector(dir_raw).normalize();
let tex1 = params.get_float_texture("tex1", 0.); let tex1 = params.get_float_texture("tex1", 0.);
let tex2 = params.get_float_texture("tex2", 1.); let tex2 = params.get_float_texture("tex2", 1.);
arena.alloc(Self::new(tex1, tex2, dir)) // arena.alloc(Self::new(tex1, tex2, dir))
FloatTexture::DirectionMix(Self::new(tex1, tex2, dir))
} }
} }
@ -85,7 +87,12 @@ impl FloatTextureTrait for FloatDirectionMixTexture {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SpectrumMixTexture; pub struct SpectrumMixTexture {
pub tex1: Arc<SpectrumTexture>,
pub tex2: Arc<SpectrumTexture>,
pub amount: Arc<FloatTexture>,
}
impl SpectrumTextureTrait for SpectrumMixTexture { impl SpectrumTextureTrait for SpectrumMixTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!() todo!()
@ -93,7 +100,12 @@ impl SpectrumTextureTrait for SpectrumMixTexture {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SpectrumDirectionMixTexture; pub struct SpectrumDirectionMixTexture {
pub tex1: Arc<SpectrumTexture>,
pub tex2: Arc<SpectrumTexture>,
pub dir: Vector3f,
}
impl SpectrumTextureTrait for SpectrumDirectionMixTexture { impl SpectrumTextureTrait for SpectrumDirectionMixTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
todo!() todo!()

View file

@ -1,30 +1,14 @@
// use crate::utils::{Arena, FileLoc, TextureParameterDictionary}; // use crate::utils::{Arena, FileLoc, TextureParameterDictionary};
use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait}; use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait};
use ptex::Cache; use crate::spectra::get_colorspace_context;
use ptex_filter::{PtexFilterOptions, PtexFilterType, ptex_filter_create}; use shared::core::spectrum::SpectrumTrait;
// use ptex::Cache;
// use ptex_filter::{PtexFilter, PtexFilterOptions, PtexFilterType};
use shared::Float; use shared::Float;
use shared::core::color::{ColorEncoding, RGB}; use shared::core::color::{ColorEncoding, RGB};
use shared::core::texture::{SpectrumType, TextureEvalContext}; use shared::core::texture::{SpectrumType, TextureEvalContext};
use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::spectra::{RGBIlluminantSpectrum, SampledSpectrum, SampledWavelengths};
use std::sync::OnceLock;
struct PtexCache(Cache);
unsafe impl Sync for PtexCache {}
unsafe impl Send for PtexCache {}
static PTEX_CACHE: OnceLock<PtexCache> = OnceLock::new();
fn get_ptex_cache() -> &'static Cache {
PTEX_CACHE
.get_or_init(|| {
let max_files = 100;
let max_mem = 1 << 32;
let premultiply = true;
let cache = Cache::new(max_files, max_mem, premultiply);
PtexCache(cache)
})
.0
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PtexTextureBase { pub struct PtexTextureBase {
@ -36,106 +20,21 @@ pub struct PtexTextureBase {
impl PtexTextureBase { impl PtexTextureBase {
pub fn new(filename: String, encoding: ColorEncoding, scale: Float) -> Self { pub fn new(filename: String, encoding: ColorEncoding, scale: Float) -> Self {
let cache: &mut Cache = get_ptex_cache(); log::warn!(
"Ptex support is currently disabled. Texture '{}' will render as Magenta.",
// Attempt to get the texture to verify it exists and is valid
let (valid, num_channels) = match cache.get(&filename) {
Ok(tex) => {
let nc = tex.num_channels();
(nc == 1 || nc == 3, nc)
}
Err(e) => {
log::error!("Ptex Error for {}: {}", filename, e);
(false, 0)
}
};
if !valid && num_channels != 0 {
log::error!(
"{}: only 1 and 3 channel ptex textures are supported",
filename filename
); );
}
Self { Self {
filename, filename,
encoding, encoding,
scale, scale,
valid, valid: false,
} }
} }
pub fn sample_texture(&self, ctx: &TextureEvalContext) -> Option<RGB> { pub fn sample_texture(&self, _ctx: &TextureEvalContext) -> Option<RGB> {
if !self.valid { Some(RGB::new(1.0, 0.0, 1.0) * self.scale)
return None;
}
let mut result = [0.0; 3];
let nc = self.ptex_handle.eval(
&mut result,
ctx.face_index,
ctx.uv[0],
ctx.uv[1],
ctx.dudx,
ctx.dvdx,
ctx.dudy,
ctx.dvdy,
);
let cache = get_ptex_cache();
let texture = match cache.get(&self.filename) {
Ok(t) => t,
Err(e) => {
log::error!("Ptex cache lookup failed for {}: {}", self.filename, e);
return None;
}
};
let nc = texture.num_channels();
let mut result = [0.0; 4];
unsafe {
let opts = PtexFilterOptions {
filter: PtexFilterType::BSpline,
lerp: 1,
sharpness: 0.0,
noedgeblend: 0,
// __structSize: std::mem::size_of::<PtexFilterOptions>() as i32,
};
let filter_ptr = ptex_filter_create(texture.as_ptr(), &opts);
if filter_ptr.is_null() {
return None;
}
// Evaluate
(*filter_ptr).eval(
result.as_mut_ptr(),
0,
texture.num_channels(),
ctx.face_index as i32,
ctx.uv[0],
ctx.uv[1],
ctx.dudx,
ctx.dvdx,
ctx.dudy,
ctx.dvdy,
);
// Manually release the C++ object
(*filter_ptr).release();
}
let mut rgb = RGB::new(result[0], result[1], result[2]);
if self.encoding != ColorEncoding::Linear {
// Convert to 8-bit, process, and convert back
let r8 = (rgb.r * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
let g8 = (rgb.g * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
let b8 = (rgb.b * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
rgb = self.encoding.to_linear_rgb([r8, g8, b8]);
}
Some(rgb * self.scale)
} }
} }
@ -145,8 +44,12 @@ pub struct FloatPtexTexture {
} }
impl FloatTextureTrait for FloatPtexTexture { impl FloatTextureTrait for FloatPtexTexture {
fn evaluate(&self, _ctx: &TextureEvalContext) -> Float { fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
todo!() if let Some(rgb) = self.base.sample_texture(ctx) {
rgb.g
} else {
0.0
}
} }
} }
@ -157,7 +60,15 @@ pub struct SpectrumPtexTexture {
} }
impl SpectrumTextureTrait for SpectrumPtexTexture { impl SpectrumTextureTrait for SpectrumPtexTexture {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum { fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
todo!() if let Some(rgb) = self.base.sample_texture(ctx) {
let stdcs = get_colorspace_context();
let srgb = stdcs.srgb.as_ref();
RGBIlluminantSpectrum::new(srgb, rgb).sample(lambda)
} else {
SampledSpectrum::new(0.0)
}
} }
} }
pub trait CreateGPUPtexTexture {}

View file

@ -22,7 +22,7 @@ impl FloatScaledTexture {
_render_from_texture: &Transform, _render_from_texture: &Transform,
params: &TextureParameterDictionary, params: &TextureParameterDictionary,
_loc: &FileLoc, _loc: &FileLoc,
arena: &mut Arena, _arena: &mut Arena,
) -> FloatTexture { ) -> FloatTexture {
let mut tex = params.get_float_texture("tex", 1.); let mut tex = params.get_float_texture("tex", 1.);
let mut scale = params.get_float_texture("scale", 1.); let mut scale = params.get_float_texture("scale", 1.);
@ -42,8 +42,8 @@ impl FloatScaledTexture {
std::mem::swap(&mut tex, &mut scale); std::mem::swap(&mut tex, &mut scale);
} }
std::mem::swap(&mut tex, &mut scale); std::mem::swap(&mut tex, &mut scale);
arena.alloc(FloatScaledTexture::new(tex, scale)); // arena.alloc(FloatScaledTexture::new(tex, scale));
FloatTexture::FloatScaled(FloatScaledTexture::new(tex, scale)) FloatTexture::Scaled(FloatScaledTexture::new(tex.clone(), scale.clone()))
} }
} }

View file

@ -1,27 +1,34 @@
use crate::core::image::Image; use crate::core::image::Image;
use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::core::texture::{FloatTexture, SpectrumTexture, get_texture_cache};
use crate::shapes::{BilinearPatchMesh, TriangleMesh}; use crate::shapes::{BilinearPatchMesh, TriangleMesh};
use crate::textures::CreateGPUSpectrumPtex;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use shared::core::color::RGBToSpectrumTable; use shared::core::color::RGBToSpectrumTable;
use shared::core::image::DeviceImage; use shared::core::image::DeviceImage;
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture}; use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture};
use shared::spectra::{RGBColorSpace, StandardColorSpaces}; use shared::spectra::{DeviceStandardColorSpaces, RGBColorSpace};
use shared::textures::*; use shared::textures::*;
use shared::utils::Ptr; use shared::utils::Ptr;
use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh}; use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh};
use shared::utils::sampling::{DevicePiecewiseConstant1D, DevicePiecewiseConstant2D}; use shared::utils::sampling::{DevicePiecewiseConstant1D, DevicePiecewiseConstant2D};
use std::alloc::Layout; use std::alloc::Layout;
use std::collections::HashMap;
use std::slice::from_raw_parts;
use std::sync::Arc; use std::sync::Arc;
pub struct Arena { pub struct Arena {
buffer: Vec<(*mut u8, Layout)>, buffer: Vec<(*mut u8, Layout)>,
texture_cache: HashMap<usize, u64>,
} }
impl Arena { impl Arena {
pub fn new() -> Self { pub fn new() -> Self {
Self { buffer: Vec::new() } Self {
buffer: Vec::new(),
texture_cache: HashMap::new(),
}
} }
pub fn alloc<T>(&mut self, value: T) -> Ptr<T> { pub fn alloc<T>(&mut self, value: T) -> Ptr<T> {
@ -71,25 +78,53 @@ impl Arena {
panic!("cudaMallocManaged failed: {:?}", result); panic!("cudaMallocManaged failed: {:?}", result);
} }
self.allocations.push((ptr as *mut u8, layout)); self.buffer.push((ptr as *mut u8, layout));
ptr as *mut u8 ptr as *mut u8
} }
pub fn get_texture_object(&mut self, mipmap: &Arc<MIPMap>) -> u64 {
let key = Arc::as_ptr(mipmap) as usize;
if let Some(&tex_obj) = self.texture_cache.get(&key) {
return tex_obj;
}
#[cfg(feature = "cuda")]
let tex_obj = self.create_cuda_texture(mipmap);
#[cfg(not(feature = "cuda"))]
let tex_obj = 0u64;
self.texture_cache.insert(key, tex_obj);
tex_obj
}
#[cfg(feature = "cuda")]
fn create_cuda_texture(&self, mipmap: &MIPMap) -> u64 {
// TODO: Implement with cudarc
// 1. Get image data from mipmap.base_image()
// 2. cudaMallocArray
// 3. cudaMemcpy2DToArray
// 4. cudaCreateTextureObject
// 5. Return handle
0
}
#[cfg(not(feature = "cuda"))] #[cfg(not(feature = "cuda"))]
unsafe fn alloc_unified(&mut self, layout: Layout) -> *mut u8 { unsafe fn alloc_unified(&mut self, layout: Layout) -> *mut u8 {
let ptr = std::alloc::alloc(layout); let ptr = unsafe { std::alloc::alloc(layout) };
self.allocations.push((ptr, layout)); self.buffer.push((ptr, layout));
ptr ptr
} }
pub fn raw_data(&self) -> &[u8] { // pub fn raw_data(&self) -> &[u8] {
&self.buffer // &self.buffer
} // }
} }
impl Drop for Arena { impl Drop for Arena {
fn drop(&mut self) { fn drop(&mut self) {
for (ptr, layout) in self.allocations.drain(..) { for (ptr, layout) in self.buffer.drain(..) {
unsafe { unsafe {
#[cfg(feature = "cuda")] #[cfg(feature = "cuda")]
{ {
@ -120,7 +155,7 @@ impl Upload for Shape {
impl Upload for Image { impl Upload for Image {
type Target = DeviceImage; type Target = DeviceImage;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> { fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
arena.alloc(self.clone()) arena.alloc(*self.device_image())
} }
} }
@ -138,23 +173,39 @@ impl Upload for SpectrumTexture {
SpectrumTexture::Constant(tex) => GPUSpectrumTexture::Constant(tex.clone()), SpectrumTexture::Constant(tex) => GPUSpectrumTexture::Constant(tex.clone()),
SpectrumTexture::Checkerboard(tex) => GPUSpectrumTexture::Checkerboard(tex.clone()), SpectrumTexture::Checkerboard(tex) => GPUSpectrumTexture::Checkerboard(tex.clone()),
SpectrumTexture::Dots(tex) => GPUSpectrumTexture::Dots(tex.clone()), SpectrumTexture::Dots(tex) => GPUSpectrumTexture::Dots(tex.clone()),
SpectrumTexture::Image(tex) => GPUSpectrumTexture::Image(tex.clone()), SpectrumTexture::Image(tex) => {
let tex_obj = arena.get_texture_object(&tex.base.mipmap);
let gpu_img = GPUSpectrumImageTexture {
mapping: tex.base.mapping,
tex_obj,
scale: tex.base.scale,
invert: tex.base.invert,
is_single_channel: tex.base.mipmap.is_single_channel(),
color_space: tex.base.mipmap.color_space.clone().unwrap(),
spectrum_type: tex.spectrum_type,
};
GPUSpectrumTexture::Image(gpu_img)
}
SpectrumTexture::Bilerp(tex) => GPUSpectrumTexture::Bilerp(tex.clone()), SpectrumTexture::Bilerp(tex) => GPUSpectrumTexture::Bilerp(tex.clone()),
SpectrumTexture::Scaled(tex) => { SpectrumTexture::Scaled(tex) => {
let child_ptr = tex.texture.upload(arena); let child_ptr = tex.tex.upload(arena);
let gpu_scaled = GPUFloatScaledTexture { let gpu_scaled = GPUSpectrumScaledTexture {
tex: child_ptr, tex: child_ptr,
scale: tex.scale.upload(arena), scale: tex.scale.upload(arena),
}; };
GPUSpectrumTexture::Scaled(gpu_scaled) GPUSpectrumTexture::Scaled(gpu_scaled)
} }
SpectrumTexture::Ptex(tex) => GPUSpectrumTexture::Ptex(tex.clone()), // SpectrumTexture::Ptex(tex) => {
// let gpu_ptex = GPUSpectrumPtexTexture::new(
// tex.base.filename,
// tex.base.encoding,
// tex.base.scale,
// tex.spectrum_type,
// );
// GPUSpectrumTexture::Ptex(gpu_ptex)
// }
SpectrumTexture::Marble(tex) => GPUSpectrumTexture::Marble(tex.clone()), SpectrumTexture::Marble(tex) => GPUSpectrumTexture::Marble(tex.clone()),
SpectrumTexture::RGBConstant(tex) => GPUSpectrumTexture::RGBConstant(tex.clone()),
SpectrumTexture::RGBReflectanceConstant(tex) => {
GPUSpectrumTexture::RGBReflectanceConstant(tex.clone())
}
SpectrumTexture::Mix(tex) => { SpectrumTexture::Mix(tex) => {
let tex1_ptr = tex.tex1.upload(arena); let tex1_ptr = tex.tex1.upload(arena);
let tex2_ptr = tex.tex2.upload(arena); let tex2_ptr = tex.tex2.upload(arena);
@ -198,7 +249,7 @@ impl Upload for FloatTexture {
FloatTexture::Wrinkled(tex) => GPUFloatTexture::Wrinkled(tex.clone()), FloatTexture::Wrinkled(tex) => GPUFloatTexture::Wrinkled(tex.clone()),
FloatTexture::Constant(val) => GPUFloatTexture::Constant(*val), FloatTexture::Constant(val) => GPUFloatTexture::Constant(*val),
FloatTexture::Scaled(tex) => { FloatTexture::Scaled(tex) => {
let child_ptr = tex.texture.upload(arena); let child_ptr = tex.tex.upload(arena);
let gpu_scaled = GPUFloatScaledTexture { let gpu_scaled = GPUFloatScaledTexture {
tex: child_ptr, tex: child_ptr,
@ -230,15 +281,13 @@ impl Upload for FloatTexture {
}; };
GPUFloatTexture::DirectionMix(gpu_dmix) GPUFloatTexture::DirectionMix(gpu_dmix)
} }
FloatTexture::Image(tex) => {
let image_ptr = tex.image.upload(arena);
FloatTexture::Image(tex) => {
let gpu_image_tex = GPUFloatImageTexture { let gpu_image_tex = GPUFloatImageTexture {
mapping: tex.mapping, mapping: tex.base.mapping,
tex_obj: image_ptr.offset as u64, tex_obj: image_ptr.offset as u64,
scale: tex.scale, scale: tex.base.scale,
invert: tex.invert, invert: tex.base.invert,
mapping: tex.mapping,
}; };
GPUFloatTexture::Image(gpu_image_tex) GPUFloatTexture::Image(gpu_image_tex)
} }
@ -258,12 +307,16 @@ impl Upload for RGBToSpectrumTable {
type Target = RGBToSpectrumTable; type Target = RGBToSpectrumTable;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> { fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
let z_ptr = arena.alloc_slice(&self.z_nodes); let n_nodes = self.n_nodes as usize;
let c_ptr = arena.alloc_slice(&self.coeffs); let z_slice = unsafe { from_raw_parts(self.z_nodes.as_raw(), n_nodes) };
let coeffs_slice = unsafe { from_raw_parts(self.coeffs.as_raw(), n_nodes) };
let (z_ptr, _) = arena.alloc_slice(z_slice);
let (c_ptr, _) = arena.alloc_slice(coeffs_slice);
let shared_table = RGBToSpectrumTable { let shared_table = RGBToSpectrumTable {
z_nodes: z_ptr, z_nodes: z_ptr,
coeffs: c_ptr, coeffs: c_ptr,
n_nodes: self.n_nodes,
}; };
arena.alloc(shared_table) arena.alloc(shared_table)
@ -291,8 +344,8 @@ impl Upload for RGBColorSpace {
} }
} }
impl Upload for StandardColorSpaces { impl Upload for DeviceStandardColorSpaces {
type Target = StandardColorSpaces; type Target = DeviceStandardColorSpaces;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> { fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
let srgb_ptr = self.srgb.upload(arena); let srgb_ptr = self.srgb.upload(arena);
@ -300,7 +353,7 @@ impl Upload for StandardColorSpaces {
let rec_ptr = self.rec2020.upload(arena); let rec_ptr = self.rec2020.upload(arena);
let aces_ptr = self.aces2065_1.upload(arena); let aces_ptr = self.aces2065_1.upload(arena);
let registry = StandardColorSpaces { let registry = DeviceStandardColorSpaces {
srgb: srgb_ptr, srgb: srgb_ptr,
dci_p3: dci_ptr, dci_p3: dci_ptr,
rec2020: rec_ptr, rec2020: rec_ptr,
@ -315,15 +368,15 @@ impl Upload for PiecewiseConstant2D {
type Target = DevicePiecewiseConstant2D; type Target = DevicePiecewiseConstant2D;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> { fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
let marginal_shared = self.p_marginal.to_shared(arena); let marginal_shared = self.marginal.to_shared(arena);
let conditionals_shared: Vec<DevicePiecewiseConstant1D> = self let conditionals_shared: Vec<DevicePiecewiseConstant1D> = self
.p_conditionals .conditionals
.iter() .iter()
.map(|c| c.to_shared(arena)) .map(|c| c.to_shared(arena))
.collect(); .collect();
let conditionals_ptr = arena.alloc_slice(&conditionals_shared); let (conditionals_ptr, _) = arena.alloc_slice(&conditionals_shared);
let shared_2d = DevicePiecewiseConstant2D { let shared_2d = DevicePiecewiseConstant2D {
conditionals: conditionals_ptr, conditionals: conditionals_ptr,
@ -339,44 +392,44 @@ impl Upload for TriangleMesh {
type Target = DeviceTriangleMesh; type Target = DeviceTriangleMesh;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> { fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
let storage = &self._storage; let storage = &self.storage;
// Upload all arrays to arena // Upload all arrays to arena
let vertex_indices_ptr = arena.alloc_slice(&storage.vertex_indices); let (vertex_indices_ptr, _) = arena.alloc_slice(&storage.vertex_indices);
let p_ptr = arena.alloc_slice(&storage.p); let (p_ptr, _) = arena.alloc_slice(&storage.p);
let n_ptr = if storage.n.is_empty() { let (n_ptr, _) = if storage.n.is_empty() {
std::ptr::null() (Ptr::null(), 0)
} else { } else {
arena.alloc_slice(&storage.n).as_ptr() arena.alloc_slice(&storage.n)
}; };
let s_ptr = if storage.s.is_empty() { let (s_ptr, _) = if storage.s.is_empty() {
std::ptr::null() (Ptr::null(), 0)
} else { } else {
arena.alloc_slice(&storage.s).as_ptr() arena.alloc_slice(&storage.s)
}; };
let uv_ptr = if storage.uv.is_empty() { let (uv_ptr, _) = if storage.uv.is_empty() {
std::ptr::null() (Ptr::null(), 0)
} else { } else {
arena.alloc_slice(&storage.uv).as_ptr() arena.alloc_slice(&storage.uv)
}; };
let face_indices_ptr = if storage.face_indices.is_empty() { let (face_indices_ptr, _) = if storage.face_indices.is_empty() {
std::ptr::null() (Ptr::null(), 0)
} else { } else {
arena.alloc_slice(&storage.face_indices).as_ptr() arena.alloc_slice(&storage.face_indices)
}; };
let device = DeviceTriangleMesh { let device = DeviceTriangleMesh {
vertex_indices: vertex_indices_ptr.as_ptr(), vertex_indices: vertex_indices_ptr,
p: p_ptr.as_ptr(), p: p_ptr,
n: n_ptr, n: n_ptr,
s: s_ptr, s: s_ptr,
uv: uv_ptr, uv: uv_ptr,
face_indices: face_indices_ptr, face_indices: face_indices_ptr,
..self.device // Copy n_triangles, n_vertices, flags ..self.device
}; };
arena.alloc(device) arena.alloc(device)
@ -387,33 +440,28 @@ impl Upload for BilinearPatchMesh {
type Target = DeviceBilinearPatchMesh; type Target = DeviceBilinearPatchMesh;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> { fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
let storage = &self._storage; let storage = &self.storage;
let vertex_indices_ptr = arena.alloc_slice(&storage.vertex_indices); let (vertex_indices_ptr, _) = arena.alloc_slice(&storage.vertex_indices);
let p_ptr = arena.alloc_slice(&storage.p); let (p_ptr, _) = arena.alloc_slice(&storage.p);
let n_ptr = if storage.n.is_empty() { let (n_ptr, _) = if storage.n.is_empty() {
std::ptr::null() (Ptr::null(), 0)
} else { } else {
arena.alloc_slice(&storage.n).as_ptr() arena.alloc_slice(&storage.n)
}; };
let uv_ptr = if storage.uv.is_empty() { let (uv_ptr, _) = if storage.uv.is_empty() {
std::ptr::null() (Ptr::null(), 0)
} else { } else {
arena.alloc_slice(&storage.uv).as_ptr() arena.alloc_slice(&storage.uv)
}; };
let image_dist_ptr = if let Some(ref dist) = storage.image_distribution { let image_dist_ptr = storage.image_distribution.upload(arena);
let uploaded = dist.upload(arena);
uploaded.as_ptr()
} else {
std::ptr::null()
};
let device = DeviceBilinearPatchMesh { let device = DeviceBilinearPatchMesh {
vertex_indices: vertex_indices_ptr.as_ptr(), vertex_indices: vertex_indices_ptr,
p: p_ptr.as_ptr(), p: p_ptr,
n: n_ptr, n: n_ptr,
uv: uv_ptr, uv: uv_ptr,
image_distribution: image_dist_ptr, image_distribution: image_dist_ptr,

View file

@ -2,6 +2,7 @@ use parking_lot::Mutex;
use shared::core::geometry::{Bounds2i, Point2i}; use shared::core::geometry::{Bounds2i, Point2i};
use shared::utils::containers::Array2D; use shared::utils::containers::Array2D;
use std::collections::HashSet; use std::collections::HashSet;
use std::hash::Hash;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::Arc; use std::sync::Arc;
@ -11,7 +12,7 @@ pub struct InternCache<T> {
impl<T> InternCache<T> impl<T> InternCache<T>
where where
T: Eq + Clone, T: Eq + Clone + Hash,
{ {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -20,14 +21,14 @@ where
} }
pub fn lookup(&self, value: T) -> Arc<T> { pub fn lookup(&self, value: T) -> Arc<T> {
let mut lock = self.cache.lock().unwrap(); let mut lock = self.cache.lock();
if let Some(existing) = lock.get(&value) { if let Some(existing) = lock.get(&value) {
return existing.clone(); return existing.clone();
} }
let new_item = Arc::new(value); let new_item = Arc::new(value);
lock.insert(new_item.clone()); lock.insert(Arc::clone(&new_item));
new_item new_item
} }
} }
@ -52,7 +53,7 @@ impl<T> DerefMut for Array2DBuffer<T> {
impl<T> Array2DBuffer<T> { impl<T> Array2DBuffer<T> {
fn from_vec(extent: Bounds2i, mut storage: Vec<T>) -> Self { fn from_vec(extent: Bounds2i, mut storage: Vec<T>) -> Self {
let width = extent.p_max.x - extent.p_min.x; let width = extent.p_max.x() - extent.p_min.x();
let view = Array2D { let view = Array2D {
extent, extent,
values: storage.as_mut_ptr(), values: storage.as_mut_ptr(),
@ -73,12 +74,12 @@ impl<T: Default + Clone> Array2DBuffer<T> {
} }
pub fn new_dims(nx: i32, ny: i32) -> Self { pub fn new_dims(nx: i32, ny: i32) -> Self {
let extent = Bounds2i::new(Point2i::new(0, 0), Point2i::new(nx, ny)); let extent = Bounds2i::from_points(Point2i::new(0, 0), Point2i::new(nx, ny));
Self::new(extent) Self::new(extent)
} }
pub fn new_filled(nx: i32, ny: i32, val: T) -> Self { pub fn new_filled(nx: i32, ny: i32, val: T) -> Self {
let extent = Bounds2i::new(Point2i::new(0, 0), Point2i::new(nx, ny)); let extent = Bounds2i::from_points(Point2i::new(0, 0), Point2i::new(nx, ny));
let n = (nx * ny) as usize; let n = (nx * ny) as usize;
let storage = vec![val; n]; let storage = vec![val; n];
Self::from_vec(extent, storage) Self::from_vec(extent, storage)

View file

@ -1,9 +1,15 @@
use shared::Float; use shared::Float;
use shared::utils::Ptr;
use shared::utils::hash::hash_buffer; use shared::utils::hash::hash_buffer;
use shared::utils::math::{DigitPermutation, PRIMES, permutation_element}; use shared::utils::math::{DeviceDigitPermutation, PRIMES, permutation_element};
pub fn new_digit_permutation(base: u32, seed: u64) -> Vec<u16> { 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 mut n_digits: u32 = 0;
let inv_base = 1. / base as Float; let inv_base = 1. / base as Float;
let mut inv_base_m = 1.; let mut inv_base_m = 1.;
@ -20,20 +26,30 @@ pub fn new_digit_permutation(base: u32, seed: u64) -> Vec<u16> {
let dseed = hash_buffer(&hash_input, 0); let dseed = hash_buffer(&hash_input, 0);
for digit_value in 0..base { for digit_value in 0..base {
let index = (digit_index * base + digit_value) as usize; let index = (digit_index as i32 * base + digit_value) as usize;
permutations[index] = permutations[index] =
permutation_element(digit_value as u32, base as u32, dseed as u32) as u16; permutation_element(digit_value as u32, base as u32, dseed as u32) as u16;
} }
} }
permutations 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<DigitPermutation>) { pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec<u16>, Vec<DigitPermutation>) {
let temp_data: Vec<Vec<u16>> = PRIMES let temp_data: Vec<Vec<u16>> = PRIMES
.iter() .iter()
.map(|&base| new_digit_permutation(base as u32, seed)) .map(|&base| DigitPermutation::new(base as i32, seed).permutations)
.collect(); .collect();
let mut storage: Vec<u16> = Vec::with_capacity(temp_data.iter().map(|v| v.len()).sum()); let mut storage: Vec<u16> = Vec::with_capacity(temp_data.iter().map(|v| v.len()).sum());
@ -42,25 +58,23 @@ pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec<u16>, Vec<DigitPe
} }
let mut views = Vec::with_capacity(PRIMES.len()); let mut views = Vec::with_capacity(PRIMES.len());
let mut current_offset = 0; // let mut current_offset = 0;
let storage_base_ptr = storage.as_ptr(); // let storage_base_ptr = storage.as_ptr();
for (i, &base) in PRIMES.iter().enumerate() { for (i, &base) in PRIMES.iter().enumerate() {
let len = temp_data[i].len(); let len = temp_data[i].len();
let n_digits = len as u32 / base as u32; let n_digits = len as u32 / base as u32;
unsafe { // let ptr_to_data = storage_base_ptr.add(current_offset);
let ptr_to_data = storage_base_ptr.add(current_offset);
views.push(DigitPermutation::new( views.push(DigitPermutation::new(
base as u32, base as i32,
n_digits, n_digits as u64,
Ptr::from(ptr_to_data), // Ptr::from(ptr_to_data),
)); ));
}
current_offset += len; // current_offset += len;
} }
(storage, views) (storage, views)

View file

@ -1,4 +1,4 @@
use crate::core::image::Image; use crate::core::image::{Image, ImageIO};
use shared::Float; use shared::Float;
use shared::core::color::{ColorEncoding, RGB}; use shared::core::color::{ColorEncoding, RGB};
use shared::core::geometry::{Point2f, Point2i, Vector2f, VectorLike}; use shared::core::geometry::{Point2f, Point2i, Vector2f, VectorLike};
@ -6,10 +6,12 @@ use shared::core::image::{WrapMode, WrapMode2D};
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::math::{lerp, safe_sqrt, square}; use shared::utils::math::{lerp, safe_sqrt, square};
use std::path::Path; use std::path::Path;
use std::sync::OnceLock;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::ops::{Add, Mul, Sub}; use std::ops::{Add, Mul, Sub};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FilterFunction { pub enum FilterFunction {
Point, Point,
@ -30,10 +32,11 @@ impl std::fmt::Display for FilterFunction {
} }
} }
#[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct MIPMapFilterOptions { pub struct MIPMapFilterOptions {
pub filter: FilterFunction, pub filter: FilterFunction,
pub max_anisotropy: f32, pub max_anisotropy: Float,
} }
impl Default for MIPMapFilterOptions { impl Default for MIPMapFilterOptions {
@ -116,12 +119,12 @@ impl MIPMapSample for RGB {
} }
} }
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct MIPMap { pub struct MIPMap {
pyramid: Vec<Image>, pub pyramid: Vec<Image>,
color_space: Option<RGBColorSpace>, pub color_space: Option<RGBColorSpace>,
wrap_mode: WrapMode, pub wrap_mode: WrapMode,
options: MIPMapFilterOptions, pub options: MIPMapFilterOptions,
} }
impl MIPMap { impl MIPMap {
@ -144,6 +147,11 @@ impl MIPMap {
self.pyramid[level].resolution() self.pyramid[level].resolution()
} }
#[inline]
pub fn is_single_channel(&self) -> bool {
self.pyramid[0].n_channels() == 1
}
pub fn levels(&self) -> usize { pub fn levels(&self) -> usize {
self.pyramid.len() self.pyramid.len()
} }
@ -156,6 +164,10 @@ impl MIPMap {
&self.pyramid[level] &self.pyramid[level]
} }
pub fn base_image(&self) -> &Image {
&self.pyramid[0]
}
pub fn filter<T: MIPMapSample>( pub fn filter<T: MIPMapSample>(
&self, &self,
st: Point2f, st: Point2f,
@ -335,6 +347,83 @@ impl MIPMap {
options, options,
)) ))
} }
#[cfg(feature = "cuda")]
pub fn texture_object(&self) -> u64 {
*self
.tex_obj
.get_or_init(|| create_cuda_texture(&self.pyramid, self.wrap_mode))
}
#[cfg(not(feature = "cuda"))]
pub fn texture_object(&self) -> u64 {
0
}
}
#[cfg(feature = "cuda")]
fn create_cuda_texture(pyramid: &[Image], wrap_mode: WrapMode) -> u64 {
use cuda_runtime_sys::*;
let base = &pyramid[0];
let (width, height) = (
base.resolution().x() as usize,
base.resolution().y() as usize,
);
let channels = base.n_channels();
unsafe {
let channel_desc = cudaCreateChannelDesc(
32,
if channels > 1 { 32 } else { 0 },
if channels > 2 { 32 } else { 0 },
if channels > 3 { 32 } else { 0 },
cudaChannelFormatKindFloat,
);
let mut array: cudaArray_t = std::ptr::null_mut();
cudaMallocArray(&mut array, &channel_desc, width, height, 0);
let pixels = base.as_slice(); // Assuming you have this method
cudaMemcpy2DToArray(
array,
0,
0,
pixels.as_ptr() as *const _,
width * channels * std::mem::size_of::<f32>(),
width * channels * std::mem::size_of::<f32>(),
height,
cudaMemcpyHostToDevice,
);
// 4. Create texture object
let res_desc = cudaResourceDesc {
resType: cudaResourceTypeArray,
res: cudaResourceDesc__bindgen_ty_1 {
array: cudaResourceDesc__bindgen_ty_1__bindgen_ty_1 { array },
},
};
let address_mode = match wrap_mode {
WrapMode::Repeat => cudaAddressModeWrap,
WrapMode::Clamp => cudaAddressModeClamp,
WrapMode::Black => cudaAddressModeBorder,
WrapMode::OctahedralSphere => cudaAddressModeBorder,
};
let tex_desc = cudaTextureDesc {
addressMode: [address_mode; 3],
filterMode: cudaFilterModeLinear,
readMode: cudaReadModeElementType,
normalizedCoords: 1,
..std::mem::zeroed()
};
let mut tex_obj: cudaTextureObject_t = 0;
cudaCreateTextureObject(&mut tex_obj, &res_desc, &tex_desc, std::ptr::null());
tex_obj
}
} }
static MIP_FILTER_LUT_SIZE: usize = 128; static MIP_FILTER_LUT_SIZE: usize = 128;

View file

@ -1,6 +1,7 @@
use crate::core::spectrum::SPECTRUM_CACHE; use crate::core::spectrum::SPECTRUM_CACHE;
use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::spectra::data::get_named_spectrum; use crate::spectra::data::get_named_spectrum;
use crate::spectra::piecewise::PiecewiseLinearSpectrumBuffer;
use crate::utils::FileLoc; use crate::utils::FileLoc;
use shared::Float; use shared::Float;
use shared::core::color::RGB; use shared::core::color::RGB;
@ -132,7 +133,7 @@ impl PBRTParameter for bool {
v[0] v[0]
} }
fn get_values(param: &ParsedParameter) -> &[Self::Raw] { fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
param.bools &param.bools
} }
} }
@ -144,7 +145,7 @@ impl PBRTParameter for Float {
v[0] v[0]
} }
fn get_values(param: &ParsedParameter) -> &[Self::Raw] { fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
param.floats &param.floats
} }
} }
@ -157,55 +158,67 @@ impl PBRTParameter for i32 {
} }
fn get_values(param: &ParsedParameter) -> &[Self::Raw] { fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
param.ints &param.ints
} }
} }
impl PBRTParameter for Point2f { impl PBRTParameter for Point2f {
type Raw = Point2f; type Raw = Float;
const TYPE_NAME: &'static str = "point2"; const TYPE_NAME: &'static str = "point2";
const N_PER_ITEM: usize = 2; const N_PER_ITEM: usize = 2;
fn convert(v: &[Self::Raw]) -> Self { fn convert(v: &[Self::Raw]) -> Self {
Point2f::new(v[0], v[1]) Point2f::new(v[0], v[1])
} }
fn get_values(param: &ParsedParameter) -> &[Self::Raw] { fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
param.floats &param.floats
} }
} }
impl PBRTParameter for Point3f { impl PBRTParameter for Point3f {
type Raw = Point3f; type Raw = Float;
const TYPE_NAME: &'static str = "point3"; const TYPE_NAME: &'static str = "point3";
const N_PER_ITEM: usize = 3; const N_PER_ITEM: usize = 3;
fn convert(v: &[Self::Raw]) -> Self { fn convert(v: &[Self::Raw]) -> Self {
Point3f::new(v[0], v[1], v[2]) Point3f::new(v[0], v[1], v[2])
} }
fn get_values(param: &ParsedParameter) -> &[Self::Raw] { fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
param.floats &param.floats
} }
} }
impl PBRTParameter for Vector2f { impl PBRTParameter for Vector2f {
type Raw = Vector2f; type Raw = Float;
const TYPE_NAME: &'static str = "vector2"; const TYPE_NAME: &'static str = "vector2";
const N_PER_ITEM: usize = 3; const N_PER_ITEM: usize = 2;
fn convert(v: &[Self::Raw]) -> Self { fn convert(v: &[Self::Raw]) -> Self {
Vector2f::new(v[0], v[1]) Vector2f::new(v[0], v[1])
} }
fn get_values(param: &ParsedParameter) -> &[Self::Raw] { fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
param.floats &param.floats
} }
} }
impl PBRTParameter for Normal3f { impl PBRTParameter for Vector3f {
type Raw = Normal3f; type Raw = Float;
const TYPE_NAME: &'static str = "normal"; const TYPE_NAME: &'static str = "vector3";
const N_PER_ITEM: usize = 3; const N_PER_ITEM: usize = 3;
fn convert(v: &[Self::Raw]) -> Self { fn convert(v: &[Self::Raw]) -> Self {
Vector3f::new(v[0], v[1], v[2]) Vector3f::new(v[0], v[1], v[2])
} }
fn get_values(param: &ParsedParameter) -> &[Self::Raw] { fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
param.floats &param.floats
}
}
impl PBRTParameter for Normal3f {
type Raw = Float;
const TYPE_NAME: &'static str = "normal";
const N_PER_ITEM: usize = 3;
fn convert(v: &[Self::Raw]) -> Self {
Normal3f::new(v[0], v[1], v[2])
}
fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
&param.floats
} }
} }
@ -217,7 +230,7 @@ impl PBRTParameter for String {
v[0].clone() v[0].clone()
} }
fn get_values(param: &ParsedParameter) -> &[Self::Raw] { fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
param.strings &param.strings
} }
} }
@ -272,7 +285,7 @@ impl ParameterDictionary {
bool::TYPE_NAME => { bool::TYPE_NAME => {
if p.bools.is_empty() { if p.bools.is_empty() {
error_exit( error_exit(
&p.loc, Some(&p.loc),
&format!( &format!(
"\"{}\": non-Boolean values provided for Boolean-valued parameter", "\"{}\": non-Boolean values provided for Boolean-valued parameter",
p.name p.name
@ -292,7 +305,7 @@ impl ParameterDictionary {
| "blackbody" => { | "blackbody" => {
if p.ints.is_empty() && p.floats.is_empty() { if p.ints.is_empty() && p.floats.is_empty() {
error_exit( error_exit(
&p.loc, Some(&p.loc),
&format!( &format!(
"\"{}\": non-numeric values provided for numeric-valued parameter", "\"{}\": non-numeric values provided for numeric-valued parameter",
p.name p.name
@ -304,7 +317,7 @@ impl ParameterDictionary {
String::TYPE_NAME | "texture" => { String::TYPE_NAME | "texture" => {
if p.strings.is_empty() { if p.strings.is_empty() {
error_exit( error_exit(
&p.loc, Some(&p.loc),
&format!( &format!(
"\"{}\": non-string values provided for string-valued parameter", "\"{}\": non-string values provided for string-valued parameter",
p.name p.name
@ -316,7 +329,7 @@ impl ParameterDictionary {
"spectrum" => { "spectrum" => {
if p.strings.is_empty() && p.ints.is_empty() && p.floats.is_empty() { if p.strings.is_empty() && p.ints.is_empty() && p.floats.is_empty() {
error_exit( error_exit(
&p.loc, Some(&p.loc),
&format!( &format!(
"\"{}\": expecting string or numeric-valued parameter for spectrum parameter", "\"{}\": expecting string or numeric-valued parameter for spectrum parameter",
p.name p.name
@ -327,7 +340,7 @@ impl ParameterDictionary {
unknown => { unknown => {
error_exit( error_exit(
&p.loc, Some(&p.loc),
&format!("\"{}\": unknown parameter type '{}'", p.name, unknown), &format!("\"{}\": unknown parameter type '{}'", p.name, unknown),
); );
} }
@ -339,20 +352,20 @@ impl ParameterDictionary {
where where
T: PBRTParameter, T: PBRTParameter,
{ {
let param = self.params; let param = self.params[0].clone();
if param.name == name && param.type_name == T::TYPE_NAME { if param.name == name && param.type_name == T::TYPE_NAME {
let values = T::get_values(param); let values = T::get_values(&param);
if values.is_empty() { if values.is_empty() {
error_exit( error_exit(
&param.loc, Some(&param.loc),
&format!("No values provided for parameter \"{}\".", name), &format!("No values provided for parameter \"{}\".", name),
); );
} }
if values.len() != T::N_PER_ITEM { if values.len() != T::N_PER_ITEM {
error_exit( error_exit(
&param.loc, Some(&param.loc),
&format!( &format!(
"Expected {} values for parameter \"{}\". Found {}.", "Expected {} values for parameter \"{}\". Found {}.",
T::N_PER_ITEM, T::N_PER_ITEM,
@ -379,7 +392,7 @@ impl ParameterDictionary {
if values.len() % T::N_PER_ITEM != 0 { if values.len() % T::N_PER_ITEM != 0 {
error_exit( error_exit(
&param.loc, Some(&param.loc),
&format!( &format!(
"Number of values for \"{}\" is not a multiple of {}", "Number of values for \"{}\" is not a multiple of {}",
name, name,
@ -412,7 +425,7 @@ impl ParameterDictionary {
} }
pub fn get_one_string(&self, name: &str, def: &str) -> String { pub fn get_one_string(&self, name: &str, def: &str) -> String {
self.lookup_single(name, def) self.lookup_single(name, def.to_string())
} }
pub fn get_one_point2f(&self, name: &str, def: Point2f) -> Point2f { pub fn get_one_point2f(&self, name: &str, def: Point2f) -> Point2f {
@ -561,8 +574,8 @@ impl ParameterDictionary {
) -> Vec<Spectrum> { ) -> Vec<Spectrum> {
match param.type_name.as_str() { match param.type_name.as_str() {
"rgb" | "color" => self.extract_rgb_spectrum(param, spectrum_type), "rgb" | "color" => self.extract_rgb_spectrum(param, spectrum_type),
"blackbody" => self.extract_blackbody_spectrum(param), "blackbody" => self.extract_file_spectrum(param),
"spectrum" => self.extract_complex_spectrum(param), "spectrum" => self.extract_sampled_spectrum(param),
_ => Vec::new(), _ => Vec::new(),
} }
} }
@ -628,7 +641,7 @@ impl ParameterDictionary {
); );
} }
let (lambdas, values): (Vec<f64>, Vec<f64>) = param let (lambdas, values): (Vec<Float>, Vec<Float>) = param
.floats .floats
.chunks(2) .chunks(2)
.enumerate() .enumerate()
@ -642,14 +655,14 @@ impl ParameterDictionary {
lam lam
); );
} }
(lam, val) (lam as Float, val as Float)
}) })
.unzip(); .unzip();
vec![Spectrum::PiecewiseLinear(PiecewiseLinearSpectrum { vec![Spectrum::Piecewise(PiecewiseLinearSpectrum {
lambdas, lambdas: lambdas.as_ptr().into(),
values, values: values.as_ptr().into(),
count: lambdas.len(), count: lambdas.len() as u32,
})] })]
} }
@ -670,19 +683,19 @@ impl ParameterDictionary {
fn read_spectrum_from_file(filename: &str) -> Result<Spectrum, String> { fn read_spectrum_from_file(filename: &str) -> Result<Spectrum, String> {
let fn_key = filename.to_string(); let fn_key = filename.to_string();
{ {
let cache = SPECTRUM_CACHE.lock().unwrap(); let cache = SPECTRUM_CACHE.lock();
if let Some(s) = cache.get(&fn_key) { if let Some(s) = cache.get(&fn_key) {
return Ok(s.clone()); return Ok(s.clone());
} }
} }
let pls = PiecewiseLinearSpectrum::read(&fn_key) let pls = PiecewiseLinearSpectrumBuffer::read(&fn_key)
.ok_or_else(|| format!("unable to read or parse spectrum file '{}'", fn_key))?; .ok_or_else(|| format!("unable to read or parse spectrum file '{}'", fn_key))?;
let spectrum = Spectrum::PiecewiseLinear(pls); let spectrum = Spectrum::Piecewise(*pls);
{ {
let mut cache = SPECTRUM_CACHE.lock().unwrap(); let mut cache = SPECTRUM_CACHE.lock();
cache.insert(fn_key, spectrum.clone()); cache.insert(fn_key, spectrum.clone());
} }
@ -794,7 +807,7 @@ impl TextureParameterDictionary {
if tex.is_some() { if tex.is_some() {
tex tex
} else if val.is_some() { } else if val.is_some() {
Some(Arc::new(SpectrumTexture::SpectrumConstant( Some(Arc::new(SpectrumTexture::Constant(
SpectrumConstantTexture::new(val.unwrap()), SpectrumConstantTexture::new(val.unwrap()),
))) )))
} else { } else {
@ -806,7 +819,7 @@ impl TextureParameterDictionary {
if let Some(tex) = self.get_float_texture_or_null(name) { if let Some(tex) = self.get_float_texture_or_null(name) {
return tex; return tex;
} else { } else {
return Arc::new(FloatTexture::FloatConstant(FloatConstantTexture::new(val))); return Arc::new(FloatTexture::Constant(FloatConstantTexture::new(val)));
} }
} }
@ -878,14 +891,14 @@ impl TextureParameterDictionary {
Spectrum::RGBAlbedo(RGBAlbedoSpectrum::new(cs, rgb)) Spectrum::RGBAlbedo(RGBAlbedoSpectrum::new(cs, rgb))
} }
}; };
return Some(Arc::new(SpectrumTexture::SpectrumConstant( return Some(Arc::new(SpectrumTexture::Constant(
SpectrumConstantTexture::new(s), SpectrumConstantTexture::new(s),
))); )));
} }
"spectrum" | "blackbody" => { "spectrum" | "blackbody" => {
let s = self.dict.get_one_spectrum(name, None, stype)?; let s = self.dict.get_one_spectrum(name, None, stype)?;
return Some(Arc::new(SpectrumTexture::SpectrumConstant( return Some(Arc::new(SpectrumTexture::Constant(
SpectrumConstantTexture::new(s), SpectrumConstantTexture::new(s),
))); )));
} }
@ -930,9 +943,9 @@ impl TextureParameterDictionary {
} }
"float" => { "float" => {
let v = self.get_one_float(name, 0.); let v = self.get_one_float(name, 0.);
return Some(Arc::new(FloatTexture::FloatConstant( return Some(Arc::new(FloatTexture::Constant(FloatConstantTexture::new(
FloatConstantTexture::new(v), v,
))); ))));
} }
_ => { _ => {
panic!("[{:?}] Couldn't find float texture", p.loc); panic!("[{:?}] Couldn't find float texture", p.loc);

View file

@ -753,7 +753,9 @@ impl<'a> SceneParser<'a> {
'A' => match token.text.as_str() { 'A' => match token.text.as_str() {
"AttributeBegin" => self.target.attribute_begin(token.loc), "AttributeBegin" => self.target.attribute_begin(token.loc),
"AttributeEnd" => self.target.attribute_end(token.loc), "AttributeEnd" => self.target.attribute_end(token.loc),
"Attribute" => self.parse_basic_entry(|t, n, p, l| t.attribute(n, p, l))?, "Attribute" => {
self.parse_basic_entry(|t, n, p, l| t.attribute(n, p.to_vec(), l))?
}
"ActiveTransform" => { "ActiveTransform" => {
let a = self.next_token_required()?; let a = self.next_token_required()?;
match a.text.as_str() { match a.text.as_str() {

View file

@ -4,7 +4,8 @@ use shared::Float;
use shared::core::geometry::{Point2i, Vector2f, Vector2i}; use shared::core::geometry::{Point2i, Vector2f, Vector2i};
use shared::utils::Ptr; use shared::utils::Ptr;
use shared::utils::sampling::{ use shared::utils::sampling::{
AliasTable, Bin, DevicePiecewiseConstant1D, DevicePiecewiseConstant2D, PiecewiseLinear2D, AliasTable, Bin, DevicePiecewiseConstant1D, DevicePiecewiseConstant2D, DeviceSummedAreaTable,
DeviceWindowedPiecewiseConstant2D, PiecewiseLinear2D,
}; };
use std::sync::Arc; use std::sync::Arc;
@ -114,13 +115,22 @@ impl std::ops::Deref for PiecewiseConstant1D {
} }
} }
#[derive(Debug, Clone)]
pub struct PiecewiseConstant2D { pub struct PiecewiseConstant2D {
conditionals: Vec<PiecewiseConstant1D>, pub conditionals: Vec<PiecewiseConstant1D>,
marginal: PiecewiseConstant1D, pub marginal: PiecewiseConstant1D,
conditional_devices: Box<[DevicePiecewiseConstant1D]>, pub conditional_devices: Box<[DevicePiecewiseConstant1D]>,
pub device: DevicePiecewiseConstant2D, pub device: DevicePiecewiseConstant2D,
} }
impl std::ops::Deref for PiecewiseConstant2D {
type Target = DevicePiecewiseConstant2D;
fn deref(&self) -> &Self::Target {
&self.device
}
}
impl PiecewiseConstant2D { impl PiecewiseConstant2D {
pub fn new(data: &[Float], n_u: usize, n_v: usize) -> Self { pub fn new(data: &[Float], n_u: usize, n_v: usize) -> Self {
assert_eq!(data.len(), n_u * n_v); assert_eq!(data.len(), n_u * n_v);
@ -149,7 +159,7 @@ impl PiecewiseConstant2D {
.into_boxed_slice(); .into_boxed_slice();
let device = DevicePiecewiseConstant2D { let device = DevicePiecewiseConstant2D {
conditionals: conditional_devices.as_ptr(), conditionals: conditional_devices.as_ptr().into(),
marginal: marginal.device, marginal: marginal.device,
n_u: n_u as u32, n_u: n_u as u32,
n_v: n_v as u32, n_v: n_v as u32,
@ -331,7 +341,7 @@ impl<const N: usize> PiecewiseLinear2DHost<N> {
} }
} }
#[derive(Copy, Debug, Clone)] #[derive(Debug, Clone)]
pub struct AliasTableHost { pub struct AliasTableHost {
pub view: AliasTable, pub view: AliasTable,
_storage: Vec<Bin>, _storage: Vec<Bin>,
@ -421,3 +431,71 @@ impl AliasTableHost {
} }
} }
} }
#[derive(Clone, Debug)]
pub struct SummedAreaTable {
pub device: DeviceSummedAreaTable,
sum: Array2D<f64>,
}
impl std::ops::Deref for SummedAreaTable {
type Target = DeviceSummedAreaTable;
fn deref(&self) -> &Self::Target {
&self.device
}
}
impl SummedAreaTable {
pub fn new(values: &Array2D<Float>) -> Self {
let width = values.x_size();
let height = values.y_size();
let mut sum = Array2D::<f64>::new_with_dims(width, height);
sum[(0, 0)] = values[(0, 0)] as f64;
for x in 1..width {
sum[(x, 0)] = values[(x as i32, 0)] as f64 + sum[(x - 1, 0)];
}
for y in 1..height {
sum[(0, y)] = values[(0, y as i32)] as f64 + sum[(0, y - 1)];
}
for y in 1..height {
for x in 1..width {
let term = values[(x as i32, y as i32)] as f64;
let left = sum[(x - 1, y)];
let up = sum[(x, y - 1)];
let diag = sum[(x - 1, y - 1)];
sum[(x, y)] = term + left + up - diag;
}
}
let device = DeviceSummedAreaTable { sum };
Self { device, sum }
}
}
#[derive(Clone, Debug)]
pub struct WindowedPiecewiseConstant2D {
pub device: DeviceWindowedPiecewiseConstant2D,
sat: DeviceSummedAreaTable,
func: Array2D<Float>,
}
impl std::ops::Deref for WindowedPiecewiseConstant2D {
type Target = DeviceWindowedPiecewiseConstant2D;
fn deref(&self) -> &Self::Target {
&self.device
}
}
impl WindowedPiecewiseConstant2D {
pub fn new(func: Array2D<Float>) -> Self {
let sat = *SummedAreaTable::new(&func);
let device = DeviceWindowedPiecewiseConstant2D { sat, func };
Self { sat, func, device }
}
}