Fixing errors in CPU code
This commit is contained in:
parent
1e21cc64f9
commit
93bcd465eb
75 changed files with 2786 additions and 3031 deletions
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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::{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>,
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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.)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1571
src/core/scene.rs
1571
src/core/scene.rs
File diff suppressed because it is too large
Load diff
615
src/core/scene/builder.rs
Normal file
615
src/core/scene/builder.rs
Normal 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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/core/scene/entities.rs
Normal file
81
src/core/scene/entities.rs
Normal 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
9
src/core/scene/mod.rs
Normal 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
732
src/core/scene/scene.rs
Normal 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(¶ms.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(¶ms.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
36
src/core/scene/state.rs
Normal 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>>,
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
|
|
@ -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>;
|
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(¶ms.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),
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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(¶ms.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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(¶meters.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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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(¶meters.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.);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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))
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 => {
|
||||||
|
|
|
||||||
|
|
@ -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!()
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
|
|
|
||||||
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
¶m.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
|
¶m.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
|
¶m.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
|
¶m.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
|
¶m.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
|
¶m.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
|
¶m.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] {
|
||||||
|
¶m.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
|
¶m.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(¶m);
|
||||||
|
|
||||||
if values.is_empty() {
|
if values.is_empty() {
|
||||||
error_exit(
|
error_exit(
|
||||||
¶m.loc,
|
Some(¶m.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(
|
||||||
¶m.loc,
|
Some(¶m.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(
|
||||||
¶m.loc,
|
Some(¶m.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);
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue