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;
|
||||
} 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);
|
||||
|
||||
|
||||
void ptex_filter_eval(
|
||||
PtexFilterHandle filter,
|
||||
float* result,
|
||||
|
|
@ -39,9 +43,9 @@ void ptex_filter_eval(
|
|||
float dudx, float dvdx,
|
||||
float dudy, float dvdy
|
||||
);
|
||||
|
||||
void ptex_filter_release(PtexFilterHandle filter);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -51,4 +51,10 @@ extern "C" {
|
|||
);
|
||||
|
||||
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 {
|
||||
/// Creates a new Ptex filter pointer
|
||||
///
|
||||
/// # Safety
|
||||
pub unsafe fn new(texture_ptr: *mut c_void, opts: &PtexFilterOptions) -> Option<Self> {
|
||||
let handle = ffi::ptex_filter_create(texture_ptr, opts);
|
||||
NonNull::new(handle).map(|h| Self {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use crate::core::scattering::{
|
|||
TrowbridgeReitzDistribution, fr_complex_from_spectrum, fr_dielectric, fresnel_moment1, reflect,
|
||||
refract,
|
||||
};
|
||||
use crate::spectra::{RGBUnboundedSpectrum, SampledSpectrum, StandardColorSpaces};
|
||||
use crate::spectra::{DeviceStandardColorSpaces, RGBUnboundedSpectrum, SampledSpectrum};
|
||||
use crate::utils::math::{
|
||||
clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete,
|
||||
square, trimmed_logistic,
|
||||
|
|
@ -34,7 +34,7 @@ pub struct HairBxDF {
|
|||
pub s: Float,
|
||||
pub sin_2k_alpha: [Float; P_MAX],
|
||||
pub cos_2k_alpha: [Float; P_MAX],
|
||||
pub colorspaces: StandardColorSpaces,
|
||||
pub colorspaces: DeviceStandardColorSpaces,
|
||||
}
|
||||
|
||||
impl HairBxDF {
|
||||
|
|
@ -45,7 +45,7 @@ impl HairBxDF {
|
|||
beta_m: Float,
|
||||
beta_n: Float,
|
||||
alpha: Float,
|
||||
colorspaces: StandardColorSpaces,
|
||||
colorspaces: DeviceStandardColorSpaces,
|
||||
) -> Self {
|
||||
let mut sin_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(
|
||||
ce: Float,
|
||||
cp: Float,
|
||||
stdcs: StandardColorSpaces,
|
||||
stdcs: DeviceStandardColorSpaces,
|
||||
) -> RGBUnboundedSpectrum {
|
||||
let eumelanin_sigma_a = RGB::new(0.419, 0.697, 1.37);
|
||||
let pheomelanin_sigma_a = RGB::new(0.187, 0.4, 1.05);
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ use crate::core::scattering::{
|
|||
refract,
|
||||
};
|
||||
use crate::spectra::{
|
||||
N_SPECTRUM_SAMPLES, RGBColorSpace, RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
|
||||
StandardColorSpaces,
|
||||
DeviceStandardColorSpaces, N_SPECTRUM_SAMPLES, RGBColorSpace, RGBUnboundedSpectrum,
|
||||
SampledSpectrum, SampledWavelengths,
|
||||
};
|
||||
use crate::utils::hash::hash_buffer;
|
||||
use crate::utils::math::{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use std::ops::{
|
|||
use crate::Float;
|
||||
use crate::core::geometry::Point2f;
|
||||
use crate::core::spectrum::Spectrum;
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::find_interval;
|
||||
use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp};
|
||||
|
||||
|
|
@ -1065,8 +1066,9 @@ impl Mul<Float> for Coeffs {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RGBToSpectrumTable {
|
||||
pub z_nodes: *const Float,
|
||||
pub coeffs: *const Coeffs,
|
||||
pub z_nodes: Ptr<Float>,
|
||||
pub coeffs: Ptr<Coeffs>,
|
||||
pub n_nodes: u32,
|
||||
}
|
||||
|
||||
unsafe impl Send for RGBToSpectrumTable {}
|
||||
|
|
@ -1112,7 +1114,8 @@ impl RGBToSpectrumTable {
|
|||
let x = coord_a / z;
|
||||
let y = coord_b / z;
|
||||
|
||||
let z_nodes_slice = unsafe { core::slice::from_raw_parts(self.z_nodes, 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 dz = (z - z_nodes_slice[zi]) / (z_nodes_slice[zi + 1] - z_nodes_slice[zi]);
|
||||
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::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::containers::Array2D;
|
||||
use crate::utils::math::{f16_to_f32, lerp, square};
|
||||
use core::hash;
|
||||
|
|
@ -70,9 +71,9 @@ impl PixelFormat {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Pixels {
|
||||
U8(*const u8),
|
||||
F16(*const u16),
|
||||
F32(*const f32),
|
||||
U8(Ptr<u8>),
|
||||
F16(Ptr<u16>),
|
||||
F32(Ptr<f32>),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
|
|||
|
|
@ -220,18 +220,18 @@ pub struct ShadingGeom {
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct SurfaceInteraction {
|
||||
pub area_light: Ptr<Light>,
|
||||
pub material: Ptr<Material>,
|
||||
pub shape: Ptr<Shape>,
|
||||
pub common: InteractionBase,
|
||||
pub shading: ShadingGeom,
|
||||
pub dpdu: Vector3f,
|
||||
pub dpdv: Vector3f,
|
||||
pub dndu: 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 dpdy: Vector3f,
|
||||
pub face_index: i32,
|
||||
pub dudx: Float,
|
||||
pub dvdx: Float,
|
||||
pub dudy: Float,
|
||||
|
|
@ -609,7 +609,7 @@ impl SurfaceInteraction {
|
|||
dndv: Normal3f,
|
||||
time: Float,
|
||||
flip: bool,
|
||||
face_index: u32,
|
||||
face_index: i32,
|
||||
) -> Self {
|
||||
let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip);
|
||||
si.face_index = face_index;
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ pub struct NormalBumpEvalContext {
|
|||
pub dudy: Float,
|
||||
pub dvdx: Float,
|
||||
pub dvdy: Float,
|
||||
pub face_index: u32,
|
||||
pub face_index: i32,
|
||||
}
|
||||
|
||||
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::containers::Array2D;
|
||||
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,
|
||||
owen_scrambled_radical_inverse, permutation_element, radical_inverse, round_up_pow2,
|
||||
scrambled_radical_inverse, sobol_interval_to_index, sobol_sample,
|
||||
|
|
@ -42,13 +42,13 @@ where
|
|||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct IndependentSampler {
|
||||
pub samples_per_pixel: u32,
|
||||
pub samples_per_pixel: i32,
|
||||
pub seed: u64,
|
||||
pub rng: Rng,
|
||||
}
|
||||
|
||||
impl IndependentSampler {
|
||||
pub fn new(samples_per_pixel: u32, seed: u64) -> Self {
|
||||
pub fn new(samples_per_pixel: i32, seed: u64) -> Self {
|
||||
Self {
|
||||
samples_per_pixel,
|
||||
seed,
|
||||
|
|
@ -58,10 +58,10 @@ impl IndependentSampler {
|
|||
}
|
||||
|
||||
impl SamplerTrait for IndependentSampler {
|
||||
fn samples_per_pixel(&self) -> u32 {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
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 sequence_index = hash_buffer(&hash_input, 0);
|
||||
self.rng.set_sequence(sequence_index);
|
||||
|
|
@ -95,65 +95,18 @@ pub enum RandomizeStrategy {
|
|||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct HaltonSampler {
|
||||
samples_per_pixel: u32,
|
||||
randomize: RandomizeStrategy,
|
||||
base_scales: [u64; 2],
|
||||
base_exponents: [u64; 2],
|
||||
mult_inverse: [u64; 2],
|
||||
halton_index: u64,
|
||||
dim: u32,
|
||||
digit_permutations: Ptr<DigitPermutation>,
|
||||
pub samples_per_pixel: i32,
|
||||
pub randomize: RandomizeStrategy,
|
||||
pub base_scales: [u64; 2],
|
||||
pub base_exponents: [u64; 2],
|
||||
pub mult_inverse: [u64; 2],
|
||||
pub halton_index: u64,
|
||||
pub dim: u32,
|
||||
pub digit_permutations: Ptr<DeviceDigitPermutation>,
|
||||
}
|
||||
|
||||
impl HaltonSampler {
|
||||
// pub fn new(
|
||||
// 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 {
|
||||
pub fn sample_dimension(&self, dimension: u32) -> Float {
|
||||
if self.randomize == RandomizeStrategy::None {
|
||||
radical_inverse(dimension, self.halton_index)
|
||||
} 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);
|
||||
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 {
|
||||
return (1, 0);
|
||||
}
|
||||
|
|
@ -187,11 +140,11 @@ impl HaltonSampler {
|
|||
}
|
||||
|
||||
impl SamplerTrait for HaltonSampler {
|
||||
fn samples_per_pixel(&self) -> u32 {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
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;
|
||||
|
||||
let sample_stride = self.base_scales[0] * self.base_scales[1];
|
||||
|
|
@ -252,20 +205,20 @@ impl SamplerTrait for HaltonSampler {
|
|||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct StratifiedSampler {
|
||||
x_pixel_samples: u32,
|
||||
y_pixel_samples: u32,
|
||||
x_pixel_samples: i32,
|
||||
y_pixel_samples: i32,
|
||||
jitter: bool,
|
||||
seed: u64,
|
||||
rng: Rng,
|
||||
pixel: Point2i,
|
||||
sample_index: u32,
|
||||
sample_index: i32,
|
||||
dim: u32,
|
||||
}
|
||||
|
||||
impl StratifiedSampler {
|
||||
pub fn new(
|
||||
x_pixel_samples: u32,
|
||||
y_pixel_samples: u32,
|
||||
x_pixel_samples: i32,
|
||||
y_pixel_samples: i32,
|
||||
seed: Option<u64>,
|
||||
jitter: bool,
|
||||
) -> Self {
|
||||
|
|
@ -283,11 +236,11 @@ impl 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
|
||||
}
|
||||
|
||||
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.sample_index = sample_index;
|
||||
let hash_input = [p.x() as u64, p.y() as u64, self.seed];
|
||||
|
|
@ -360,16 +313,16 @@ impl SamplerTrait for StratifiedSampler {
|
|||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct PaddedSobolSampler {
|
||||
samples_per_pixel: u32,
|
||||
samples_per_pixel: i32,
|
||||
seed: u64,
|
||||
randomize: RandomizeStrategy,
|
||||
pixel: Point2i,
|
||||
sample_index: u32,
|
||||
sample_index: i32,
|
||||
dim: u32,
|
||||
}
|
||||
|
||||
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 {
|
||||
samples_per_pixel,
|
||||
seed: seed.unwrap_or(0),
|
||||
|
|
@ -398,10 +351,10 @@ impl PaddedSobolSampler {
|
|||
}
|
||||
|
||||
impl SamplerTrait for PaddedSobolSampler {
|
||||
fn samples_per_pixel(&self) -> u32 {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
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.sample_index = sample_index;
|
||||
self.dim = dim.unwrap_or(0);
|
||||
|
|
@ -449,7 +402,7 @@ impl SamplerTrait for PaddedSobolSampler {
|
|||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct SobolSampler {
|
||||
samples_per_pixel: u32,
|
||||
samples_per_pixel: i32,
|
||||
scale: i32,
|
||||
seed: u64,
|
||||
randomize: RandomizeStrategy,
|
||||
|
|
@ -460,7 +413,7 @@ pub struct SobolSampler {
|
|||
|
||||
impl SobolSampler {
|
||||
pub fn new(
|
||||
samples_per_pixel: u32,
|
||||
samples_per_pixel: i32,
|
||||
full_resolution: Point2i,
|
||||
randomize: RandomizeStrategy,
|
||||
seed: Option<u64>,
|
||||
|
|
@ -501,10 +454,10 @@ impl SobolSampler {
|
|||
}
|
||||
|
||||
impl SamplerTrait for SobolSampler {
|
||||
fn samples_per_pixel(&self) -> u32 {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
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.dim = 2.max(dim.unwrap_or(0));
|
||||
self.sobol_index =
|
||||
|
|
@ -557,7 +510,7 @@ impl SamplerTrait for SobolSampler {
|
|||
pub struct ZSobolSampler {
|
||||
randomize: RandomizeStrategy,
|
||||
seed: u64,
|
||||
log2_samples_per_pixel: u32,
|
||||
log2_samples_per_pixel: i32,
|
||||
n_base4_digits: u32,
|
||||
morton_index: u64,
|
||||
dim: u32,
|
||||
|
|
@ -565,7 +518,7 @@ pub struct ZSobolSampler {
|
|||
|
||||
impl ZSobolSampler {
|
||||
pub fn new(
|
||||
samples_per_pixel: u32,
|
||||
samples_per_pixel: i32,
|
||||
full_resolution: Point2i,
|
||||
randomize: RandomizeStrategy,
|
||||
seed: Option<u64>,
|
||||
|
|
@ -573,11 +526,11 @@ impl ZSobolSampler {
|
|||
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 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 {
|
||||
randomize,
|
||||
seed: seed.unwrap_or(0),
|
||||
log2_samples_per_pixel,
|
||||
log2_samples_per_pixel: log2_samples_per_pixel as i32,
|
||||
n_base4_digits,
|
||||
morton_index: 0,
|
||||
dim: 0,
|
||||
|
|
@ -641,10 +594,10 @@ impl ZSobolSampler {
|
|||
}
|
||||
|
||||
impl SamplerTrait for ZSobolSampler {
|
||||
fn samples_per_pixel(&self) -> u32 {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
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.morton_index = (encode_morton_2(p.x() as u32, p.y() as u32)
|
||||
<< self.log2_samples_per_pixel)
|
||||
|
|
@ -710,10 +663,10 @@ impl SamplerTrait for ZSobolSampler {
|
|||
#[derive(Default, Debug, Clone)]
|
||||
pub struct MLTSampler;
|
||||
impl SamplerTrait for MLTSampler {
|
||||
fn samples_per_pixel(&self) -> u32 {
|
||||
fn samples_per_pixel(&self) -> i32 {
|
||||
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!()
|
||||
}
|
||||
fn get1d(&mut self) -> Float {
|
||||
|
|
@ -729,8 +682,8 @@ impl SamplerTrait for MLTSampler {
|
|||
|
||||
#[enum_dispatch]
|
||||
pub trait SamplerTrait {
|
||||
fn samples_per_pixel(&self) -> u32;
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: u32, dim: Option<u32>);
|
||||
fn samples_per_pixel(&self) -> i32;
|
||||
fn start_pixel_sample(&mut self, p: Point2i, sample_index: i32, dim: Option<u32>);
|
||||
fn get1d(&mut self) -> Float;
|
||||
fn get2d(&mut self) -> Point2f;
|
||||
fn get_pixel2d(&mut self) -> Point2f;
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ pub struct TextureEvalContext {
|
|||
pub dudy: Float,
|
||||
pub dvdx: Float,
|
||||
pub dvdy: Float,
|
||||
pub face_index: u32,
|
||||
pub face_index: i32,
|
||||
}
|
||||
|
||||
impl TextureEvalContext {
|
||||
|
|
@ -287,7 +287,7 @@ impl TextureEvalContext {
|
|||
dudy: Float,
|
||||
dvdx: Float,
|
||||
dvdy: Float,
|
||||
face_index: u32,
|
||||
face_index: i32,
|
||||
) -> Self {
|
||||
Self {
|
||||
p,
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ impl DiffuseAreaLight {
|
|||
return false;
|
||||
};
|
||||
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 {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths
|
|||
use crate::spectra::{RGBColorSpace, RGBIlluminantSpectrum};
|
||||
use crate::utils::math::{clamp, equal_area_sphere_to_square, equal_area_square_to_sphere, square};
|
||||
use crate::utils::sampling::{
|
||||
AliasTable, DevicePiecewiseConstant2D, WindowedPiecewiseConstant2D, sample_uniform_sphere,
|
||||
uniform_sphere_pdf,
|
||||
AliasTable, DevicePiecewiseConstant2D, DeviceWindowedPiecewiseConstant2D,
|
||||
sample_uniform_sphere, uniform_sphere_pdf,
|
||||
};
|
||||
use crate::utils::{Ptr, Transform};
|
||||
use crate::{Float, PI};
|
||||
|
|
@ -132,11 +132,11 @@ impl ImageInfiniteLight {
|
|||
for c in 0..3 {
|
||||
rgb[c] = self.image.lookup_nearest_channel_with_wrap(
|
||||
uv,
|
||||
c as i32,
|
||||
c,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -222,11 +222,11 @@ impl LightTrait for ImageInfiniteLight {
|
|||
for c in 0..3 {
|
||||
rgb[c] = self.image.get_channel_with_wrap(
|
||||
Point2i::new(u, v),
|
||||
c as i32,
|
||||
c,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -255,7 +255,7 @@ pub struct PortalInfiniteLight {
|
|||
pub scale: Float,
|
||||
pub portal: [Point3f; 4],
|
||||
pub portal_frame: Frame,
|
||||
pub distribution: WindowedPiecewiseConstant2D,
|
||||
pub distribution: DeviceWindowedPiecewiseConstant2D,
|
||||
pub scene_center: Point3f,
|
||||
pub scene_radius: Float,
|
||||
}
|
||||
|
|
@ -264,9 +264,9 @@ impl PortalInfiniteLight {
|
|||
pub fn image_lookup(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
let mut rgb = RGB::default();
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ pub struct HairMaterial {
|
|||
|
||||
impl HairMaterial {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
sigma_a: Ptr<GPUSpectrumTexture>,
|
||||
color: Ptr<GPUSpectrumTexture>,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ impl TriangleIntersection {
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TriangleShape {
|
||||
pub mesh: DeviceTriangleMesh,
|
||||
pub tri_index: u32,
|
||||
pub tri_index: i32,
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ use std::cmp::{Eq, PartialEq};
|
|||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Debug, Clone)]
|
||||
pub struct StandardColorSpaces {
|
||||
pub struct DeviceStandardColorSpaces {
|
||||
pub srgb: Ptr<RGBColorSpace>,
|
||||
pub dci_p3: Ptr<RGBColorSpace>,
|
||||
pub rec2020: Ptr<RGBColorSpace>,
|
||||
pub aces2065_1: Ptr<RGBColorSpace>,
|
||||
}
|
||||
|
||||
impl StandardColorSpaces {
|
||||
impl DeviceStandardColorSpaces {
|
||||
#[cfg(not(target_arch = "nvptx64"))]
|
||||
pub fn get_named(&self, name: &str) -> Option<Ptr<RGBColorSpace>> {
|
||||
match name.to_lowercase().as_str() {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ pub mod simple;
|
|||
|
||||
use crate::core::pbrt::Float;
|
||||
|
||||
pub use colorspace::{RGBColorSpace, StandardColorSpaces};
|
||||
pub use colorspace::{DeviceStandardColorSpaces, RGBColorSpace};
|
||||
pub use rgb::*;
|
||||
pub use sampled::{CIE_Y_INTEGRAL, LAMBDA_MAX, LAMBDA_MIN};
|
||||
pub use sampled::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths};
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ use crate::spectra::{
|
|||
RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
|
||||
SampledWavelengths,
|
||||
};
|
||||
use crate::utils::Ptr;
|
||||
|
||||
/* GPU heavy code, dont know if this will ever work the way Im doing things.
|
||||
* Leaving it here isolated, for careful handling */
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct GPUSpectrumImageTexture {
|
||||
|
|
|
|||
|
|
@ -14,15 +14,15 @@ pub struct GPUFloatMixTexture {
|
|||
|
||||
impl GPUFloatMixTexture {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
0.0
|
||||
};
|
||||
|
|
@ -43,13 +43,13 @@ impl GPUFloatDirectionMixTexture {
|
|||
pub fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
let amt = self.dir.abs_dot(ctx.n.into());
|
||||
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 {
|
||||
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 {
|
||||
0.0
|
||||
};
|
||||
|
|
@ -72,11 +72,11 @@ impl GPUSpectrumMixTexture {
|
|||
ctx: &TextureEvalContext,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> 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 {
|
||||
self.tex1
|
||||
.get()
|
||||
.map(|t| t.evaluate(&ctx, &lambda))
|
||||
.map(|t| t.evaluate(ctx, lambda))
|
||||
.unwrap_or(SampledSpectrum::new(0.))
|
||||
} else {
|
||||
SampledSpectrum::new(0.)
|
||||
|
|
@ -85,7 +85,7 @@ impl GPUSpectrumMixTexture {
|
|||
let t2 = if amt != 0.0 {
|
||||
self.tex2
|
||||
.get()
|
||||
.map(|t| t.evaluate(&ctx, &lambda))
|
||||
.map(|t| t.evaluate(ctx, lambda))
|
||||
.unwrap_or(SampledSpectrum::new(0.))
|
||||
} else {
|
||||
SampledSpectrum::new(0.)
|
||||
|
|
@ -113,7 +113,7 @@ impl GPUSpectrumDirectionMixTexture {
|
|||
let t1 = if amt != 1.0 {
|
||||
self.tex1
|
||||
.get()
|
||||
.map(|t| t.evaluate(&ctx, &lambda))
|
||||
.map(|t| t.evaluate(ctx, lambda))
|
||||
.unwrap_or(SampledSpectrum::new(0.))
|
||||
} else {
|
||||
SampledSpectrum::new(0.)
|
||||
|
|
@ -122,7 +122,7 @@ impl GPUSpectrumDirectionMixTexture {
|
|||
let t2 = if amt != 0.0 {
|
||||
self.tex2
|
||||
.get()
|
||||
.map(|t| t.evaluate(&ctx, &lambda))
|
||||
.map(|t| t.evaluate(ctx, lambda))
|
||||
.unwrap_or(SampledSpectrum::new(0.))
|
||||
} else {
|
||||
SampledSpectrum::new(0.)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use crate::Float;
|
||||
use crate::core::color::RGB;
|
||||
use crate::core::color::{ColorEncoding, RGB};
|
||||
use crate::core::spectrum::{SpectrumTrait, StandardSpectra};
|
||||
use crate::core::texture::{SpectrumType, TextureEvalContext};
|
||||
use crate::spectra::{
|
||||
RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
|
||||
SampledWavelengths, StandardColorSpaces,
|
||||
DeviceStandardColorSpaces, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum,
|
||||
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths,
|
||||
};
|
||||
use crate::utils::Ptr;
|
||||
|
||||
|
|
@ -23,10 +23,10 @@ impl GPUFloatPtexTexture {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct GPUSpectrumPtexTexture {
|
||||
pub face_values: *const RGB,
|
||||
pub face_values: Ptr<RGB>,
|
||||
pub n_faces: u32,
|
||||
pub spectrum_type: SpectrumType,
|
||||
pub colorspaces: StandardColorSpaces,
|
||||
pub colorspaces: DeviceStandardColorSpaces,
|
||||
}
|
||||
|
||||
impl GPUSpectrumPtexTexture {
|
||||
|
|
@ -35,7 +35,7 @@ impl GPUSpectrumPtexTexture {
|
|||
ctx: &TextureEvalContext,
|
||||
lambda: &SampledWavelengths,
|
||||
) -> 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 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
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Copy, Clone)]
|
||||
pub struct DigitPermutation {
|
||||
pub base: u32,
|
||||
pub struct DeviceDigitPermutation {
|
||||
pub base: i32,
|
||||
pub n_digits: u32,
|
||||
pub permutations: Ptr<u16>,
|
||||
}
|
||||
|
||||
impl DigitPermutation {
|
||||
impl DeviceDigitPermutation {
|
||||
#[inline(always)]
|
||||
pub fn permute(&self, digit_index: i32, digit_value: i32) -> i32 {
|
||||
let idx = (digit_index * self.base as i32 + digit_value) as usize;
|
||||
|
|
@ -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 limit = (u64::MAX / base).saturating_sub(base);
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ use crate::utils::sampling::DevicePiecewiseConstant2D;
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct DeviceTriangleMesh {
|
||||
pub n_triangles: u32,
|
||||
pub n_vertices: u32,
|
||||
pub vertex_indices: Ptr<u32>,
|
||||
pub p: Ptr<Point3f>,
|
||||
pub n: Ptr<Normal3f>,
|
||||
pub s: Ptr<Vector3f>,
|
||||
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 transform_swaps_handedness: bool,
|
||||
}
|
||||
|
|
@ -22,15 +22,15 @@ pub struct DeviceTriangleMesh {
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DeviceBilinearPatchMesh {
|
||||
pub n_patches: u32,
|
||||
pub n_vertices: u32,
|
||||
pub vertex_indices: Ptr<u32>,
|
||||
pub image_distribution: Ptr<DevicePiecewiseConstant2D>,
|
||||
pub p: Ptr<Point3f>,
|
||||
pub n: Ptr<Normal3f>,
|
||||
pub uv: Ptr<Point2f>,
|
||||
pub vertex_indices: Ptr<i32>,
|
||||
pub n_patches: u32,
|
||||
pub n_vertices: u32,
|
||||
pub reverse_orientation: bool,
|
||||
pub transform_swaps_handedness: bool,
|
||||
pub image_distribution: Ptr<DevicePiecewiseConstant2D>,
|
||||
}
|
||||
|
||||
unsafe impl Send for DeviceTriangleMesh {}
|
||||
|
|
|
|||
|
|
@ -772,12 +772,15 @@ impl DevicePiecewiseConstant1D {
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
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 n_u: u32,
|
||||
pub n_v: u32,
|
||||
}
|
||||
|
||||
unsafe impl Send for DevicePiecewiseConstant2D {}
|
||||
unsafe impl Sync for DevicePiecewiseConstant2D {}
|
||||
|
||||
impl DevicePiecewiseConstant2D {
|
||||
// pub fn resolution(&self) -> Point2i {
|
||||
// Point2i::new(
|
||||
|
|
@ -792,7 +795,7 @@ impl DevicePiecewiseConstant2D {
|
|||
|
||||
pub fn sample(&self, u: Point2f) -> (Point2f, f32, Point2i) {
|
||||
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 offset = Point2i::new(off_x as i32, off_y as i32);
|
||||
(Point2f::new(d0, d1), pdf, offset)
|
||||
|
|
@ -817,11 +820,11 @@ impl DevicePiecewiseConstant2D {
|
|||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SummedAreaTable {
|
||||
pub struct DeviceSummedAreaTable {
|
||||
pub sum: Array2D<f64>,
|
||||
}
|
||||
|
||||
impl SummedAreaTable {
|
||||
impl DeviceSummedAreaTable {
|
||||
// pub fn new(values: &Array2D<Float>) -> Self {
|
||||
// let width = values.x_size();
|
||||
// let height = values.y_size();
|
||||
|
|
@ -897,17 +900,12 @@ impl SummedAreaTable {
|
|||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct WindowedPiecewiseConstant2D {
|
||||
sat: SummedAreaTable,
|
||||
func: Array2D<Float>,
|
||||
pub struct DeviceWindowedPiecewiseConstant2D {
|
||||
pub sat: DeviceSummedAreaTable,
|
||||
pub func: Array2D<Float>,
|
||||
}
|
||||
|
||||
impl WindowedPiecewiseConstant2D {
|
||||
// pub fn new(func: Array2D<Float>) -> Self {
|
||||
// let sat = SummedAreaTable::new(&func);
|
||||
// Self { sat, func }
|
||||
// }
|
||||
|
||||
impl DeviceWindowedPiecewiseConstant2D {
|
||||
pub fn sample(&self, u: Point2f, b: Bounds2f) -> Option<(Point2f, Float)> {
|
||||
let b_int = self.sat.integral(b);
|
||||
if b_int == 0.0 {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use std::path::Path;
|
|||
use std::sync::Arc;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CameraBaseParameters {
|
||||
pub camera_transform: CameraTransform,
|
||||
pub shutter_open: Float,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::utils::read_float_file;
|
||||
use anyhow::Error;
|
||||
use shared::Float;
|
||||
use shared::core::color::{Coeffs, RES, RGBToSpectrumTable};
|
||||
use std::ops::Deref;
|
||||
|
|
@ -25,7 +26,8 @@ impl RGBToSpectrumTableData {
|
|||
|
||||
let view = RGBToSpectrumTable {
|
||||
z_nodes: z_nodes.as_ptr(),
|
||||
coeffs: coeffs.as_ptr() as *const Coeffs,
|
||||
coeffs: coeffs.as_ptr().into(),
|
||||
n_nodes: z_nodes.len(),
|
||||
};
|
||||
|
||||
Self {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use super::{Image, ImageAndMetadata, ImageMetadata};
|
||||
use crate::core::image::{PixelStorage, WrapMode};
|
||||
use crate::utils::error::ImageError;
|
||||
use anyhow::Error;
|
||||
use anyhow::{Context, Result, bail};
|
||||
use exr::prelude::{read_first_rgba_layer_from_file, write_rgba_file};
|
||||
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 ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
|
||||
let res = match ext.to_lowercase().as_str() {
|
||||
|
|
|
|||
|
|
@ -48,3 +48,9 @@ pub struct ImageMetadata {
|
|||
pub strings: HashMap<String, 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 shared::Float;
|
||||
use shared::core::color::ColorEncoding;
|
||||
|
|
@ -98,12 +99,21 @@ impl PixelStorage {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Image {
|
||||
storage: PixelStorage,
|
||||
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)]
|
||||
pub struct ImageAndMetadata {
|
||||
pub image: Image,
|
||||
|
|
@ -115,7 +125,7 @@ impl Image {
|
|||
fn from_storage(
|
||||
storage: PixelStorage,
|
||||
resolution: Point2i,
|
||||
channel_names: Vec<String>,
|
||||
channel_names: &[&str],
|
||||
encoding: ColorEncoding,
|
||||
) -> Self {
|
||||
let n_channels = channel_names.len() as i32;
|
||||
|
|
@ -142,7 +152,7 @@ impl Image {
|
|||
pub fn from_u8(
|
||||
data: Vec<u8>,
|
||||
resolution: Point2i,
|
||||
channel_names: Vec<String>,
|
||||
channel_names: &[&str],
|
||||
encoding: ColorEncoding,
|
||||
) -> Self {
|
||||
Self::from_storage(
|
||||
|
|
@ -156,7 +166,7 @@ impl Image {
|
|||
pub fn from_u8(
|
||||
data: Vec<u8>,
|
||||
resolution: Point2i,
|
||||
channel_names: Vec<String>,
|
||||
channel_names: &[&str],
|
||||
encoding: ColorEncoding,
|
||||
) -> Self {
|
||||
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(
|
||||
PixelStorage::F16(data.into_boxed_slice()),
|
||||
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(
|
||||
PixelStorage::F32(data.into_boxed_slice()),
|
||||
resolution,
|
||||
|
|
@ -189,7 +199,7 @@ impl Image {
|
|||
format: PixelFormat,
|
||||
resolution: Point2i,
|
||||
channel_names: &[&str],
|
||||
encoding: *const ColorEncoding,
|
||||
encoding: Arc<ColorEncoding>,
|
||||
) -> Self {
|
||||
let n_channels = channel_names.len();
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
pub fn device_image(&self) -> &DeviceImage {
|
||||
&self.device
|
||||
|
|
@ -365,10 +398,7 @@ impl Image {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_channel_desc(
|
||||
&self,
|
||||
requested_channels: &[&str],
|
||||
) -> Result<ImageChannelDesc, String> {
|
||||
pub fn get_channel_desc(&self, requested_channels: &[&str]) -> Result<ImageChannelDesc> {
|
||||
let mut offset = Vec::with_capacity(requested_channels.len());
|
||||
|
||||
for &req in requested_channels.iter() {
|
||||
|
|
@ -562,6 +592,23 @@ impl Image {
|
|||
}
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use crate::Arena;
|
||||
use crate::utils::{FileLoc, ParameterDictionary};
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::sampler::{
|
||||
HaltonSampler, IndependentSampler, PaddedSobolSampler, Sampler, SobolSampler,
|
||||
StratifiedSampler, ZSobolSampler,
|
||||
};
|
||||
use std::fmt::Error;
|
||||
|
||||
pub trait CreateSampler {
|
||||
fn create(
|
||||
|
|
@ -13,7 +13,7 @@ pub trait CreateSampler {
|
|||
full_res: Point2i,
|
||||
loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Self, Error>;
|
||||
) -> Result<Self>;
|
||||
}
|
||||
|
||||
pub trait SamplerFactory {
|
||||
|
|
@ -23,7 +23,7 @@ pub trait SamplerFactory {
|
|||
full_res: Point2i,
|
||||
loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Self, Error>;
|
||||
) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl SamplerFactory for Sampler {
|
||||
|
|
@ -33,7 +33,7 @@ impl SamplerFactory for Sampler {
|
|||
full_res: Point2i,
|
||||
loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Self, Error> {
|
||||
) -> Result<Self> {
|
||||
match name {
|
||||
"zsobol" => {
|
||||
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)?;
|
||||
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),
|
||||
}
|
||||
|
||||
impl FloatTextureTrait for Arc<FloatTexture> {
|
||||
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
self.as_ref().evaluate(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl FloatTexture {
|
||||
pub fn create(
|
||||
name: &str,
|
||||
|
|
@ -119,10 +125,16 @@ pub enum SpectrumTexture {
|
|||
Mix(SpectrumMixTexture),
|
||||
DirectionMix(SpectrumDirectionMixTexture),
|
||||
Dots(SpectrumDotsTexture),
|
||||
Ptex(SpectrumPtexTexture),
|
||||
// Ptex(SpectrumPtexTexture),
|
||||
Scaled(SpectrumScaledTexture),
|
||||
}
|
||||
|
||||
impl SpectrumTextureTrait for Arc<SpectrumTexture> {
|
||||
fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
self.as_ref().evaluate(ctx, lambda)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CreateTextureMapping {
|
||||
fn create(
|
||||
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;
|
||||
#[cfg(feature = "use_gpu")]
|
||||
mod memory;
|
||||
#[cfg(feature = "use_gpu")]
|
||||
|
||||
pub use context::{GpuState, gpu_init, gpu_state, gpu_state_or_panic, gpu_thread_init};
|
||||
|
||||
pub mod wavefront;
|
||||
|
||||
#[cfg(feature = "use_gpu")]
|
||||
pub use context::{GpuContext, GpuError, gpu, gpu_init, gpu_unwrap};
|
||||
#[cfg(feature = "use_gpu")]
|
||||
pub use memory::UnifiedBuffer;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
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::light::{CreateLight, lookup_spectrum};
|
||||
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 log::error;
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::light::{Light, LightBase, LightType};
|
||||
use shared::core::medium::{Medium, MediumInterface};
|
||||
use shared::core::shape::Shape;
|
||||
use shared::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use shared::core::shape::{Shape, ShapeTrait};
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::{SpectrumType, TextureEvalContext};
|
||||
use shared::lights::DiffuseAreaLight;
|
||||
use shared::spectra::RGBColorSpace;
|
||||
use shared::utils::{Ptr, Transform};
|
||||
use shared::{Float, PI};
|
||||
use std::fmt::Error;
|
||||
|
||||
pub trait CreateDiffuseLight {
|
||||
fn new(
|
||||
render_from_light: shared::utils::Transform,
|
||||
render_from_light: Transform,
|
||||
medium_interface: MediumInterface,
|
||||
le: Spectrum,
|
||||
scale: Float,
|
||||
shape: Ptr<Shape>,
|
||||
alpha: Ptr<FloatTexture>,
|
||||
image: Ptr<Image>,
|
||||
colorspace: Option<RGBColorSpace>,
|
||||
alpha: Ptr<GPUFloatTexture>,
|
||||
image: Ptr<DeviceImage>,
|
||||
colorspace: Ptr<RGBColorSpace>,
|
||||
two_sided: bool,
|
||||
fov: Float,
|
||||
) -> Self;
|
||||
|
|
@ -33,19 +34,19 @@ pub trait CreateDiffuseLight {
|
|||
|
||||
impl CreateDiffuseLight for DiffuseAreaLight {
|
||||
fn new(
|
||||
render_from_light: shared::utils::Transform,
|
||||
render_from_light: Transform,
|
||||
medium_interface: MediumInterface,
|
||||
le: Spectrum,
|
||||
scale: Float,
|
||||
shape: Ptr<Shape>,
|
||||
alpha: Ptr<FloatTexture>,
|
||||
image: Ptr<Image>,
|
||||
colorspace: Option<RGBColorSpace>,
|
||||
alpha: Ptr<GPUFloatTexture>,
|
||||
image: Ptr<DeviceImage>,
|
||||
colorspace: Ptr<RGBColorSpace>,
|
||||
two_sided: bool,
|
||||
fov: Float,
|
||||
) -> Self {
|
||||
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,
|
||||
};
|
||||
|
||||
|
|
@ -55,11 +56,12 @@ impl CreateDiffuseLight for DiffuseAreaLight {
|
|||
(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);
|
||||
if let Some(im) = &image {
|
||||
let desc = im
|
||||
let lemit = Ptr::from(&lookup_spectrum(&le));
|
||||
|
||||
if !image.is_null() {
|
||||
let desc = image
|
||||
.get_channel_desc(&["R", "G", "B"])
|
||||
.expect("Image used for DiffuseAreaLight doesn't have R, G, B channels");
|
||||
|
||||
|
|
@ -70,11 +72,13 @@ impl CreateDiffuseLight for DiffuseAreaLight {
|
|||
);
|
||||
|
||||
assert!(
|
||||
colorspace.is_some(),
|
||||
!colorspace.is_null(),
|
||||
"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 {
|
||||
println!(
|
||||
"Scaling detected in rendering to light space transformation! \
|
||||
|
|
@ -108,44 +112,49 @@ impl CreateLight for DiffuseAreaLight {
|
|||
shape: &Shape,
|
||||
alpha: &FloatTexture,
|
||||
colorspace: Option<&RGBColorSpace>,
|
||||
) -> Result<Light, Error> {
|
||||
) -> Result<Light> {
|
||||
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 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() {
|
||||
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() {
|
||||
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() {
|
||||
return Err(error!(loc, "{}: image has NaN pixel values", filename));
|
||||
return Err(anyhow!(loc, "{}: image has NaN pixel values", filename));
|
||||
}
|
||||
|
||||
let channel_desc = im
|
||||
.image
|
||||
.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 cs = im.metadata.get_color_space();
|
||||
let cs = im.metadata.get_colorspace();
|
||||
|
||||
(Some(image), Some(cs))
|
||||
(Some(image), cs)
|
||||
} else {
|
||||
if l.is_none() {
|
||||
l = Some(colorspace.illuminant.clone());
|
||||
l = Some(illum_spec);
|
||||
}
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let l_for_scale = l.as_ref().unwrap_or(&colorspace.illuminant);
|
||||
scale /= spectrum_to_photometric(l_for_scale);
|
||||
let l_for_scale = l.as_ref().unwrap_or(&illum_spec);
|
||||
scale /= spectrum_to_photometric(*l_for_scale);
|
||||
|
||||
let phi_v = params.get_one_float("power", -1.0);
|
||||
if phi_v > 0.0 {
|
||||
|
|
@ -158,13 +167,13 @@ impl CreateLight for DiffuseAreaLight {
|
|||
|
||||
if let Some(ref img) = image {
|
||||
// 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 res = img.resolution;
|
||||
let res = img.resolution();
|
||||
|
||||
for y in 0..res.y {
|
||||
for x in 0..res.x {
|
||||
for y in 0..res.y() {
|
||||
for x in 0..res.x() {
|
||||
let r = img.get_channel(Point2i::new(x, y), 0);
|
||||
let g = img.get_channel(Point2i::new(x, y), 1);
|
||||
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];
|
||||
}
|
||||
}
|
||||
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 };
|
||||
|
|
@ -185,7 +194,7 @@ impl CreateLight for DiffuseAreaLight {
|
|||
let specific = DiffuseAreaLight::new(
|
||||
render_from_light,
|
||||
medium.into(),
|
||||
l.as_ref(),
|
||||
*l_for_scale,
|
||||
scale,
|
||||
shape.upload(arena),
|
||||
alpha.upload(arena),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::core::spectrum::spectrum_to_photometric;
|
|||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||
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::medium::{Medium, MediumInterface};
|
||||
use shared::core::shape::Shape;
|
||||
|
|
@ -25,12 +25,12 @@ impl CreateDistantLight for DistantLight {
|
|||
render_from_light,
|
||||
MediumInterface::empty(),
|
||||
);
|
||||
let lemit = lookup_spectrum(le);
|
||||
let lemit = Ptr::from(&lookup_spectrum(&le));
|
||||
Self {
|
||||
base,
|
||||
lemit,
|
||||
scale,
|
||||
scene_center: Vector3f::default(),
|
||||
scene_center: Point3f::default(),
|
||||
scene_radius: 0.,
|
||||
}
|
||||
}
|
||||
|
|
@ -38,23 +38,23 @@ impl CreateDistantLight for DistantLight {
|
|||
|
||||
impl CreateLight for DistantLight {
|
||||
fn create(
|
||||
arena: &mut Arena,
|
||||
_arena: &mut Arena,
|
||||
render_from_light: Transform,
|
||||
medium: Medium,
|
||||
_medium: Medium,
|
||||
parameters: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
shape: &Shape,
|
||||
alpha_text: &FloatTexture,
|
||||
_loc: &FileLoc,
|
||||
_shape: &Shape,
|
||||
_alpha_text: &FloatTexture,
|
||||
colorspace: Option<&RGBColorSpace>,
|
||||
) -> Result<Light, Error> {
|
||||
let l = parameters
|
||||
.get_one_spectrum(
|
||||
"L",
|
||||
colorspace.unwrap().illuminant,
|
||||
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
||||
SpectrumType::Illuminant,
|
||||
)
|
||||
.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 to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.));
|
||||
|
|
@ -78,12 +78,10 @@ impl CreateLight for DistantLight {
|
|||
0.,
|
||||
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;
|
||||
scale /= spectrum_to_photometric(l.unwrap());
|
||||
scale /= spectrum_to_photometric(l);
|
||||
// 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.);
|
||||
if e_v > 0. {
|
||||
scale *= e_v;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::core::image::{Image, ImageIO};
|
||||
use crate::core::light::{CreateLight, lookup_spectrum};
|
||||
use crate::core::spectrum::spectrum_to_photometric;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary, resolve_filename};
|
||||
use log::error;
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::core::color::ColorEncoding;
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::image::PixelFormat;
|
||||
|
|
@ -17,7 +19,6 @@ use shared::lights::GoniometricLight;
|
|||
use shared::spectra::RGBColorSpace;
|
||||
use shared::utils::{Ptr, Transform};
|
||||
use shared::{Float, PI};
|
||||
use std::fmt::Error;
|
||||
|
||||
pub trait CreateGoniometricLight {
|
||||
fn new(
|
||||
|
|
@ -43,15 +44,15 @@ impl CreateGoniometricLight for GoniometricLight {
|
|||
medium_interface,
|
||||
);
|
||||
|
||||
let iemit = lookup_spectrum(le);
|
||||
let d = image.unwrap().get_sampling_distribution_uniform();
|
||||
let distrib = PiecewiseConstant2D::new_with_data(d);
|
||||
let iemit = lookup_spectrum(&le);
|
||||
// let d = image.get_sampling_distribution_uniform();
|
||||
let distrib = PiecewiseConstant2D::from_image(&image);
|
||||
Self {
|
||||
base,
|
||||
iemit,
|
||||
scale,
|
||||
image,
|
||||
distrib,
|
||||
image: Ptr::from(image.device_image()),
|
||||
distrib: Ptr::from(&distrib.device),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,18 +68,19 @@ impl CreateLight for GoniometricLight {
|
|||
alpha_text: &FloatTexture,
|
||||
colorspace: Option<&RGBColorSpace>,
|
||||
) -> Result<Light, Error> {
|
||||
let i = params.get_one_spectrum(
|
||||
let i = params
|
||||
.get_one_spectrum(
|
||||
"I",
|
||||
colorspace.unwrap().illuminant,
|
||||
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
||||
SpectrumType::Illuminant,
|
||||
);
|
||||
)
|
||||
.expect("Could not retrieve spectrum");
|
||||
let mut scale = params.get_one_float("scale", 1.);
|
||||
let filename = resolve_filename(params.get_one_string("filename", ""));
|
||||
let mut image: Option<Image> = None;
|
||||
let image = if filename.is_empty() {
|
||||
None
|
||||
let filename = resolve_filename(¶ms.get_one_string("filename", ""));
|
||||
let image: Ptr<Image> = if filename.is_empty() {
|
||||
Ptr::null()
|
||||
} 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))?;
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
let swap_yz: [Float; 16] = [
|
||||
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 specific =
|
||||
|
|
@ -126,11 +127,7 @@ impl CreateLight for GoniometricLight {
|
|||
}
|
||||
}
|
||||
|
||||
fn convert_to_luminance_image(
|
||||
image: &Image,
|
||||
filename: &str,
|
||||
loc: &FileLoc,
|
||||
) -> Result<Image, Error> {
|
||||
fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> Result<Image> {
|
||||
let res = image.resolution();
|
||||
let rgb_desc = image.get_channel_desc(&["R", "G", "B"]);
|
||||
let y_desc = image.get_channel_desc(&["Y"]);
|
||||
|
|
@ -143,10 +140,10 @@ fn convert_to_luminance_image(
|
|||
|
||||
(Ok(_), Err(_)) => {
|
||||
// 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 x in 0..res.x {
|
||||
for y in 0..res.y() {
|
||||
for x in 0..res.x() {
|
||||
let r = image.get_channel(Point2i::new(x, y), 0);
|
||||
let g = image.get_channel(Point2i::new(x, y), 1);
|
||||
let b = image.get_channel(Point2i::new(x, y), 2);
|
||||
|
|
@ -154,17 +151,11 @@ fn convert_to_luminance_image(
|
|||
}
|
||||
}
|
||||
|
||||
Ok(Image::from_pixels(
|
||||
PixelFormat::F32,
|
||||
res,
|
||||
&["Y"],
|
||||
ColorEncoding::Linear,
|
||||
&y_pixels,
|
||||
))
|
||||
Ok(Image::from_f32(y_pixels, res, &["Y"].to_vec()))
|
||||
}
|
||||
|
||||
(Err(_), Ok(_)) => {
|
||||
// Already has Y channel, use as-is
|
||||
// Already has Y channel
|
||||
Ok(image.clone())
|
||||
}
|
||||
|
||||
|
|
@ -179,11 +170,11 @@ fn compute_emissive_power(image: &Image) -> Float {
|
|||
let res = image.resolution();
|
||||
let mut sum_y = 0.0;
|
||||
|
||||
for y in 0..res.y {
|
||||
for x in 0..res.x {
|
||||
for y in 0..res.y() {
|
||||
for x in 0..res.x() {
|
||||
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::core::image::Image;
|
||||
use crate::core::image::{Image, ImageIO};
|
||||
use crate::core::spectrum::spectrum_to_photometric;
|
||||
use crate::utils::{FileLoc, ParameterDictionary, resolve_filename};
|
||||
use log::error;
|
||||
use crate::spectra::{get_colorspace_context, get_spectra_context};
|
||||
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::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::light::{Light, LightBase, LightType};
|
||||
use shared::core::medium::MediumInterface;
|
||||
|
|
@ -14,10 +19,9 @@ use shared::lights::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLig
|
|||
use shared::spectra::RGBColorSpace;
|
||||
use shared::utils::hash::hash_float;
|
||||
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::{Float, PI};
|
||||
use std::fmt::Error;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::core::light::lookup_spectrum;
|
||||
|
|
@ -27,8 +31,8 @@ pub trait CreateImageInfiniteLight {
|
|||
render_from_light: Transform,
|
||||
medium_interface: MediumInterface,
|
||||
scale: Float,
|
||||
image: Ptr<Image>,
|
||||
image_color_space: Ptr<RGBColorSpace>,
|
||||
image: Arc<DeviceImage>,
|
||||
image_color_space: Arc<RGBColorSpace>,
|
||||
) -> Self;
|
||||
}
|
||||
|
||||
|
|
@ -37,13 +41,13 @@ impl CreateImageInfiniteLight for ImageInfiniteLight {
|
|||
render_from_light: Transform,
|
||||
medium_interface: MediumInterface,
|
||||
scale: Float,
|
||||
image: Ptr<Image>,
|
||||
image_color_space: Ptr<RGBColorSpace>,
|
||||
image: Arc<Image>,
|
||||
image_color_space: Arc<RGBColorSpace>,
|
||||
) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::Infinite,
|
||||
&render_from_light,
|
||||
&MediumInterface::default(),
|
||||
render_from_light,
|
||||
MediumInterface::default(),
|
||||
);
|
||||
|
||||
let desc = image
|
||||
|
|
@ -52,52 +56,62 @@ impl CreateImageInfiniteLight for ImageInfiniteLight {
|
|||
|
||||
assert_eq!(3, desc.size());
|
||||
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() {
|
||||
*v = (*v - average).max(0.0);
|
||||
all_zero &= *v == 0.0;
|
||||
}
|
||||
|
||||
let all_zero = slice.iter().all(|&v| v == 0.0);
|
||||
if all_zero {
|
||||
for v in slice.iter_mut() {
|
||||
*v = 1.0;
|
||||
}
|
||||
data.fill(1.0);
|
||||
}
|
||||
|
||||
let compensated_distrib = DevicePiecewiseConstant2D::new_with_bounds(&d, domain);
|
||||
let compensated_distrib = PiecewiseConstant2D::new(&data, n_u, n_v);
|
||||
|
||||
ImageInfiniteLight {
|
||||
base,
|
||||
image: &image,
|
||||
image_color_space,
|
||||
image: Ptr::from(image.device_image()),
|
||||
image_color_space: Ptr::from(image_color_space.as_ref()),
|
||||
scene_center: Point3f::default(),
|
||||
scene_radius: 0.,
|
||||
scale,
|
||||
distrib: &distrib,
|
||||
compensated_distrib: &compensated_distrib,
|
||||
};
|
||||
distrib: Ptr::from(&*distrib),
|
||||
compensated_distrib: Ptr::from(&*compensated_distrib),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InfinitePortalLightStorage {
|
||||
image: Image,
|
||||
distribution: WindowedPiecewiseConstant2D,
|
||||
distribution: DeviceWindowedPiecewiseConstant2D,
|
||||
image_color_space: RGBColorSpace,
|
||||
}
|
||||
|
||||
|
|
@ -112,9 +126,9 @@ pub trait CreatePortalInfiniteLight {
|
|||
fn new(
|
||||
render_from_light: Transform,
|
||||
scale: Float,
|
||||
image: Ptr<Image>,
|
||||
image_color_space: Ptr<RGBColorSpace>,
|
||||
points: Ptr<Point3f>,
|
||||
image: Arc<Image>,
|
||||
image_color_space: Arc<RGBColorSpace>,
|
||||
points: Vec<Point3f>,
|
||||
) -> Self;
|
||||
}
|
||||
|
||||
|
|
@ -122,14 +136,14 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight {
|
|||
fn new(
|
||||
render_from_light: Transform,
|
||||
scale: Float,
|
||||
image: Ptr<Image>,
|
||||
image_color_space: Ptr<RGBColorSpace>,
|
||||
points: Ptr<Point3f>,
|
||||
image: Arc<Image>,
|
||||
image_color_space: Arc<RGBColorSpace>,
|
||||
points: Vec<Point3f>,
|
||||
) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::Infinite,
|
||||
&render_from_light,
|
||||
&MediumInterface::default(),
|
||||
render_from_light,
|
||||
MediumInterface::default(),
|
||||
);
|
||||
|
||||
let desc = image
|
||||
|
|
@ -139,7 +153,7 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight {
|
|||
});
|
||||
|
||||
assert_eq!(3, desc.offset.len());
|
||||
let src_res = image.resolution;
|
||||
let src_res = image.resolution();
|
||||
if src_res.x() != src_res.y() {
|
||||
panic!(
|
||||
"Image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.",
|
||||
|
|
@ -205,12 +219,17 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight {
|
|||
c,
|
||||
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 (_, 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.)),
|
||||
);
|
||||
|
||||
let distribution = WindowedPiecewiseConstant2D::new(d);
|
||||
let distribution = DeviceWindowedPiecewiseConstant2D::new(d);
|
||||
|
||||
PortalInfiniteLight {
|
||||
base,
|
||||
image: img,
|
||||
image_color_space: &image_color_space,
|
||||
image: Ptr::from(img.device_image()),
|
||||
image_color_space: Ptr::from(&*image_color_space),
|
||||
scale,
|
||||
scene_center: Point3f::default(),
|
||||
scene_radius: 0.,
|
||||
|
|
@ -246,10 +265,10 @@ impl CreateUniformInfiniteLight for UniformInfiniteLight {
|
|||
fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::Infinite,
|
||||
&render_from_light,
|
||||
&MediumInterface::default(),
|
||||
render_from_light,
|
||||
MediumInterface::default(),
|
||||
);
|
||||
let lemit = lookup_spectrum(le);
|
||||
let lemit = Ptr::from(&lookup_spectrum(&le));
|
||||
Self {
|
||||
base,
|
||||
lemit,
|
||||
|
|
@ -268,11 +287,11 @@ pub fn create(
|
|||
parameters: &ParameterDictionary,
|
||||
colorspace: &RGBColorSpace,
|
||||
loc: &FileLoc,
|
||||
) -> Result<Light, Error> {
|
||||
) -> Result<Light> {
|
||||
let l = parameters.get_spectrum_array("L", SpectrumType::Illuminant);
|
||||
let mut scale = parameters.get_one_float("scale", 1.0);
|
||||
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 has_spectrum = !l.is_empty();
|
||||
|
|
@ -280,22 +299,22 @@ pub fn create(
|
|||
let has_portal = !portal.is_empty();
|
||||
|
||||
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 {
|
||||
let spectrum = if has_spectrum {
|
||||
scale /= spectrum_to_photometric(&l[0]);
|
||||
&l[0]
|
||||
scale /= spectrum_to_photometric(l[0]);
|
||||
l[0]
|
||||
} else {
|
||||
&colorspace.illuminant
|
||||
Spectrum::Dense(colorspace.illuminant)
|
||||
};
|
||||
|
||||
if e_v > 0.0 {
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
@ -303,32 +322,28 @@ pub fn create(
|
|||
|
||||
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 {
|
||||
let k_e = compute_hemisphere_illuminance(&image, &image_cs);
|
||||
scale *= e_v / k_e;
|
||||
}
|
||||
|
||||
let image_ptr = image.upload(arena);
|
||||
let cs_ptr = image_cs.upload(arena);
|
||||
// let image_ptr = image.upload(arena);
|
||||
// let cs_ptr = image_cs.upload(arena);
|
||||
|
||||
if has_portal {
|
||||
let portal_render: Vec<Point3f> = portal
|
||||
.iter()
|
||||
.map(|p| camera_transform.render_from_world(*p))
|
||||
.map(|p| camera_transform.camera_from_world(0.0).apply_to_point(*p))
|
||||
.collect();
|
||||
|
||||
let light = PortalInfiniteLight::new(
|
||||
render_from_light,
|
||||
scale,
|
||||
image_ptr,
|
||||
cs_ptr,
|
||||
arena.alloc_slice(&portal_render),
|
||||
);
|
||||
let (portal_ptr, portal_len) = arena.alloc_slice(&portal_render);
|
||||
let light =
|
||||
PortalInfiniteLight::new(render_from_light, scale, image.into(), cs, portal_render);
|
||||
Ok(Light::InfinitePortal(light))
|
||||
} 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))
|
||||
}
|
||||
}
|
||||
|
|
@ -338,28 +353,28 @@ fn load_image_or_constant(
|
|||
l: &[Spectrum],
|
||||
colorspace: &RGBColorSpace,
|
||||
loc: &FileLoc,
|
||||
) -> Result<(Image, RGBColorSpace), Error> {
|
||||
) -> Result<(Image, RGBColorSpace)> {
|
||||
if filename.is_empty() {
|
||||
let rgb = &l[0].to_rgb();
|
||||
let image = Image::new_constant(Point2i::new(1, 1), &["R", "G", "B"], &rgb);
|
||||
let stdspec = get_spectra_context();
|
||||
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()))
|
||||
} else {
|
||||
let im = Image::read(filename, None)
|
||||
.map_err(|e| error!(loc, "failed to load '{}': {}", filename, e))?;
|
||||
let im = Image::read(Path::new(&filename), None)
|
||||
.map_err(|e| anyhow!(loc, "failed to load '{}': {}", filename, e))?;
|
||||
|
||||
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
|
||||
.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
|
||||
.metadata
|
||||
.color_space
|
||||
.unwrap_or_else(|| colorspace.clone());
|
||||
let selected = im.image.select_channels(&["R", "G", "B"])?;
|
||||
let cs = im.metadata.colorspace.unwrap_or_else(|| colorspace.clone());
|
||||
let image_desc = im.image.get_channel_desc(&["R", "G", "B"])?;
|
||||
let selected = im.image.select_channels(&image_desc);
|
||||
|
||||
Ok((selected, cs))
|
||||
}
|
||||
|
|
@ -370,13 +385,13 @@ fn compute_hemisphere_illuminance(image: &Image, cs: &RGBColorSpace) -> Float {
|
|||
let res = image.resolution();
|
||||
let mut sum = 0.0;
|
||||
|
||||
for y in 0..res.y {
|
||||
let v = (y as Float + 0.5) / res.y as Float;
|
||||
for x in 0..res.x {
|
||||
let u = (x as Float + 0.5) / res.x as Float;
|
||||
for y in 0..res.y() {
|
||||
let v = (y as Float + 0.5) / res.y() as Float;
|
||||
for x in 0..res.x() {
|
||||
let u = (x as Float + 0.5) / res.x() as Float;
|
||||
let w = equal_area_square_to_sphere(Point2f::new(u, v));
|
||||
|
||||
if w.z <= 0.0 {
|
||||
if w.z() <= 0.0 {
|
||||
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 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,
|
||||
medium_interface,
|
||||
);
|
||||
let i = lookup_spectrum(le);
|
||||
let i = Ptr::from(lookup_spectrum(&le));
|
||||
|
||||
Self { base, scale, i }
|
||||
}
|
||||
|
|
@ -43,24 +43,24 @@ impl CreatePointLight for PointLight {
|
|||
|
||||
impl CreateLight for PointLight {
|
||||
fn create(
|
||||
arena: &mut Arena,
|
||||
_arena: &mut Arena,
|
||||
render_from_light: Transform,
|
||||
medium: Medium,
|
||||
parameters: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
shape: &Shape,
|
||||
alpha_text: &FloatTexture,
|
||||
_loc: &FileLoc,
|
||||
_shape: &Shape,
|
||||
_alpha: &FloatTexture,
|
||||
colorspace: Option<&RGBColorSpace>,
|
||||
) -> Result<Light, Error> {
|
||||
let l = parameters
|
||||
.get_one_spectrum(
|
||||
"L",
|
||||
colorspace.unwrap().illuminant,
|
||||
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
||||
SpectrumType::Illuminant,
|
||||
)
|
||||
.unwrap();
|
||||
let mut scale = parameters.get_one_float("scale", 1);
|
||||
scale /= spectrum_to_photometric(l.unwrap());
|
||||
let mut scale = parameters.get_one_float("scale", 1.);
|
||||
scale /= spectrum_to_photometric(l);
|
||||
let phi_v = parameters.get_one_float("power", 1.);
|
||||
if phi_v > 0. {
|
||||
let k_e = 4. * PI;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::core::spectrum::spectrum_to_photometric;
|
|||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename};
|
||||
use log::error;
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::Float;
|
||||
use shared::core::geometry::{
|
||||
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::medium::{Medium, MediumInterface};
|
||||
use shared::core::shape::Shape;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::lights::ProjectionLight;
|
||||
use shared::spectra::RGBColorSpace;
|
||||
use shared::utils::math::{radians, square};
|
||||
use shared::utils::{Ptr, Transform};
|
||||
use std::fmt::Error;
|
||||
use std::path::Path;
|
||||
|
||||
pub trait CreateProjectionLight {
|
||||
fn new(
|
||||
render_from_light: Transform,
|
||||
medium_interface: MediumInterface,
|
||||
scale: Float,
|
||||
image: Ptr<Image>,
|
||||
image: Ptr<DeviceImage>,
|
||||
image_color_space: Ptr<RGBColorSpace>,
|
||||
fov: Float,
|
||||
) -> Self;
|
||||
|
|
@ -34,7 +35,7 @@ impl CreateProjectionLight for ProjectionLight {
|
|||
render_from_light: Transform,
|
||||
medium_interface: MediumInterface,
|
||||
scale: Float,
|
||||
image: Ptr<Image>,
|
||||
image: Ptr<DeviceImage>,
|
||||
image_color_space: Ptr<RGBColorSpace>,
|
||||
fov: Float,
|
||||
) -> Self {
|
||||
|
|
@ -54,9 +55,9 @@ impl CreateProjectionLight for ProjectionLight {
|
|||
};
|
||||
|
||||
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 opposite = (radians(fov.unwrap()) / 2.).tan();
|
||||
let opposite = (radians(fov) / 2.).tan();
|
||||
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
|
||||
let a = 4. * square(opposite) * aspect_ratio;
|
||||
let dwda = |p: Point2f| {
|
||||
|
|
@ -66,7 +67,7 @@ impl CreateProjectionLight for ProjectionLight {
|
|||
};
|
||||
|
||||
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 {
|
||||
base,
|
||||
|
|
@ -93,47 +94,49 @@ impl CreateLight for ProjectionLight {
|
|||
_shape: &Shape,
|
||||
_alpha_text: &FloatTexture,
|
||||
colorspace: Option<&RGBColorSpace>,
|
||||
) -> Result<Light, Error> {
|
||||
) -> Result<Light> {
|
||||
let mut scale = parameters.get_one_float("scale", 1.);
|
||||
let power = parameters.get_one_float("power", -1.);
|
||||
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() {
|
||||
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))?;
|
||||
|
||||
if im.image.has_any_infinite_pixels() {
|
||||
return Err(error!(
|
||||
return Err(anyhow!(
|
||||
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() {
|
||||
return Err(error!(
|
||||
return Err(anyhow!(
|
||||
loc,
|
||||
"image '{}' has NaN pixels, not suitable for light", filename
|
||||
"image '{}' has NaN pixels, not suitable for light",
|
||||
filename
|
||||
));
|
||||
}
|
||||
|
||||
let channel_desc = im
|
||||
.image
|
||||
.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 colorspace = im
|
||||
.metadata
|
||||
.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. {
|
||||
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.);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use crate::utils::sampling::AliasTableHost;
|
||||
use shared::core::light::Light;
|
||||
use shared::core::light::{Light, LightTrait};
|
||||
use shared::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use shared::utils::sampling::AliasTable;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct PowerSamplerHost {
|
||||
pub lights: Vec<Light>,
|
||||
pub light_to_index: HashMap<Light, usize>,
|
||||
pub lights: Vec<Arc<Light>>,
|
||||
pub light_to_index: HashMap<usize, usize>,
|
||||
pub alias_table: AliasTableHost,
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ impl PowerSamplerHost {
|
|||
light_power.push(phi.average());
|
||||
}
|
||||
|
||||
let alias_table = AliasTable::new(&light_power);
|
||||
let alias_table = AliasTableHost::new(&light_power);
|
||||
|
||||
Self {
|
||||
lights: lights_vec,
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ impl CreateSpotLight for SpotLight {
|
|||
MediumInterface::empty(),
|
||||
);
|
||||
|
||||
let iemit = lookup_spectrum(le);
|
||||
let iemit = Ptr::from(&lookup_spectrum(&le));
|
||||
Self {
|
||||
base,
|
||||
iemit,
|
||||
|
|
@ -59,15 +59,15 @@ impl CreateLight for SpotLight {
|
|||
render_from_light: Transform,
|
||||
medium: Medium,
|
||||
parameters: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
shape: &Shape,
|
||||
alpha_tex: &FloatTexture,
|
||||
_loc: &FileLoc,
|
||||
_shape: &Shape,
|
||||
_alpha_tex: &FloatTexture,
|
||||
colorspace: Option<&RGBColorSpace>,
|
||||
) -> Result<Light, Error> {
|
||||
let i = parameters
|
||||
.get_one_spectrum(
|
||||
"I",
|
||||
colorspace.unwrap().illuminant,
|
||||
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
||||
SpectrumType::Illuminant,
|
||||
)
|
||||
.expect("No spectrum");
|
||||
|
|
@ -86,7 +86,7 @@ impl CreateLight for SpotLight {
|
|||
let cos_falloff_end = radians(coneangle).cos();
|
||||
let cos_falloff_start = radians(coneangle - conedelta).cos();
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use crate::core::material::CreateMaterial;
|
|||
use crate::core::texture::SpectrumTexture;
|
||||
use crate::spectra::data::get_named_spectrum;
|
||||
use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload, parameters::error_exit};
|
||||
use anyhow::Result;
|
||||
use shared::core::material::Material;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::SpectrumType;
|
||||
|
|
@ -10,17 +11,16 @@ use shared::materials::coated::*;
|
|||
use shared::spectra::ConstantSpectrum;
|
||||
use shared::textures::SpectrumConstantTexture;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl CreateMaterial for CoatedDiffuseMaterial {
|
||||
fn create(
|
||||
parameters: &TextureParameterDictionary,
|
||||
normal_map: Option<Arc<Image>>,
|
||||
named_materials: &HashMap<String, Material>,
|
||||
loc: &FileLoc,
|
||||
_named_materials: &HashMap<String, Material>,
|
||||
_loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Material, Error> {
|
||||
) -> Result<Material> {
|
||||
let reflectance = parameters
|
||||
.get_spectrum_texture("reflectance", None, SpectrumType::Albedo)
|
||||
.unwrap_or(Arc::new(SpectrumTexture::Constant(
|
||||
|
|
@ -29,18 +29,18 @@ impl CreateMaterial for CoatedDiffuseMaterial {
|
|||
|
||||
let u_roughness = parameters
|
||||
.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
|
||||
.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 eta = parameters
|
||||
.get_float_array("eta")
|
||||
.first()
|
||||
.map(|&v| ConstantSpectrum::new(v))
|
||||
.map(|&v| Spectrum::Constant(ConstantSpectrum::new(v)))
|
||||
.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 n_samples = parameters.get_one_int("nsamples", 1);
|
||||
|
|
@ -49,7 +49,7 @@ impl CreateMaterial for CoatedDiffuseMaterial {
|
|||
.get_spectrum_texture("albedo", None, SpectrumType::Albedo)
|
||||
.unwrap_or_else(|| {
|
||||
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 remap_roughness = parameters.get_one_bool("remaproughness", true);
|
||||
|
|
@ -60,12 +60,12 @@ impl CreateMaterial for CoatedDiffuseMaterial {
|
|||
thickness.upload(arena),
|
||||
albedo.upload(arena),
|
||||
g.upload(arena),
|
||||
eta,
|
||||
eta.upload(arena),
|
||||
displacement.upload(arena),
|
||||
normal_map.upload(arena),
|
||||
remap_roughness,
|
||||
max_depth,
|
||||
n_samples,
|
||||
max_depth as u32,
|
||||
n_samples as u32,
|
||||
);
|
||||
|
||||
Ok(Material::CoatedDiffuse(specific))
|
||||
|
|
@ -76,49 +76,59 @@ impl CreateMaterial for CoatedConductorMaterial {
|
|||
fn create(
|
||||
parameters: &TextureParameterDictionary,
|
||||
normal_map: Option<Arc<Image>>,
|
||||
named_materials: &HashMap<String, Material>,
|
||||
_named_materials: &HashMap<String, Material>,
|
||||
loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Material, Error> {
|
||||
) -> Result<Material> {
|
||||
let interface_u_roughness = parameters
|
||||
.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
|
||||
.GetFloatTextureOrNull("interface.vroughness")
|
||||
.get_float_texture_or_null("interface.vroughness")
|
||||
.unwrap_or_else(|| parameters.get_float_texture("interface.vroughness", 0.));
|
||||
let thickness = parameters.get_float_texture("interface.thickness", 0.01);
|
||||
let interface_eta = parameters
|
||||
let interface_eta: Spectrum = parameters
|
||||
.get_float_array("interface.eta")
|
||||
.first()
|
||||
.map(|&v| ConstantSpectrum::new(v))
|
||||
.map(|&v| Spectrum::Constant(ConstantSpectrum::new(v)))
|
||||
.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
|
||||
.get_float_texture_or_null("conductor.uroughness")
|
||||
.unwrap_or_else(|| parameters.get_float_texture("conductor.roughness", 0.));
|
||||
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.));
|
||||
let reflectance =
|
||||
parameters.get_spectrum_texture_or_null("reflectance", SpectrumType::Albedo);
|
||||
let conductor_eta =
|
||||
parameters.get_spectrum_texture_or_null("conductor.eta", 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(_)) => {
|
||||
return Err(error_exit(
|
||||
loc,
|
||||
Some(loc),
|
||||
"For the coated conductor material, both \"reflectance\" \
|
||||
and \"eta\" and \"k\" can't be provided.",
|
||||
));
|
||||
}
|
||||
(None, eta, k) => (
|
||||
eta.unwrap_or_else(|| {
|
||||
SpectrumConstantTexture::new(get_named_spectrum("metal-Cu-eta"))
|
||||
}),
|
||||
k.unwrap_or_else(|| SpectrumConstantTexture::new(get_named_spectrum("metal-Cu-k"))),
|
||||
),
|
||||
(Some(_), None, None) => (conductor_eta, k),
|
||||
|
||||
(None, eta, k) => {
|
||||
let final_eta = eta.unwrap_or_else(|| {
|
||||
let s = get_named_spectrum("metal-Cu-eta").expect("Missing copper spectrum");
|
||||
Arc::new(SpectrumTexture::Constant(SpectrumConstantTexture::new(s)))
|
||||
});
|
||||
|
||||
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);
|
||||
|
|
@ -127,27 +137,28 @@ impl CreateMaterial for CoatedConductorMaterial {
|
|||
let albedo = parameters
|
||||
.get_spectrum_texture("albedo", None, SpectrumType::Albedo)
|
||||
.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 remap_roughness = parameters.get_one_bool("remaproughness", true);
|
||||
let material = Self::new(
|
||||
displacement.upload(arena),
|
||||
normal_map.upload(arena),
|
||||
interface_u_roughness,
|
||||
interface_v_roughness,
|
||||
thickness,
|
||||
interface_eta,
|
||||
g,
|
||||
albedo,
|
||||
conductor_u_roughness,
|
||||
conductor_v_roughness,
|
||||
conductor_eta,
|
||||
k,
|
||||
reflectance,
|
||||
displacement.upload(arena),
|
||||
interface_u_roughness.upload(arena),
|
||||
interface_v_roughness.upload(arena),
|
||||
thickness.upload(arena),
|
||||
interface_eta.upload(arena),
|
||||
g.upload(arena),
|
||||
albedo.upload(arena),
|
||||
conductor_u_roughness.upload(arena),
|
||||
conductor_v_roughness.upload(arena),
|
||||
conductor_eta.upload(arena),
|
||||
k.upload(arena),
|
||||
reflectance.upload(arena),
|
||||
max_depth as u32,
|
||||
n_samples as u32,
|
||||
remap_roughness,
|
||||
max_depth,
|
||||
n_samples,
|
||||
);
|
||||
arena.alloc(material);
|
||||
Ok(Material::CoatedConductor(material))
|
||||
|
|
|
|||
|
|
@ -2,19 +2,16 @@ use crate::core::image::Image;
|
|||
use crate::core::material::CreateMaterial;
|
||||
use crate::spectra::get_colorspace_context;
|
||||
use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload};
|
||||
use shared::bxdfs::HairBxDF;
|
||||
use shared::core::bsdf::BSDF;
|
||||
use shared::core::bssrdf::BSSRDF;
|
||||
use shared::core::material::{Material, MaterialEvalContext, MaterialTrait};
|
||||
use shared::core::material::Material;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::{GPUFloatTexture, SpectrumType, TextureEvaluator};
|
||||
use shared::core::texture::SpectrumType;
|
||||
use shared::materials::complex::*;
|
||||
use shared::spectra::RGBUnboundedSpectrum;
|
||||
use shared::spectra::SampledWavelengths;
|
||||
// use shared::spectra::SampledWavelengths;
|
||||
use shared::textures::SpectrumConstantTexture;
|
||||
use shared::utils::Ptr;
|
||||
// use shared::utils::Ptr;
|
||||
use anyhow::Result;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl CreateMaterial for HairMaterial {
|
||||
|
|
@ -24,7 +21,7 @@ impl CreateMaterial for HairMaterial {
|
|||
_named_materials: &HashMap<String, Material>,
|
||||
loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Material, Error> {
|
||||
) -> Result<Material> {
|
||||
let sigma_a = parameters.get_spectrum_texture_or_null("sigma_a", SpectrumType::Unbounded);
|
||||
let reflectance = parameters
|
||||
.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 stdcs = get_colorspace_context();
|
||||
let default_rgb = HairBxDF::sigma_a_from_concentration(1.3, 0.0, stdcs);
|
||||
Some(Arc::new(SpectrumConstantTexture::new(
|
||||
Spectrum::RGBUnbounded(RGBUnboundedSpectrum::new(
|
||||
reflectance.to_rgb(),
|
||||
default_rgb,
|
||||
)),
|
||||
)))
|
||||
let spectrum =
|
||||
Spectrum::RGBUnbounded(RGBUnboundedSpectrum::new(stdc.srgb, default_rgb));
|
||||
let texture = SpectrumTexture::Constant(SpectrumConstantTexture::new(spectrum));
|
||||
Some(Arc::new(texture))
|
||||
} else {
|
||||
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_n = parameters.get_float_texture("beta_n", 0.3);
|
||||
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 {
|
||||
fn create(
|
||||
_parameters: &TextureParameterDictionary,
|
||||
|
|
@ -105,7 +68,7 @@ impl CreateMaterial for SubsurfaceMaterial {
|
|||
_named_materials: &HashMap<String, Material>,
|
||||
_loc: &FileLoc,
|
||||
_arena: &mut Arena,
|
||||
) -> Result<Material, Error> {
|
||||
) -> Result<Material> {
|
||||
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 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 shared::utils::hash::hash_float;
|
||||
use crate::core::material::CreateMaterial;
|
||||
use shared::materials::MixMaterial;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MixMaterial {
|
||||
pub amount: Ptr<GPUFloatTexture>,
|
||||
pub materials: [Ptr<Material>; 2],
|
||||
}
|
||||
|
||||
impl MixMaterial {
|
||||
pub fn choose_material<T: TextureEvaluator>(
|
||||
&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
|
||||
impl CreateMaterial for MixMaterial {
|
||||
fn create(
|
||||
parameters: &crate::utils::TextureParameterDictionary,
|
||||
normal_map: Option<std::sync::Arc<Image>>,
|
||||
named_materials: &std::collections::HashMap<String, Material>,
|
||||
loc: &crate::utils::FileLoc,
|
||||
arena: &mut crate::Arena,
|
||||
) -> Result<Material, std::fmt::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,76 @@
|
|||
use crate::Arena;
|
||||
use crate::core::sampler::CreateSampler;
|
||||
use crate::utils::{FileLoc, ParameterDictionary};
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::options::get_options;
|
||||
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 {
|
||||
fn create(
|
||||
params: &ParameterDictionary,
|
||||
full_res: Point2i,
|
||||
loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Self, Error> {
|
||||
_loc: &FileLoc,
|
||||
_arena: &mut Arena,
|
||||
) -> Result<Self> {
|
||||
let options = get_options();
|
||||
let nsamp = options
|
||||
.quick_render
|
||||
|
|
@ -29,15 +87,15 @@ impl CreateSampler for HaltonSampler {
|
|||
"fastowen" => RandomizeStrategy::FastOwen,
|
||||
"owen" => RandomizeStrategy::Owen,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
return Err(anyhow!(
|
||||
"{}: Unknown randomization strategy for Halton",
|
||||
loc
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let sampler = HaltonSampler::new(nsamp as u32, full_res, s, seed as u64);
|
||||
arena.alloc(sampler);
|
||||
let sampler = HaltonSampler::new(nsamp, full_res, s, seed as u64);
|
||||
// arena.alloc(sampler);
|
||||
Ok(sampler)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::Arena;
|
||||
use crate::core::sampler::CreateSampler;
|
||||
use crate::utils::{FileLoc, ParameterDictionary};
|
||||
use anyhow::Result;
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::options::get_options;
|
||||
use shared::core::sampler::IndependentSampler;
|
||||
|
|
@ -12,7 +13,7 @@ impl CreateSampler for IndependentSampler {
|
|||
_full_res: Point2i,
|
||||
_loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Self, Error> {
|
||||
) -> Result<Self> {
|
||||
let options = get_options();
|
||||
let nsamp = options
|
||||
.quick_render
|
||||
|
|
@ -20,7 +21,7 @@ impl CreateSampler for IndependentSampler {
|
|||
.or(options.pixel_samples)
|
||||
.unwrap_or_else(|| params.get_one_int("pixelsamples", 16));
|
||||
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);
|
||||
Ok(sampler)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
use crate::Arena;
|
||||
use crate::core::sampler::CreateSampler;
|
||||
use crate::utils::{FileLoc, ParameterDictionary};
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::options::get_options;
|
||||
use shared::core::sampler::{PaddedSobolSampler, RandomizeStrategy, SobolSampler, ZSobolSampler};
|
||||
use std::fmt::Error;
|
||||
|
||||
impl CreateSampler for SobolSampler {
|
||||
fn create(
|
||||
params: &ParameterDictionary,
|
||||
full_res: Point2i,
|
||||
loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Self, Error> {
|
||||
_arena: &mut Arena,
|
||||
) -> Result<Self> {
|
||||
let options = get_options();
|
||||
let nsamp = options
|
||||
.quick_render
|
||||
|
|
@ -26,12 +26,12 @@ impl CreateSampler for SobolSampler {
|
|||
"fastowen" => RandomizeStrategy::FastOwen,
|
||||
"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));
|
||||
arena.alloc(sampler);
|
||||
let sampler = Self::new(nsamp, full_res, s, Some(seed as u64));
|
||||
// arena.alloc(sampler);
|
||||
Ok(sampler)
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ impl CreateSampler for PaddedSobolSampler {
|
|||
_full_res: Point2i,
|
||||
loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Self, Error> {
|
||||
) -> Result<Self> {
|
||||
let options = get_options();
|
||||
let nsamp = options
|
||||
.quick_render
|
||||
|
|
@ -56,14 +56,14 @@ impl CreateSampler for PaddedSobolSampler {
|
|||
"fastowen" => RandomizeStrategy::FastOwen,
|
||||
"owen" => RandomizeStrategy::Owen,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
return Err(anyhow!(
|
||||
"{}: Unknown randomization strategy for ZSobol",
|
||||
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);
|
||||
Ok(sampler)
|
||||
}
|
||||
|
|
@ -74,8 +74,8 @@ impl CreateSampler for ZSobolSampler {
|
|||
params: &ParameterDictionary,
|
||||
full_res: Point2i,
|
||||
loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Self, Error> {
|
||||
_arena: &mut Arena,
|
||||
) -> Result<Self> {
|
||||
let options = get_options();
|
||||
let nsamp = options
|
||||
.quick_render
|
||||
|
|
@ -89,14 +89,14 @@ impl CreateSampler for ZSobolSampler {
|
|||
"fastowen" => RandomizeStrategy::FastOwen,
|
||||
"owen" => RandomizeStrategy::Owen,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
return Err(anyhow!(
|
||||
"{}: Unknown randomization strategy for ZSobol",
|
||||
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);
|
||||
Ok(sampler)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
use crate::Arena;
|
||||
use crate::core::sampler::CreateSampler;
|
||||
use crate::utils::{FileLoc, ParameterDictionary};
|
||||
use anyhow::Result;
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::options::get_options;
|
||||
use shared::core::sampler::StratifiedSampler;
|
||||
use std::fmt::Error;
|
||||
|
||||
impl CreateSampler for StratifiedSampler {
|
||||
fn create(
|
||||
params: &ParameterDictionary,
|
||||
_full_res: Point2i,
|
||||
_loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Self, Error> {
|
||||
_arena: &mut Arena,
|
||||
) -> Result<Self> {
|
||||
let options = get_options();
|
||||
let jitter = params.get_one_bool("jitter", true);
|
||||
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 sampler = Self::new(
|
||||
x_samples as u32,
|
||||
y_samples as u32,
|
||||
Some(seed as u64),
|
||||
jitter,
|
||||
);
|
||||
arena.aloc(sampler);
|
||||
let sampler = Self::new(x_samples, y_samples, Some(seed as u64), jitter);
|
||||
// arena.aloc(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::texture::FloatTexture;
|
||||
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!"
|
||||
);
|
||||
} else {
|
||||
match Image::read(&filename) {
|
||||
match Image::read(Path::new(filename), None) {
|
||||
Ok(mut im) => {
|
||||
im.flip_y();
|
||||
|
||||
let domain = Bounds2f::new(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0));
|
||||
let distribution = im.get_sampling_distribution(); // Assuming this returns Array2D<Float>
|
||||
|
||||
image_dist =
|
||||
Some(PiecewiseConstant2D::new_with_bounds(distribution, domain));
|
||||
let img = im.image;
|
||||
img.flip_y();
|
||||
image_dist = Some(PiecewiseConstant2D::from_image(&img));
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to load emission image \"{}\": {}", filename, e);
|
||||
|
|
@ -111,7 +107,7 @@ impl CreateShape for BilinearPatchShape {
|
|||
}
|
||||
|
||||
let host = BilinearPatchMesh::new(
|
||||
render_from_object,
|
||||
&render_from_object,
|
||||
reverse_orientation,
|
||||
vertex_indices,
|
||||
p,
|
||||
|
|
@ -121,15 +117,15 @@ impl CreateShape for BilinearPatchShape {
|
|||
);
|
||||
|
||||
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;
|
||||
global_store.push(host_arc.clone());
|
||||
drop(global_store);
|
||||
let n_patches = host_arc.view.n_patches;
|
||||
let mesh_ptr = &host_arc.view as *const _;
|
||||
let n_patches = host_arc.device.n_patches;
|
||||
let mesh_ptr = Ptr::from(&host_arc.device);
|
||||
let mut shapes = Vec::with_capacity(n_patches as usize);
|
||||
for i in 0..n_patches {
|
||||
shapes.push(Shape::Bilinear(BilinearPatchShape {
|
||||
shapes.push(Shape::BilinearPatch(BilinearPatchShape {
|
||||
mesh: mesh_ptr,
|
||||
blp_index: i,
|
||||
area: 0.0,
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@ impl CreateShape for CurveShape {
|
|||
object_from_render: Transform,
|
||||
reverse_orientation: bool,
|
||||
parameters: ParameterDictionary,
|
||||
float_textures: HashMap<String, FloatTexture>,
|
||||
loc: FileLoc,
|
||||
arena: &mut Arena,
|
||||
_float_textures: HashMap<String, FloatTexture>,
|
||||
_loc: FileLoc,
|
||||
_arena: &mut Arena,
|
||||
) -> Result<Vec<Shape>, String> {
|
||||
let width = parameters.get_one_float("width", 1.0);
|
||||
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;
|
||||
|
||||
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 curve_type != CurveType::Ribbon {
|
||||
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)
|
||||
};
|
||||
|
||||
let mut curves: Vec<Arc<Shape>> = Vec::new();
|
||||
let mut curves: Vec<Shape> = Vec::new();
|
||||
let mut cp_offset = 0;
|
||||
|
||||
for seg in 0..n_segments {
|
||||
|
|
@ -194,13 +194,13 @@ impl CreateShape for CurveShape {
|
|||
w0,
|
||||
w1,
|
||||
curve_type,
|
||||
seg_normals,
|
||||
split_depth,
|
||||
seg_normals.expect("Could not determine normals to curve segments"),
|
||||
split_depth.try_into().unwrap(),
|
||||
);
|
||||
|
||||
curves.extend(new_curves);
|
||||
}
|
||||
arena.alloc(curves);
|
||||
// arena.alloc(curves);
|
||||
Ok(curves)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ use crate::utils::sampling::PiecewiseConstant2D;
|
|||
use anyhow::{Context, Result as AnyResult, bail};
|
||||
use ply_rs::parser::Parser;
|
||||
use ply_rs::ply::{DefaultElement, Property};
|
||||
use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f};
|
||||
use shared::utils::Transform;
|
||||
use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f, VectorLike};
|
||||
use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh};
|
||||
use shared::utils::{Ptr, Transform};
|
||||
use std::fs::File;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
|
@ -17,38 +17,38 @@ pub struct TriQuadMesh {
|
|||
pub n: Vec<Normal3f>,
|
||||
pub uv: Vec<Point2f>,
|
||||
pub face_indices: Vec<i32>,
|
||||
pub tri_indices: Vec<u32>,
|
||||
pub quad_indices: Vec<u32>,
|
||||
pub tri_indices: Vec<i32>,
|
||||
pub quad_indices: Vec<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TriangleMeshStorage {
|
||||
vertex_indices: Vec<u32>,
|
||||
p: Vec<Point3f>,
|
||||
n: Vec<Normal3f>,
|
||||
s: Vec<Vector3f>,
|
||||
uv: Vec<Point2f>,
|
||||
face_indices: Vec<i32>,
|
||||
pub p: Vec<Point3f>,
|
||||
pub n: Vec<Normal3f>,
|
||||
pub s: Vec<Vector3f>,
|
||||
pub uv: Vec<Point2f>,
|
||||
pub vertex_indices: Vec<i32>,
|
||||
pub face_indices: Vec<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BilinearMeshStorage {
|
||||
vertex_indices: Vec<u32>,
|
||||
p: Vec<Point3f>,
|
||||
n: Vec<Normal3f>,
|
||||
uv: Vec<Point2f>,
|
||||
image_distribution: Option<PiecewiseConstant2D>,
|
||||
pub vertex_indices: Vec<i32>,
|
||||
pub p: Vec<Point3f>,
|
||||
pub n: Vec<Normal3f>,
|
||||
pub uv: Vec<Point2f>,
|
||||
pub image_distribution: Option<PiecewiseConstant2D>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TriangleMesh {
|
||||
_storage: Box<TriangleMeshStorage>,
|
||||
pub storage: Box<TriangleMeshStorage>,
|
||||
pub device: DeviceTriangleMesh,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BilinearPatchMesh {
|
||||
_storage: Box<BilinearMeshStorage>,
|
||||
pub storage: Box<BilinearMeshStorage>,
|
||||
pub device: DeviceBilinearPatchMesh,
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ impl TriangleMesh {
|
|||
pub fn new(
|
||||
render_from_object: &Transform,
|
||||
reverse_orientation: bool,
|
||||
vertex_indices: Vec<u32>,
|
||||
vertex_indices: Vec<i32>,
|
||||
mut p: Vec<Point3f>,
|
||||
mut n: Vec<Normal3f>,
|
||||
mut s: Vec<Vector3f>,
|
||||
|
|
@ -84,14 +84,14 @@ impl TriangleMesh {
|
|||
|
||||
// Transform positions to render space
|
||||
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
|
||||
if !n.is_empty() {
|
||||
assert_eq!(n_vertices, n.len(), "Normal count mismatch");
|
||||
for nn in n.iter_mut() {
|
||||
*nn = render_from_object.apply_normal(*nn);
|
||||
*nn = render_from_object.apply_to_normal(*nn);
|
||||
if reverse_orientation {
|
||||
*nn = -*nn;
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ impl TriangleMesh {
|
|||
if !s.is_empty() {
|
||||
assert_eq!(n_vertices, s.len(), "Tangent count mismatch");
|
||||
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 {
|
||||
n_triangles: n_triangles as u32,
|
||||
n_vertices: n_vertices as u32,
|
||||
vertex_indices: storage.vertex_indices.as_ptr(),
|
||||
p: storage.p.as_ptr(),
|
||||
vertex_indices: storage.vertex_indices.as_ptr().into(),
|
||||
p: storage.p.as_ptr().into(),
|
||||
n: if storage.n.is_empty() {
|
||||
std::ptr::null()
|
||||
Ptr::null()
|
||||
} else {
|
||||
storage.n.as_ptr()
|
||||
storage.n.as_ptr().into()
|
||||
},
|
||||
s: if storage.s.is_empty() {
|
||||
std::ptr::null()
|
||||
Ptr::null()
|
||||
} else {
|
||||
storage.s.as_ptr()
|
||||
storage.s.as_ptr().into()
|
||||
},
|
||||
uv: if storage.uv.is_empty() {
|
||||
std::ptr::null()
|
||||
Ptr::null()
|
||||
} else {
|
||||
storage.uv.as_ptr()
|
||||
storage.uv.as_ptr().into()
|
||||
},
|
||||
face_indices: if storage.face_indices.is_empty() {
|
||||
std::ptr::null()
|
||||
Ptr::null()
|
||||
} else {
|
||||
storage.face_indices.as_ptr()
|
||||
storage.face_indices.as_ptr().into()
|
||||
},
|
||||
reverse_orientation,
|
||||
transform_swaps_handedness,
|
||||
};
|
||||
|
||||
Self {
|
||||
_storage: storage,
|
||||
device,
|
||||
}
|
||||
Self { storage, device }
|
||||
}
|
||||
|
||||
pub fn positions(&self) -> &[Point3f] {
|
||||
&self._storage.p
|
||||
&self.storage.p
|
||||
}
|
||||
|
||||
pub fn indices(&self) -> &[u32] {
|
||||
&self._storage.vertex_indices
|
||||
pub fn indices(&self) -> &[i32] {
|
||||
&self.storage.vertex_indices
|
||||
}
|
||||
|
||||
pub fn normals(&self) -> &[Normal3f] {
|
||||
&self._storage.n
|
||||
&self.storage.n
|
||||
}
|
||||
|
||||
pub fn uvs(&self) -> &[Point2f] {
|
||||
&self._storage.uv
|
||||
&self.storage.uv
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -184,7 +181,7 @@ impl BilinearPatchMesh {
|
|||
pub fn new(
|
||||
render_from_object: &Transform,
|
||||
reverse_orientation: bool,
|
||||
vertex_indices: Vec<u32>,
|
||||
vertex_indices: Vec<ui2>,
|
||||
mut p: Vec<Point3f>,
|
||||
mut n: Vec<Normal3f>,
|
||||
uv: Vec<Point2f>,
|
||||
|
|
@ -194,13 +191,13 @@ impl BilinearPatchMesh {
|
|||
let n_vertices = p.len();
|
||||
|
||||
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() {
|
||||
assert_eq!(n_vertices, n.len(), "Normal count mismatch");
|
||||
for nn in n.iter_mut() {
|
||||
*nn = render_from_object.apply_normal(*nn);
|
||||
*nn = render_from_object.apply_to_normal(*nn);
|
||||
if reverse_orientation {
|
||||
*nn = -*nn;
|
||||
}
|
||||
|
|
@ -224,31 +221,28 @@ impl BilinearPatchMesh {
|
|||
let device = DeviceBilinearPatchMesh {
|
||||
n_patches: n_patches as u32,
|
||||
n_vertices: n_vertices as u32,
|
||||
vertex_indices: storage.vertex_indices.as_ptr(),
|
||||
p: storage.p.as_ptr(),
|
||||
vertex_indices: storage.vertex_indices.as_ptr().into(),
|
||||
p: storage.p.as_ptr().into(),
|
||||
n: if storage.n.is_empty() {
|
||||
std::ptr::null()
|
||||
Ptr::null()
|
||||
} else {
|
||||
storage.n.as_ptr()
|
||||
storage.n.as_ptr().into()
|
||||
},
|
||||
uv: if storage.uv.is_empty() {
|
||||
std::ptr::null()
|
||||
Ptr::null()
|
||||
} else {
|
||||
storage.uv.as_ptr()
|
||||
storage.uv.as_ptr().into()
|
||||
},
|
||||
reverse_orientation,
|
||||
transform_swaps_handedness,
|
||||
image_distribution: storage
|
||||
.image_distribution
|
||||
.as_ref()
|
||||
.map(|d| &d.device as *const _)
|
||||
.unwrap_or(std::ptr::null()),
|
||||
.map(|d| Ptr::from(&d.device))
|
||||
.unwrap_or(Ptr::null()),
|
||||
};
|
||||
|
||||
Self {
|
||||
_storage: storage,
|
||||
device,
|
||||
}
|
||||
Self { storage, device }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -374,8 +368,8 @@ impl TriQuadMesh {
|
|||
|
||||
if let Ok(indices) = get_list_uint(f_elem, "vertex_indices") {
|
||||
match indices.len() {
|
||||
3 => mesh.tri_indices.extend_from_slice(&indices),
|
||||
4 => mesh.quad_indices.extend_from_slice(&indices),
|
||||
3 => mesh.tri_indices.extend(indices.iter().map(|&i| i as i32)),
|
||||
4 => mesh.quad_indices.extend(indices.iter().map(|&i| i as i32)),
|
||||
n => {
|
||||
log::warn!(
|
||||
"{}: Skipping face with {} vertices (only 3 or 4 supported)",
|
||||
|
|
@ -393,7 +387,7 @@ impl TriQuadMesh {
|
|||
}
|
||||
|
||||
// Validate indices
|
||||
let vertex_count = mesh.p.len() as u32;
|
||||
let vertex_count = mesh.p.len() as i32;
|
||||
for &idx in &mesh.tri_indices {
|
||||
if idx >= vertex_count {
|
||||
bail!(
|
||||
|
|
@ -457,7 +451,7 @@ impl TriQuadMesh {
|
|||
|
||||
let v10 = p1 - p0;
|
||||
let v20 = p2 - p0;
|
||||
let face_normal = v10.cross(&v20);
|
||||
let face_normal = v10.cross(v20);
|
||||
|
||||
// Accumulate (will normalize later)
|
||||
self.n[i0] = self.n[i0] + Normal3f::from(face_normal);
|
||||
|
|
@ -475,7 +469,7 @@ impl TriQuadMesh {
|
|||
|
||||
let v10 = p1 - p0;
|
||||
let v20 = p2 - p0;
|
||||
let face_normal = v10.cross(&v20);
|
||||
let face_normal = v10.cross(v20);
|
||||
|
||||
for &idx in &indices {
|
||||
self.n[idx] = self.n[idx] + Normal3f::from(face_normal);
|
||||
|
|
@ -484,7 +478,7 @@ impl TriQuadMesh {
|
|||
|
||||
// Normalize all normals
|
||||
for normal in &mut self.n {
|
||||
let len_sq = normal.length_squared();
|
||||
let len_sq = normal.norm_squared();
|
||||
if len_sq > 0.0 {
|
||||
*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;
|
||||
|
||||
if !face_indices.is_empty() && face_indices.len() != n_triangles {
|
||||
|
|
@ -86,28 +86,28 @@ impl CreateShape for TriangleShape {
|
|||
}
|
||||
|
||||
let host = TriangleMesh::new(
|
||||
render_from_object,
|
||||
&render_from_object,
|
||||
reverse_orientation,
|
||||
vertex_indices,
|
||||
p,
|
||||
s,
|
||||
n,
|
||||
s,
|
||||
uvs,
|
||||
face_indices,
|
||||
);
|
||||
|
||||
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;
|
||||
global_store.push(host_arc.clone());
|
||||
drop(global_store);
|
||||
let n_patches = host_arc.view.n_patches;
|
||||
let mesh_ptr = &host_arc.view as *const _;
|
||||
let n_patches = host_arc.device.n_triangles;
|
||||
let mesh_ptr = Ptr::from(&host_arc.device);
|
||||
let mut shapes = Vec::with_capacity(n_patches as usize);
|
||||
for i in 0..n_patches {
|
||||
shapes.push(Shape::Triangle(TriangleShape {
|
||||
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::ptr::Ptr;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RGBColorSpaceData {
|
||||
_illuminant: DenselySampledSpectrumBuffer,
|
||||
pub view: RGBColorSpace,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for RGBColorSpaceData {
|
||||
type Target = RGBColorSpace;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.view
|
||||
}
|
||||
}
|
||||
|
||||
impl RGBColorSpaceData {
|
||||
pub fn new(
|
||||
r: Point2f,
|
||||
|
|
@ -18,7 +26,7 @@ impl RGBColorSpaceData {
|
|||
illuminant: DenselySampledSpectrumBuffer,
|
||||
rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
|
||||
) -> Self {
|
||||
let w_xyz: XYZ = illuminant.to_xyz();
|
||||
let w_xyz: XYZ = Spectrum::Dense(illuminant).to_xyz();
|
||||
let w = w_xyz.xy();
|
||||
let r_xyz = XYZ::from_xyy(r, Some(1.0));
|
||||
let g_xyz = XYZ::from_xyy(g, Some(1.0));
|
||||
|
|
@ -29,7 +37,8 @@ impl RGBColorSpaceData {
|
|||
[r_xyz.z(), g_xyz.z(), b_xyz.z()],
|
||||
];
|
||||
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 rgb_from_xyz = xyz_from_rgb
|
||||
.inverse()
|
||||
|
|
@ -39,7 +48,7 @@ impl RGBColorSpaceData {
|
|||
g,
|
||||
b,
|
||||
w,
|
||||
illuminant: illuminant.view,
|
||||
illuminant: *illuminant,
|
||||
xyz_from_rgb,
|
||||
rgb_from_xyz,
|
||||
rgb_to_spectrum_table,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ use shared::spectra::cie::*;
|
|||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub fn create_cie_buffer(data: &[Float]) -> Spectrum {
|
||||
let buffer = PiecewiseLinearSpectrum::from_interleaved(data, false);
|
||||
let spec = Spectrum::Piecewise(buffer.view);
|
||||
DenselySampledSpectrumBuffer::from_spectrum(spec)
|
||||
pub fn create_cie_buffer(data: &[Float]) -> DenselySampledSpectrumBuffer {
|
||||
let buffer = PiecewiseLinearSpectrumBuffer::from_interleaved(data, false);
|
||||
let spec = Spectrum::Piecewise(buffer.device);
|
||||
DenselySampledSpectrumBuffer::from_spectrum(&spec)
|
||||
}
|
||||
|
||||
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 {
|
||||
($name:expr, $data:expr, $norm:expr) => {
|
||||
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> {
|
||||
let buffer = NAMED_SPECTRA.get(name)?;
|
||||
Some(Spectrum::PiecewiseLinear(buffer.view))
|
||||
NAMED_SPECTRA.get(name).copied()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
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::{
|
||||
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, PiecewiseLinearSpectrum,
|
||||
SampledSpectrum, SampledWavelengths,
|
||||
};
|
||||
use shared::utils::math::square;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DenselySampledSpectrumBuffer {
|
||||
pub device: DenselySampledSpectrum,
|
||||
pub _storage: Vec<Float>,
|
||||
|
|
@ -24,7 +24,7 @@ impl DenselySampledSpectrumBuffer {
|
|||
let view = DenselySampledSpectrum {
|
||||
lambda_min,
|
||||
lambda_max,
|
||||
values: values.as_ptr(),
|
||||
values: values.as_ptr().into(),
|
||||
};
|
||||
Self {
|
||||
device: view,
|
||||
|
|
@ -90,8 +90,8 @@ impl DenselySampledSpectrumBuffer {
|
|||
.collect();
|
||||
|
||||
let temp_pls = PiecewiseLinearSpectrum {
|
||||
lambdas: CIE_S_LAMBDA.as_ptr(),
|
||||
values: coarse_values.as_ptr(),
|
||||
lambdas: CIE_S_LAMBDA.as_ptr().into(),
|
||||
values: coarse_values.as_ptr().into(),
|
||||
count: N_CIES as u32,
|
||||
};
|
||||
|
||||
|
|
@ -100,9 +100,9 @@ impl DenselySampledSpectrumBuffer {
|
|||
|
||||
pub fn as_view(&self) -> DenselySampledSpectrum {
|
||||
DenselySampledSpectrum {
|
||||
lambda_min: self.lambda_min,
|
||||
lambda_max: self.lambda_max,
|
||||
values: self.samples.as_ptr(),
|
||||
lambda_min: self.device.lambda_min,
|
||||
lambda_max: self.device.lambda_max,
|
||||
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 shared::core::color::RGBToSpectrumTable;
|
||||
use shared::core::geometry::Point2f;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::spectrum::StandardSpectra;
|
||||
use shared::spectra::DeviceStandardColorSpaces;
|
||||
use shared::spectra::RGBColorSpace;
|
||||
use shared::spectra::StandardColorSpaces;
|
||||
use shared::spectra::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z};
|
||||
use std::sync::Arc;
|
||||
use std::sync::LazyLock;
|
||||
|
|
@ -14,9 +15,16 @@ pub mod data;
|
|||
pub mod dense;
|
||||
pub mod piecewise;
|
||||
|
||||
// pub use data;
|
||||
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> =
|
||||
LazyLock::new(|| data::create_cie_buffer(&CIE_X));
|
||||
static CIE_Y_DATA: LazyLock<DenselySampledSpectrumBuffer> =
|
||||
|
|
@ -27,92 +35,86 @@ static CIE_D65_DATA: LazyLock<DenselySampledSpectrumBuffer> =
|
|||
LazyLock::new(|| data::create_cie_buffer(&CIE_D65));
|
||||
|
||||
pub fn cie_x() -> Spectrum {
|
||||
Spectrum::DenselySampled(CIE_X_DATA.view)
|
||||
Spectrum::Dense(CIE_X_DATA.device)
|
||||
}
|
||||
|
||||
pub fn cie_y() -> Spectrum {
|
||||
Spectrum::DenselySampled(CIE_Y_DATA.view)
|
||||
Spectrum::Dense(CIE_Y_DATA.device)
|
||||
}
|
||||
|
||||
pub fn cie_z() -> Spectrum {
|
||||
Spectrum::DenselySampled(CIE_Z_DATA.view)
|
||||
Spectrum::Dense(CIE_Z_DATA.device)
|
||||
}
|
||||
|
||||
pub fn cie_d65() -> Spectrum {
|
||||
Spectrum::DenselySampled(CIE_D65_DATA.view)
|
||||
Spectrum::Dense(CIE_D65_DATA.device)
|
||||
}
|
||||
|
||||
pub fn get_spectra_context() -> StandardSpectra {
|
||||
StandardSpectra {
|
||||
x: CIE_X_DATA.view,
|
||||
y: CIE_Y_DATA.view,
|
||||
z: CIE_Z_DATA.view,
|
||||
d65: CIE_D65_DATA.view,
|
||||
x: CIE_X_DATA.device,
|
||||
y: CIE_Y_DATA.device,
|
||||
z: CIE_Z_DATA.device,
|
||||
d65: CIE_D65_DATA.device,
|
||||
}
|
||||
}
|
||||
|
||||
pub static SRGB: LazyLock<RGBColorSpaceData> = LazyLock::new(|| {
|
||||
let illum = DenselySampledSpectrumBuffer::new(
|
||||
CIE_D65_DATA.view.lambda_min,
|
||||
CIE_D65_DATA.view.lambda_max,
|
||||
CIE_D65_DATA._storage.clone(),
|
||||
);
|
||||
pub static SRGB: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
|
||||
let illum = get_d65_illuminant_buffer();
|
||||
let r = Point2f::new(0.64, 0.33);
|
||||
let g = Point2f::new(0.3, 0.6);
|
||||
let b = Point2f::new(0.15, 0.06);
|
||||
let table = Ptr::from(SRGB_TABLE.clone());
|
||||
|
||||
RGBColorSpaceData::new(
|
||||
Point2f::new(0.64, 0.33),
|
||||
Point2f::new(0.3, 0.6),
|
||||
Point2f::new(0.15, 0.06),
|
||||
illum,
|
||||
RGBToSpectrumTable::srgb(),
|
||||
)
|
||||
Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr))
|
||||
});
|
||||
|
||||
pub static DCI_P3: LazyLock<RGBColorSpaceData> = LazyLock::new(|| {
|
||||
let illum = DenselySampledSpectrumBuffer::new(
|
||||
CIE_D65_DATA.view.lambda_min,
|
||||
CIE_D65_DATA.view.lambda_max,
|
||||
CIE_D65_DATA._storage.clone(),
|
||||
);
|
||||
|
||||
pub static DCI_P3: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
|
||||
let illum = get_d65_illuminant_buffer();
|
||||
let r = Point2f::new(0.680, 0.320);
|
||||
let g = Point2f::new(0.265, 0.690);
|
||||
let b = Point2f::new(0.150, 0.060);
|
||||
|
||||
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(|| {
|
||||
let illum = DenselySampledSpectrumBuffer::new(
|
||||
CIE_D65_DATA.view.lambda_min,
|
||||
CIE_D65_DATA.view.lambda_max,
|
||||
CIE_D65_DATA._storage.clone(),
|
||||
);
|
||||
|
||||
pub static REC2020: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
|
||||
let illum = get_d65_illuminant_buffer();
|
||||
let r = Point2f::new(0.708, 0.292);
|
||||
let g = Point2f::new(0.170, 0.797);
|
||||
let b = Point2f::new(0.131, 0.046);
|
||||
|
||||
let table = 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 g = Point2f::new(0.0000, 1.0000);
|
||||
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 {
|
||||
StandardColorSpaces {
|
||||
srgb: SRGB,
|
||||
dci_p3: DCI_P3,
|
||||
rec2020: REC2020,
|
||||
aces2065_1: ACES,
|
||||
srgb: SRGB.clone().into(),
|
||||
dci_p3: DCI_P3.clone().into(),
|
||||
rec2020: REC2020.clone().into(),
|
||||
aces2065_1: ACES.clone().into(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use crate::utils::read_float_file;
|
||||
use shared::Float;
|
||||
use shared::spectra::PiecewiseLinearSpectrum;
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
pub struct PiecewiseLinearSpectrumBuffer {
|
||||
pub view: PiecewiseLinearSpectrum,
|
||||
pub device: PiecewiseLinearSpectrum,
|
||||
_lambdas: Vec<Float>,
|
||||
_values: Vec<Float>,
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ pub struct PiecewiseLinearSpectrumBuffer {
|
|||
impl Deref for PiecewiseLinearSpectrumBuffer {
|
||||
type Target = PiecewiseLinearSpectrum;
|
||||
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 {
|
||||
assert_eq!(lambdas.len(), values.len());
|
||||
let view = PiecewiseLinearSpectrum {
|
||||
lambdas: lambdas.as_ptr(),
|
||||
values: values.as_ptr(),
|
||||
lambdas: lambdas.as_ptr().into(),
|
||||
values: values.as_ptr().into(),
|
||||
count: lambdas.len() as u32,
|
||||
};
|
||||
Self {
|
||||
view,
|
||||
device: view,
|
||||
_lambdas: lambdas,
|
||||
_values: values,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use shared::core::color::ColorEncoding;
|
|||
use shared::core::color::RGB;
|
||||
use shared::core::geometry::Vector2f;
|
||||
use shared::core::image::WrapMode;
|
||||
use shared::core::spectrum::SpectrumTrait;
|
||||
use shared::core::texture::{SpectrumType, TextureEvalContext, TextureMapping2D};
|
||||
use shared::spectra::{
|
||||
RGBAlbedoSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum, SampledSpectrum,
|
||||
|
|
@ -129,7 +130,7 @@ impl SpectrumTextureTrait for SpectrumImageTexture {
|
|||
let dst0 = Vector2f::new(c.dsdx, c.dtdx);
|
||||
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 = RGB::clamp_zero(rgb_unclamp);
|
||||
let rgb = RGB::clamp_zero(&rgb_unclamp);
|
||||
if let Some(cs) = self.base.mipmap.get_rgb_colorspace() {
|
||||
match self.spectrum_type {
|
||||
SpectrumType::Unbounded => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use crate::core::texture::FloatTexture;
|
|||
use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait};
|
||||
use crate::utils::{Arena, FileLoc, TextureParameterDictionary};
|
||||
use shared::Float;
|
||||
use shared::core::geometry::Vector3f;
|
||||
use shared::core::geometry::{Vector3f, VectorLike};
|
||||
use shared::core::texture::TextureEvalContext;
|
||||
use shared::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use shared::utils::Transform;
|
||||
|
|
@ -28,12 +28,13 @@ impl FloatMixTexture {
|
|||
_render_from_texture: &Transform,
|
||||
params: &TextureParameterDictionary,
|
||||
_loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Self {
|
||||
_arena: &mut Arena,
|
||||
) -> FloatTexture {
|
||||
let tex1 = params.get_float_texture("tex1", 0.);
|
||||
let tex2 = params.get_float_texture("tex2", 1.);
|
||||
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,
|
||||
params: &TextureParameterDictionary,
|
||||
_loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
) -> Self {
|
||||
_arena: &mut Arena,
|
||||
) -> FloatTexture {
|
||||
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 tex1 = params.get_float_texture("tex1", 0.);
|
||||
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)]
|
||||
pub struct SpectrumMixTexture;
|
||||
pub struct SpectrumMixTexture {
|
||||
pub tex1: Arc<SpectrumTexture>,
|
||||
pub tex2: Arc<SpectrumTexture>,
|
||||
pub amount: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
impl SpectrumTextureTrait for SpectrumMixTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
|
|
@ -93,7 +100,12 @@ impl SpectrumTextureTrait for SpectrumMixTexture {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpectrumDirectionMixTexture;
|
||||
pub struct SpectrumDirectionMixTexture {
|
||||
pub tex1: Arc<SpectrumTexture>,
|
||||
pub tex2: Arc<SpectrumTexture>,
|
||||
pub dir: Vector3f,
|
||||
}
|
||||
|
||||
impl SpectrumTextureTrait for SpectrumDirectionMixTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
|
|
|
|||
|
|
@ -1,30 +1,14 @@
|
|||
// use crate::utils::{Arena, FileLoc, TextureParameterDictionary};
|
||||
use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait};
|
||||
use ptex::Cache;
|
||||
use ptex_filter::{PtexFilterOptions, PtexFilterType, ptex_filter_create};
|
||||
use crate::spectra::get_colorspace_context;
|
||||
use shared::core::spectrum::SpectrumTrait;
|
||||
// use ptex::Cache;
|
||||
// use ptex_filter::{PtexFilter, PtexFilterOptions, PtexFilterType};
|
||||
|
||||
use shared::Float;
|
||||
use shared::core::color::{ColorEncoding, RGB};
|
||||
use shared::core::texture::{SpectrumType, TextureEvalContext};
|
||||
use shared::spectra::{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
|
||||
}
|
||||
use shared::spectra::{RGBIlluminantSpectrum, SampledSpectrum, SampledWavelengths};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PtexTextureBase {
|
||||
|
|
@ -36,106 +20,21 @@ pub struct PtexTextureBase {
|
|||
|
||||
impl PtexTextureBase {
|
||||
pub fn new(filename: String, encoding: ColorEncoding, scale: Float) -> Self {
|
||||
let cache: &mut Cache = get_ptex_cache();
|
||||
|
||||
// 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",
|
||||
log::warn!(
|
||||
"Ptex support is currently disabled. Texture '{}' will render as Magenta.",
|
||||
filename
|
||||
);
|
||||
}
|
||||
|
||||
Self {
|
||||
filename,
|
||||
encoding,
|
||||
scale,
|
||||
valid,
|
||||
valid: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_texture(&self, ctx: &TextureEvalContext) -> Option<RGB> {
|
||||
if !self.valid {
|
||||
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)
|
||||
pub fn sample_texture(&self, _ctx: &TextureEvalContext) -> Option<RGB> {
|
||||
Some(RGB::new(1.0, 0.0, 1.0) * self.scale)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -145,8 +44,12 @@ pub struct FloatPtexTexture {
|
|||
}
|
||||
|
||||
impl FloatTextureTrait for FloatPtexTexture {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
|
||||
todo!()
|
||||
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
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 {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
fn evaluate(&self, ctx: &TextureEvalContext, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
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,
|
||||
params: &TextureParameterDictionary,
|
||||
_loc: &FileLoc,
|
||||
arena: &mut Arena,
|
||||
_arena: &mut Arena,
|
||||
) -> FloatTexture {
|
||||
let mut tex = params.get_float_texture("tex", 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);
|
||||
arena.alloc(FloatScaledTexture::new(tex, scale));
|
||||
FloatTexture::FloatScaled(FloatScaledTexture::new(tex, scale))
|
||||
// arena.alloc(FloatScaledTexture::new(tex, scale));
|
||||
FloatTexture::Scaled(FloatScaledTexture::new(tex.clone(), scale.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,34 @@
|
|||
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::textures::CreateGPUSpectrumPtex;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use shared::core::color::RGBToSpectrumTable;
|
||||
use shared::core::image::DeviceImage;
|
||||
use shared::core::shape::Shape;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture};
|
||||
use shared::spectra::{RGBColorSpace, StandardColorSpaces};
|
||||
use shared::spectra::{DeviceStandardColorSpaces, RGBColorSpace};
|
||||
use shared::textures::*;
|
||||
use shared::utils::Ptr;
|
||||
use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh};
|
||||
use shared::utils::sampling::{DevicePiecewiseConstant1D, DevicePiecewiseConstant2D};
|
||||
use std::alloc::Layout;
|
||||
use std::collections::HashMap;
|
||||
use std::slice::from_raw_parts;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Arena {
|
||||
buffer: Vec<(*mut u8, Layout)>,
|
||||
texture_cache: HashMap<usize, u64>,
|
||||
}
|
||||
|
||||
impl Arena {
|
||||
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> {
|
||||
|
|
@ -71,25 +78,53 @@ impl Arena {
|
|||
panic!("cudaMallocManaged failed: {:?}", result);
|
||||
}
|
||||
|
||||
self.allocations.push((ptr as *mut u8, layout));
|
||||
self.buffer.push((ptr as *mut u8, layout));
|
||||
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"))]
|
||||
unsafe fn alloc_unified(&mut self, layout: Layout) -> *mut u8 {
|
||||
let ptr = std::alloc::alloc(layout);
|
||||
self.allocations.push((ptr, layout));
|
||||
let ptr = unsafe { std::alloc::alloc(layout) };
|
||||
self.buffer.push((ptr, layout));
|
||||
ptr
|
||||
}
|
||||
|
||||
pub fn raw_data(&self) -> &[u8] {
|
||||
&self.buffer
|
||||
}
|
||||
// pub fn raw_data(&self) -> &[u8] {
|
||||
// &self.buffer
|
||||
// }
|
||||
}
|
||||
|
||||
impl Drop for Arena {
|
||||
fn drop(&mut self) {
|
||||
for (ptr, layout) in self.allocations.drain(..) {
|
||||
for (ptr, layout) in self.buffer.drain(..) {
|
||||
unsafe {
|
||||
#[cfg(feature = "cuda")]
|
||||
{
|
||||
|
|
@ -120,7 +155,7 @@ impl Upload for Shape {
|
|||
impl Upload for Image {
|
||||
type Target = DeviceImage;
|
||||
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::Checkerboard(tex) => GPUSpectrumTexture::Checkerboard(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::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,
|
||||
scale: tex.scale.upload(arena),
|
||||
};
|
||||
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::RGBConstant(tex) => GPUSpectrumTexture::RGBConstant(tex.clone()),
|
||||
SpectrumTexture::RGBReflectanceConstant(tex) => {
|
||||
GPUSpectrumTexture::RGBReflectanceConstant(tex.clone())
|
||||
}
|
||||
SpectrumTexture::Mix(tex) => {
|
||||
let tex1_ptr = tex.tex1.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::Constant(val) => GPUFloatTexture::Constant(*val),
|
||||
FloatTexture::Scaled(tex) => {
|
||||
let child_ptr = tex.texture.upload(arena);
|
||||
let child_ptr = tex.tex.upload(arena);
|
||||
|
||||
let gpu_scaled = GPUFloatScaledTexture {
|
||||
tex: child_ptr,
|
||||
|
|
@ -230,15 +281,13 @@ impl Upload for FloatTexture {
|
|||
};
|
||||
GPUFloatTexture::DirectionMix(gpu_dmix)
|
||||
}
|
||||
FloatTexture::Image(tex) => {
|
||||
let image_ptr = tex.image.upload(arena);
|
||||
|
||||
FloatTexture::Image(tex) => {
|
||||
let gpu_image_tex = GPUFloatImageTexture {
|
||||
mapping: tex.mapping,
|
||||
mapping: tex.base.mapping,
|
||||
tex_obj: image_ptr.offset as u64,
|
||||
scale: tex.scale,
|
||||
invert: tex.invert,
|
||||
mapping: tex.mapping,
|
||||
scale: tex.base.scale,
|
||||
invert: tex.base.invert,
|
||||
};
|
||||
GPUFloatTexture::Image(gpu_image_tex)
|
||||
}
|
||||
|
|
@ -258,12 +307,16 @@ impl Upload for RGBToSpectrumTable {
|
|||
type Target = RGBToSpectrumTable;
|
||||
|
||||
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
|
||||
let z_ptr = arena.alloc_slice(&self.z_nodes);
|
||||
let c_ptr = arena.alloc_slice(&self.coeffs);
|
||||
let n_nodes = self.n_nodes as usize;
|
||||
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 {
|
||||
z_nodes: z_ptr,
|
||||
coeffs: c_ptr,
|
||||
n_nodes: self.n_nodes,
|
||||
};
|
||||
|
||||
arena.alloc(shared_table)
|
||||
|
|
@ -291,8 +344,8 @@ impl Upload for RGBColorSpace {
|
|||
}
|
||||
}
|
||||
|
||||
impl Upload for StandardColorSpaces {
|
||||
type Target = StandardColorSpaces;
|
||||
impl Upload for DeviceStandardColorSpaces {
|
||||
type Target = DeviceStandardColorSpaces;
|
||||
|
||||
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
|
||||
let srgb_ptr = self.srgb.upload(arena);
|
||||
|
|
@ -300,7 +353,7 @@ impl Upload for StandardColorSpaces {
|
|||
let rec_ptr = self.rec2020.upload(arena);
|
||||
let aces_ptr = self.aces2065_1.upload(arena);
|
||||
|
||||
let registry = StandardColorSpaces {
|
||||
let registry = DeviceStandardColorSpaces {
|
||||
srgb: srgb_ptr,
|
||||
dci_p3: dci_ptr,
|
||||
rec2020: rec_ptr,
|
||||
|
|
@ -315,15 +368,15 @@ impl Upload for PiecewiseConstant2D {
|
|||
type Target = DevicePiecewiseConstant2D;
|
||||
|
||||
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
|
||||
.p_conditionals
|
||||
.conditionals
|
||||
.iter()
|
||||
.map(|c| c.to_shared(arena))
|
||||
.collect();
|
||||
|
||||
let conditionals_ptr = arena.alloc_slice(&conditionals_shared);
|
||||
let (conditionals_ptr, _) = arena.alloc_slice(&conditionals_shared);
|
||||
|
||||
let shared_2d = DevicePiecewiseConstant2D {
|
||||
conditionals: conditionals_ptr,
|
||||
|
|
@ -339,44 +392,44 @@ impl Upload for TriangleMesh {
|
|||
type Target = DeviceTriangleMesh;
|
||||
|
||||
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
|
||||
let storage = &self._storage;
|
||||
let storage = &self.storage;
|
||||
|
||||
// Upload all arrays to arena
|
||||
let vertex_indices_ptr = arena.alloc_slice(&storage.vertex_indices);
|
||||
let p_ptr = arena.alloc_slice(&storage.p);
|
||||
let (vertex_indices_ptr, _) = arena.alloc_slice(&storage.vertex_indices);
|
||||
let (p_ptr, _) = arena.alloc_slice(&storage.p);
|
||||
|
||||
let n_ptr = if storage.n.is_empty() {
|
||||
std::ptr::null()
|
||||
let (n_ptr, _) = if storage.n.is_empty() {
|
||||
(Ptr::null(), 0)
|
||||
} else {
|
||||
arena.alloc_slice(&storage.n).as_ptr()
|
||||
arena.alloc_slice(&storage.n)
|
||||
};
|
||||
|
||||
let s_ptr = if storage.s.is_empty() {
|
||||
std::ptr::null()
|
||||
let (s_ptr, _) = if storage.s.is_empty() {
|
||||
(Ptr::null(), 0)
|
||||
} else {
|
||||
arena.alloc_slice(&storage.s).as_ptr()
|
||||
arena.alloc_slice(&storage.s)
|
||||
};
|
||||
|
||||
let uv_ptr = if storage.uv.is_empty() {
|
||||
std::ptr::null()
|
||||
let (uv_ptr, _) = if storage.uv.is_empty() {
|
||||
(Ptr::null(), 0)
|
||||
} else {
|
||||
arena.alloc_slice(&storage.uv).as_ptr()
|
||||
arena.alloc_slice(&storage.uv)
|
||||
};
|
||||
|
||||
let face_indices_ptr = if storage.face_indices.is_empty() {
|
||||
std::ptr::null()
|
||||
let (face_indices_ptr, _) = if storage.face_indices.is_empty() {
|
||||
(Ptr::null(), 0)
|
||||
} else {
|
||||
arena.alloc_slice(&storage.face_indices).as_ptr()
|
||||
arena.alloc_slice(&storage.face_indices)
|
||||
};
|
||||
|
||||
let device = DeviceTriangleMesh {
|
||||
vertex_indices: vertex_indices_ptr.as_ptr(),
|
||||
p: p_ptr.as_ptr(),
|
||||
vertex_indices: vertex_indices_ptr,
|
||||
p: p_ptr,
|
||||
n: n_ptr,
|
||||
s: s_ptr,
|
||||
uv: uv_ptr,
|
||||
face_indices: face_indices_ptr,
|
||||
..self.device // Copy n_triangles, n_vertices, flags
|
||||
..self.device
|
||||
};
|
||||
|
||||
arena.alloc(device)
|
||||
|
|
@ -387,33 +440,28 @@ impl Upload for BilinearPatchMesh {
|
|||
type Target = DeviceBilinearPatchMesh;
|
||||
|
||||
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 p_ptr = arena.alloc_slice(&storage.p);
|
||||
let (vertex_indices_ptr, _) = arena.alloc_slice(&storage.vertex_indices);
|
||||
let (p_ptr, _) = arena.alloc_slice(&storage.p);
|
||||
|
||||
let n_ptr = if storage.n.is_empty() {
|
||||
std::ptr::null()
|
||||
let (n_ptr, _) = if storage.n.is_empty() {
|
||||
(Ptr::null(), 0)
|
||||
} else {
|
||||
arena.alloc_slice(&storage.n).as_ptr()
|
||||
arena.alloc_slice(&storage.n)
|
||||
};
|
||||
|
||||
let uv_ptr = if storage.uv.is_empty() {
|
||||
std::ptr::null()
|
||||
let (uv_ptr, _) = if storage.uv.is_empty() {
|
||||
(Ptr::null(), 0)
|
||||
} 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 uploaded = dist.upload(arena);
|
||||
uploaded.as_ptr()
|
||||
} else {
|
||||
std::ptr::null()
|
||||
};
|
||||
let image_dist_ptr = storage.image_distribution.upload(arena);
|
||||
|
||||
let device = DeviceBilinearPatchMesh {
|
||||
vertex_indices: vertex_indices_ptr.as_ptr(),
|
||||
p: p_ptr.as_ptr(),
|
||||
vertex_indices: vertex_indices_ptr,
|
||||
p: p_ptr,
|
||||
n: n_ptr,
|
||||
uv: uv_ptr,
|
||||
image_distribution: image_dist_ptr,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use parking_lot::Mutex;
|
|||
use shared::core::geometry::{Bounds2i, Point2i};
|
||||
use shared::utils::containers::Array2D;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -11,7 +12,7 @@ pub struct InternCache<T> {
|
|||
|
||||
impl<T> InternCache<T>
|
||||
where
|
||||
T: Eq + Clone,
|
||||
T: Eq + Clone + Hash,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
|
@ -20,14 +21,14 @@ where
|
|||
}
|
||||
|
||||
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) {
|
||||
return existing.clone();
|
||||
}
|
||||
|
||||
let new_item = Arc::new(value);
|
||||
lock.insert(new_item.clone());
|
||||
lock.insert(Arc::clone(&new_item));
|
||||
new_item
|
||||
}
|
||||
}
|
||||
|
|
@ -52,7 +53,7 @@ impl<T> DerefMut for Array2DBuffer<T> {
|
|||
|
||||
impl<T> Array2DBuffer<T> {
|
||||
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 {
|
||||
extent,
|
||||
values: storage.as_mut_ptr(),
|
||||
|
|
@ -73,12 +74,12 @@ impl<T: Default + Clone> Array2DBuffer<T> {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 storage = vec![val; n];
|
||||
Self::from_vec(extent, storage)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
use shared::Float;
|
||||
use shared::utils::Ptr;
|
||||
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 inv_base = 1. / base as Float;
|
||||
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);
|
||||
|
||||
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] =
|
||||
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>) {
|
||||
let temp_data: Vec<Vec<u16>> = PRIMES
|
||||
.iter()
|
||||
.map(|&base| new_digit_permutation(base as u32, seed))
|
||||
.map(|&base| DigitPermutation::new(base as i32, seed).permutations)
|
||||
.collect();
|
||||
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 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() {
|
||||
let len = temp_data[i].len();
|
||||
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(
|
||||
base as u32,
|
||||
n_digits,
|
||||
Ptr::from(ptr_to_data),
|
||||
base as i32,
|
||||
n_digits as u64,
|
||||
// Ptr::from(ptr_to_data),
|
||||
));
|
||||
}
|
||||
|
||||
current_offset += len;
|
||||
// current_offset += len;
|
||||
}
|
||||
|
||||
(storage, views)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::image::Image;
|
||||
use crate::core::image::{Image, ImageIO};
|
||||
use shared::Float;
|
||||
use shared::core::color::{ColorEncoding, RGB};
|
||||
use shared::core::geometry::{Point2f, Point2i, Vector2f, VectorLike};
|
||||
|
|
@ -6,10 +6,12 @@ use shared::core::image::{WrapMode, WrapMode2D};
|
|||
use shared::spectra::RGBColorSpace;
|
||||
use shared::utils::math::{lerp, safe_sqrt, square};
|
||||
use std::path::Path;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Add, Mul, Sub};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum FilterFunction {
|
||||
Point,
|
||||
|
|
@ -30,10 +32,11 @@ impl std::fmt::Display for FilterFunction {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct MIPMapFilterOptions {
|
||||
pub filter: FilterFunction,
|
||||
pub max_anisotropy: f32,
|
||||
pub max_anisotropy: Float,
|
||||
}
|
||||
|
||||
impl Default for MIPMapFilterOptions {
|
||||
|
|
@ -116,12 +119,12 @@ impl MIPMapSample for RGB {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MIPMap {
|
||||
pyramid: Vec<Image>,
|
||||
color_space: Option<RGBColorSpace>,
|
||||
wrap_mode: WrapMode,
|
||||
options: MIPMapFilterOptions,
|
||||
pub pyramid: Vec<Image>,
|
||||
pub color_space: Option<RGBColorSpace>,
|
||||
pub wrap_mode: WrapMode,
|
||||
pub options: MIPMapFilterOptions,
|
||||
}
|
||||
|
||||
impl MIPMap {
|
||||
|
|
@ -144,6 +147,11 @@ impl MIPMap {
|
|||
self.pyramid[level].resolution()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_single_channel(&self) -> bool {
|
||||
self.pyramid[0].n_channels() == 1
|
||||
}
|
||||
|
||||
pub fn levels(&self) -> usize {
|
||||
self.pyramid.len()
|
||||
}
|
||||
|
|
@ -156,6 +164,10 @@ impl MIPMap {
|
|||
&self.pyramid[level]
|
||||
}
|
||||
|
||||
pub fn base_image(&self) -> &Image {
|
||||
&self.pyramid[0]
|
||||
}
|
||||
|
||||
pub fn filter<T: MIPMapSample>(
|
||||
&self,
|
||||
st: Point2f,
|
||||
|
|
@ -335,6 +347,83 @@ impl MIPMap {
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::core::spectrum::SPECTRUM_CACHE;
|
||||
use crate::core::texture::{FloatTexture, SpectrumTexture};
|
||||
use crate::spectra::data::get_named_spectrum;
|
||||
use crate::spectra::piecewise::PiecewiseLinearSpectrumBuffer;
|
||||
use crate::utils::FileLoc;
|
||||
use shared::Float;
|
||||
use shared::core::color::RGB;
|
||||
|
|
@ -132,7 +133,7 @@ impl PBRTParameter for bool {
|
|||
v[0]
|
||||
}
|
||||
fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
|
||||
param.bools
|
||||
¶m.bools
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +145,7 @@ impl PBRTParameter for Float {
|
|||
v[0]
|
||||
}
|
||||
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] {
|
||||
param.ints
|
||||
¶m.ints
|
||||
}
|
||||
}
|
||||
|
||||
impl PBRTParameter for Point2f {
|
||||
type Raw = Point2f;
|
||||
type Raw = Float;
|
||||
const TYPE_NAME: &'static str = "point2";
|
||||
const N_PER_ITEM: usize = 2;
|
||||
fn convert(v: &[Self::Raw]) -> Self {
|
||||
Point2f::new(v[0], v[1])
|
||||
}
|
||||
fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
|
||||
param.floats
|
||||
¶m.floats
|
||||
}
|
||||
}
|
||||
|
||||
impl PBRTParameter for Point3f {
|
||||
type Raw = Point3f;
|
||||
type Raw = Float;
|
||||
const TYPE_NAME: &'static str = "point3";
|
||||
const N_PER_ITEM: usize = 3;
|
||||
fn convert(v: &[Self::Raw]) -> Self {
|
||||
Point3f::new(v[0], v[1], v[2])
|
||||
}
|
||||
fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
|
||||
param.floats
|
||||
¶m.floats
|
||||
}
|
||||
}
|
||||
|
||||
impl PBRTParameter for Vector2f {
|
||||
type Raw = Vector2f;
|
||||
type Raw = Float;
|
||||
const TYPE_NAME: &'static str = "vector2";
|
||||
const N_PER_ITEM: usize = 3;
|
||||
const N_PER_ITEM: usize = 2;
|
||||
fn convert(v: &[Self::Raw]) -> Self {
|
||||
Vector2f::new(v[0], v[1])
|
||||
}
|
||||
fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
|
||||
param.floats
|
||||
¶m.floats
|
||||
}
|
||||
}
|
||||
|
||||
impl PBRTParameter for Normal3f {
|
||||
type Raw = Normal3f;
|
||||
const TYPE_NAME: &'static str = "normal";
|
||||
impl PBRTParameter for Vector3f {
|
||||
type Raw = Float;
|
||||
const TYPE_NAME: &'static str = "vector3";
|
||||
const N_PER_ITEM: usize = 3;
|
||||
fn convert(v: &[Self::Raw]) -> Self {
|
||||
Vector3f::new(v[0], v[1], v[2])
|
||||
}
|
||||
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()
|
||||
}
|
||||
fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
|
||||
param.strings
|
||||
¶m.strings
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -272,7 +285,7 @@ impl ParameterDictionary {
|
|||
bool::TYPE_NAME => {
|
||||
if p.bools.is_empty() {
|
||||
error_exit(
|
||||
&p.loc,
|
||||
Some(&p.loc),
|
||||
&format!(
|
||||
"\"{}\": non-Boolean values provided for Boolean-valued parameter",
|
||||
p.name
|
||||
|
|
@ -292,7 +305,7 @@ impl ParameterDictionary {
|
|||
| "blackbody" => {
|
||||
if p.ints.is_empty() && p.floats.is_empty() {
|
||||
error_exit(
|
||||
&p.loc,
|
||||
Some(&p.loc),
|
||||
&format!(
|
||||
"\"{}\": non-numeric values provided for numeric-valued parameter",
|
||||
p.name
|
||||
|
|
@ -304,7 +317,7 @@ impl ParameterDictionary {
|
|||
String::TYPE_NAME | "texture" => {
|
||||
if p.strings.is_empty() {
|
||||
error_exit(
|
||||
&p.loc,
|
||||
Some(&p.loc),
|
||||
&format!(
|
||||
"\"{}\": non-string values provided for string-valued parameter",
|
||||
p.name
|
||||
|
|
@ -316,7 +329,7 @@ impl ParameterDictionary {
|
|||
"spectrum" => {
|
||||
if p.strings.is_empty() && p.ints.is_empty() && p.floats.is_empty() {
|
||||
error_exit(
|
||||
&p.loc,
|
||||
Some(&p.loc),
|
||||
&format!(
|
||||
"\"{}\": expecting string or numeric-valued parameter for spectrum parameter",
|
||||
p.name
|
||||
|
|
@ -327,7 +340,7 @@ impl ParameterDictionary {
|
|||
|
||||
unknown => {
|
||||
error_exit(
|
||||
&p.loc,
|
||||
Some(&p.loc),
|
||||
&format!("\"{}\": unknown parameter type '{}'", p.name, unknown),
|
||||
);
|
||||
}
|
||||
|
|
@ -339,20 +352,20 @@ impl ParameterDictionary {
|
|||
where
|
||||
T: PBRTParameter,
|
||||
{
|
||||
let param = self.params;
|
||||
let param = self.params[0].clone();
|
||||
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() {
|
||||
error_exit(
|
||||
¶m.loc,
|
||||
Some(¶m.loc),
|
||||
&format!("No values provided for parameter \"{}\".", name),
|
||||
);
|
||||
}
|
||||
|
||||
if values.len() != T::N_PER_ITEM {
|
||||
error_exit(
|
||||
¶m.loc,
|
||||
Some(¶m.loc),
|
||||
&format!(
|
||||
"Expected {} values for parameter \"{}\". Found {}.",
|
||||
T::N_PER_ITEM,
|
||||
|
|
@ -379,7 +392,7 @@ impl ParameterDictionary {
|
|||
|
||||
if values.len() % T::N_PER_ITEM != 0 {
|
||||
error_exit(
|
||||
¶m.loc,
|
||||
Some(¶m.loc),
|
||||
&format!(
|
||||
"Number of values for \"{}\" is not a multiple of {}",
|
||||
name,
|
||||
|
|
@ -412,7 +425,7 @@ impl ParameterDictionary {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -561,8 +574,8 @@ impl ParameterDictionary {
|
|||
) -> Vec<Spectrum> {
|
||||
match param.type_name.as_str() {
|
||||
"rgb" | "color" => self.extract_rgb_spectrum(param, spectrum_type),
|
||||
"blackbody" => self.extract_blackbody_spectrum(param),
|
||||
"spectrum" => self.extract_complex_spectrum(param),
|
||||
"blackbody" => self.extract_file_spectrum(param),
|
||||
"spectrum" => self.extract_sampled_spectrum(param),
|
||||
_ => 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
|
||||
.chunks(2)
|
||||
.enumerate()
|
||||
|
|
@ -642,14 +655,14 @@ impl ParameterDictionary {
|
|||
lam
|
||||
);
|
||||
}
|
||||
(lam, val)
|
||||
(lam as Float, val as Float)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
vec![Spectrum::PiecewiseLinear(PiecewiseLinearSpectrum {
|
||||
lambdas,
|
||||
values,
|
||||
count: lambdas.len(),
|
||||
vec![Spectrum::Piecewise(PiecewiseLinearSpectrum {
|
||||
lambdas: lambdas.as_ptr().into(),
|
||||
values: values.as_ptr().into(),
|
||||
count: lambdas.len() as u32,
|
||||
})]
|
||||
}
|
||||
|
||||
|
|
@ -670,19 +683,19 @@ impl ParameterDictionary {
|
|||
fn read_spectrum_from_file(filename: &str) -> Result<Spectrum, 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) {
|
||||
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))?;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
@ -794,7 +807,7 @@ impl TextureParameterDictionary {
|
|||
if tex.is_some() {
|
||||
tex
|
||||
} else if val.is_some() {
|
||||
Some(Arc::new(SpectrumTexture::SpectrumConstant(
|
||||
Some(Arc::new(SpectrumTexture::Constant(
|
||||
SpectrumConstantTexture::new(val.unwrap()),
|
||||
)))
|
||||
} else {
|
||||
|
|
@ -806,7 +819,7 @@ impl TextureParameterDictionary {
|
|||
if let Some(tex) = self.get_float_texture_or_null(name) {
|
||||
return tex;
|
||||
} 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))
|
||||
}
|
||||
};
|
||||
return Some(Arc::new(SpectrumTexture::SpectrumConstant(
|
||||
return Some(Arc::new(SpectrumTexture::Constant(
|
||||
SpectrumConstantTexture::new(s),
|
||||
)));
|
||||
}
|
||||
|
||||
"spectrum" | "blackbody" => {
|
||||
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),
|
||||
)));
|
||||
}
|
||||
|
|
@ -930,9 +943,9 @@ impl TextureParameterDictionary {
|
|||
}
|
||||
"float" => {
|
||||
let v = self.get_one_float(name, 0.);
|
||||
return Some(Arc::new(FloatTexture::FloatConstant(
|
||||
FloatConstantTexture::new(v),
|
||||
)));
|
||||
return Some(Arc::new(FloatTexture::Constant(FloatConstantTexture::new(
|
||||
v,
|
||||
))));
|
||||
}
|
||||
_ => {
|
||||
panic!("[{:?}] Couldn't find float texture", p.loc);
|
||||
|
|
|
|||
|
|
@ -753,7 +753,9 @@ impl<'a> SceneParser<'a> {
|
|||
'A' => match token.text.as_str() {
|
||||
"AttributeBegin" => self.target.attribute_begin(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" => {
|
||||
let a = self.next_token_required()?;
|
||||
match a.text.as_str() {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ use shared::Float;
|
|||
use shared::core::geometry::{Point2i, Vector2f, Vector2i};
|
||||
use shared::utils::Ptr;
|
||||
use shared::utils::sampling::{
|
||||
AliasTable, Bin, DevicePiecewiseConstant1D, DevicePiecewiseConstant2D, PiecewiseLinear2D,
|
||||
AliasTable, Bin, DevicePiecewiseConstant1D, DevicePiecewiseConstant2D, DeviceSummedAreaTable,
|
||||
DeviceWindowedPiecewiseConstant2D, PiecewiseLinear2D,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -114,13 +115,22 @@ impl std::ops::Deref for PiecewiseConstant1D {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PiecewiseConstant2D {
|
||||
conditionals: Vec<PiecewiseConstant1D>,
|
||||
marginal: PiecewiseConstant1D,
|
||||
conditional_devices: Box<[DevicePiecewiseConstant1D]>,
|
||||
pub conditionals: Vec<PiecewiseConstant1D>,
|
||||
pub marginal: PiecewiseConstant1D,
|
||||
pub conditional_devices: Box<[DevicePiecewiseConstant1D]>,
|
||||
pub device: DevicePiecewiseConstant2D,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for PiecewiseConstant2D {
|
||||
type Target = DevicePiecewiseConstant2D;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl PiecewiseConstant2D {
|
||||
pub fn new(data: &[Float], n_u: usize, n_v: usize) -> Self {
|
||||
assert_eq!(data.len(), n_u * n_v);
|
||||
|
|
@ -149,7 +159,7 @@ impl PiecewiseConstant2D {
|
|||
.into_boxed_slice();
|
||||
|
||||
let device = DevicePiecewiseConstant2D {
|
||||
conditionals: conditional_devices.as_ptr(),
|
||||
conditionals: conditional_devices.as_ptr().into(),
|
||||
marginal: marginal.device,
|
||||
n_u: n_u 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 view: AliasTable,
|
||||
_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