diff --git a/shared/src/cameras/perspective.rs b/shared/src/cameras/perspective.rs index 20f2104..72e9b84 100644 --- a/shared/src/cameras/perspective.rs +++ b/shared/src/cameras/perspective.rs @@ -98,7 +98,7 @@ impl CameraTrait for PerspectiveCamera { Point3f::new(0., 0., 0.), p_vector.normalize(), Some(self.sample_time(sample.time)), - &*self.base().medium, + self.base().medium, ); // Modify ray for depth of field if self.lens_radius > 0. { diff --git a/shared/src/core/aggregates.rs b/shared/src/core/aggregates.rs index 8acf0ba..441f3d1 100644 --- a/shared/src/core/aggregates.rs +++ b/shared/src/core/aggregates.rs @@ -4,7 +4,7 @@ use crate::core::shape::ShapeIntersection; use crate::{Float, Ptr, GVec, gvec}; #[repr(C)] -#[derive(Debug, Clone, Copy)] +#[derive(Default, Debug, Clone, Copy)] pub struct LinearBVHNode { pub bounds: Bounds3f, pub primitives_offset: usize, diff --git a/shared/src/utils/math.rs b/shared/src/utils/math.rs index 2c43f1c..c86045a 100644 --- a/shared/src/utils/math.rs +++ b/shared/src/utils/math.rs @@ -798,7 +798,11 @@ impl DigitPermutation { inv_base_m *= inv_base; } - let mut permutations = gvec_with_capacity(n_digits as usize * base as usize); + let mut permutations = { + let mut v = gvec_with_capacity(n_digits as usize * base as usize); + v.resize(n_digits as usize * base as usize, 0u16); + v + }; for digit_index in 0..n_digits { let hash_input = [base as u64, digit_index as u64, seed]; @@ -829,7 +833,11 @@ impl DigitPermutation { pub fn compute_radical_inverse_permutations(seed: u64) -> GVec { let mut result = gvec(); - result.extend(PRIMES.iter().map(|&base| DigitPermutation::new(base as i32, seed))); + result.extend( + PRIMES + .iter() + .map(|&base| DigitPermutation::new(base as i32, seed)), + ); result } diff --git a/src/core/light.rs b/src/core/light.rs index 4c9aaef..6bb4499 100644 --- a/src/core/light.rs +++ b/src/core/light.rs @@ -6,10 +6,9 @@ use shared::core::camera::CameraTransform; use shared::core::light::Light; use shared::core::medium::Medium; use shared::core::shape::Shape; -use shared::spectra::DenselySampledSpectrum; use shared::core::spectrum::Spectrum; -use shared::spectra::RGBColorSpace; -use shared::{Ptr, Transform}; +use shared::spectra::{DenselySampledSpectrum, RGBColorSpace}; +use shared::Transform; use std::sync::Arc; pub fn lookup_spectrum(s: &Spectrum) -> Arc { @@ -18,9 +17,6 @@ pub fn lookup_spectrum(s: &Spectrum) -> Arc { cache.lookup(dense_spectrum).into() } -// Placeholders for non-area lights that never inspect these arguments. -// TODO: refactor each light to only take what it actually needs, -// then delete this bullshit fn dummy_shape() -> Shape { Shape::default() } @@ -29,6 +25,8 @@ fn dummy_alpha() -> FloatTexture { FloatTexture::default() } +/// Create a non-area light. Returns a Light value — the caller decides +/// whether to wrap it in Arc (host ownership) or arena.alloc (GPU). pub fn create_light( name: &str, render_from_light: Transform, @@ -36,79 +34,47 @@ pub fn create_light( parameters: &ParameterDictionary, loc: &FileLoc, camera_transform: CameraTransform, - arena: &mut Arena, + arena: &Arena, ) -> Result { let shape = dummy_shape(); let alpha = dummy_alpha(); - match name { "point" => crate::lights::point::create( - render_from_light, - medium, - parameters, - loc, - &shape, - &alpha, - None, - arena, + render_from_light, medium, parameters, loc, + &shape, &alpha, None, arena, ), "spot" => crate::lights::spot::create( - render_from_light, - medium, - parameters, - loc, - &shape, - &alpha, - None, - arena, + render_from_light, medium, parameters, loc, + &shape, &alpha, None, arena, ), "distant" => crate::lights::distant::create( - render_from_light, - medium, - parameters, - loc, - &shape, - &alpha, - None, - arena, + render_from_light, medium, parameters, loc, + &shape, &alpha, None, arena, ), "goniometric" => crate::lights::goniometric::create( - render_from_light, - medium, - parameters, - loc, - &shape, - &alpha, - None, - arena, + render_from_light, medium, parameters, loc, + &shape, &alpha, None, arena, ), "projection" => crate::lights::projection::create( - render_from_light, - medium, - parameters, - loc, - &shape, - &alpha, - None, - arena, + render_from_light, medium, parameters, loc, + &shape, &alpha, None, arena, ), "infinite" => crate::lights::infinite::create( - render_from_light, - medium.into(), - camera_transform, - parameters, - None, - loc, - arena, + render_from_light, medium.into(), camera_transform, + parameters, None, loc, arena, ), "diffuse" => Err(anyhow!( - "{}: \"diffuse\" is an area light. Use create_area_light with a shape", + "{}: \"diffuse\" is an area light; use create_area_light with a shape", loc )), _ => Err(anyhow!("{}: unknown light type \"{}\"", loc, name)), } } +/// Create a diffuse area light bound to a specific shape. +/// Returns a Light value. The individual light constructor still uses +/// the arena internally for sub-allocations (shape, image, spectra), +/// but the Light itself is returned as a value for the caller to place. pub fn create_area_light( render_from_light: Transform, medium: Option, @@ -118,10 +84,9 @@ pub fn create_area_light( alpha_tex: &FloatTexture, colorspace: Option<&RGBColorSpace>, arena: &Arena, -) -> Result> { - let light = crate::lights::diffuse::create( +) -> Result { + crate::lights::diffuse::create( render_from_light, medium, parameters, loc, shape, alpha_tex, colorspace, arena, - )?; - Ok(arena.alloc(light)) + ) } diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index 9d81778..0a9af77 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -36,7 +36,7 @@ pub struct SceneLookup<'a> { pub media: &'a HashMap>, pub named_materials: &'a HashMap, pub materials: &'a Vec, - pub shape_lights: &'a HashMap>>, + pub shape_lights: &'a HashMap>, } impl<'a> SceneLookup<'a> { @@ -130,8 +130,9 @@ impl BasicScene { *self.film_colorspace.lock() = Some(Arc::clone(cs)); } - let filter = Filter::create(&filter.name, &filter.parameters, &filter.loc, &arena) + let filter = Filter::create(&filter.name, &filter.parameters, &filter.loc, arena) .map_err(|e| anyhow!("Failed to create filter: {}", e))?; + 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; @@ -144,7 +145,7 @@ impl BasicScene { filter, Some(camera.camera_transform.clone()), &film.loc, - &arena, + arena, ) .map_err(|e| anyhow!("Failed to create film: {}", e))?, ); @@ -154,36 +155,33 @@ impl BasicScene { 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, - &arena, - ) - .map_err(|e| anyhow!("Failed to create sampler: {}", e)) - }); - self.sampler_state.lock().job = Some(sampler_job); + let res = film_instance.as_ref().base().full_resolution; + let sampler_result = + Sampler::create(&sampler.name, &sampler.parameters, res, &sampler.loc, arena) + .map_err(|e| anyhow!("Failed to create sampler: {}", e))?; + + *self.sampler_state.lock() = SingletonState { + result: Some(Arc::new(sampler_result)), + job: None, + }; + + let medium = self.get_medium(&camera.medium, &camera.base.loc); + let camera_result = Camera::create( + &camera.base.name, + &camera.base.parameters, + &camera.camera_transform, + medium, + Arc::clone(&film_instance), + &camera.base.loc, + arena, + ) + .map_err(|e| anyhow!("Failed to create camera: {}", e))?; + + *self.camera_state.lock() = SingletonState { + result: Some(Arc::new(camera_result)), + job: None, + }; - 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, - &camera.camera_transform, - medium, - camera_film, - &camera.base.loc, - &arena, - ) - .map_err(|e| anyhow!("Failed to create camera: {}", e)) - }); - self.camera_state.lock().job = Some(camera_job); Ok(()) } @@ -506,11 +504,7 @@ impl BasicScene { Ok((named_materials, materials)) } - pub fn create_lights( - &self, - camera_transform: &CameraTransform, - arena: &mut Arena, - ) -> Vec { + pub fn create_lights(&self, camera_transform: &CameraTransform, arena: &Arena) -> Vec { let state = self.light_state.lock(); state @@ -555,9 +549,9 @@ impl BasicScene { shape_entities: &[ShapeSceneEntity], textures: &NamedTextures, arena: &Arena, - ) -> HashMap>> { + ) -> HashMap> { let light_state = self.light_state.lock(); - let mut shape_lights: HashMap>> = HashMap::new(); + let mut shape_lights: HashMap> = HashMap::new(); for (i, entity) in shape_entities.iter().enumerate() { let light_idx = match entity.light_index { @@ -581,7 +575,6 @@ impl BasicScene { let default_alpha = Arc::new(FloatTexture::default()); let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha); - // Use the film colorspace as fallback for area light emission let film_cs = self.film_colorspace.lock(); let colorspace_ref = al_entity .parameters @@ -591,7 +584,7 @@ impl BasicScene { let render_from_light = *entity.render_from_object; - let lights: Vec> = shapes + let lights: Vec = shapes .iter() .filter_map(|shape| { match crate::core::light::create_area_light( @@ -678,7 +671,7 @@ impl BasicScene { al_params: Option<&SceneEntity>, render_from_light: Transform, film_cs: Option<&RGBColorSpace>, - arena: &mut Arena, + arena: &Arena, area_lights: &mut Vec>, ) -> Vec<(Ptr, Ptr, Ptr)> { shapes @@ -689,7 +682,7 @@ impl BasicScene { let cs = al_entity.parameters.color_space.as_deref().or(film_cs); let default_alpha = Arc::new(FloatTexture::default()); let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha); - crate::core::light::create_area_light( + match crate::core::light::create_area_light( render_from_light, None, &al_entity.parameters, @@ -698,15 +691,21 @@ impl BasicScene { alpha_ref, cs, arena, - ) - .ok() + ) { + Ok(light) => { + // Keep an Arc copy for the host-side light list + area_lights.push(Arc::new(light)); + // Alloc into arena for the GPU primitive + Some(arena.alloc(light)) + } + Err(e) => { + log::error!("Failed to create area light: {}", e); + None + } + } }) .unwrap_or(Ptr::null()); - if !area_light_ptr.is_null() { - area_lights.push(Arc::new(unsafe { &*area_light_ptr.as_raw() }.clone())); - } - let alpha_ptr = alpha_tex .as_ref() .map(|t| arena.upload(t.as_ref())) @@ -932,13 +931,16 @@ impl BasicScene { .unwrap_or(Ptr::null()), }; - let shape_lights_opt = lookup + // Light is &Light from the HashMap — alloc into arena for the Ptr + let light_ptr = lookup .shape_lights .get(&shape_ctx.entity_index) - .and_then(|lights| lights.get(shape_ctx.shape_index)); + .and_then(|lights| lights.get(shape_ctx.shape_index)) + .map(|l| arena.alloc(*l)) + .unwrap_or(Ptr::null()); - let light_ptr = shape_lights_opt.copied().unwrap_or(Ptr::null()); let shape_ptr = shape_ctx.shape; + let prim = if light_ptr.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() { Primitive::Simple(SimplePrimitive::new(shape_ptr, Ptr::from(&mtl))) } else { @@ -947,7 +949,10 @@ impl BasicScene { arena.alloc(mtl), light_ptr, mi.clone(), - arena.upload(alpha_tex), + alpha_tex + .as_ref() + .map(|t| arena.upload(t.as_ref())) + .unwrap_or(Ptr::null()), )) }; diff --git a/src/lights/diffuse.rs b/src/lights/diffuse.rs index 30dc47b..b479e2f 100644 --- a/src/lights/diffuse.rs +++ b/src/lights/diffuse.rs @@ -11,7 +11,7 @@ use shared::core::light::{Light, LightBase, LightType}; use shared::core::medium::{Medium, MediumInterface}; use shared::core::shape::{Shape, ShapeTrait}; use shared::core::spectrum::Spectrum; -use shared::core::texture::{SpectrumType, TextureEvalContext, GPUFloatTexture}; +use shared::core::texture::{GPUFloatTexture, SpectrumType, TextureEvalContext}; use shared::lights::DiffuseAreaLight; use shared::spectra::RGBColorSpace; use shared::utils::Transform; @@ -101,7 +101,7 @@ pub fn create( // Upload alpha texture to GPU and check for null texture let alpha_ptr = arena.upload(alpha); - let light_type = match alpha_ptr.as_ref() { + let light_type = match unsafe { alpha_ptr.as_ref() } { GPUFloatTexture::Constant(t) if t.evaluate(&TextureEvalContext::default()) == 0.0 => { LightType::DeltaPosition } diff --git a/src/lights/sampler.rs b/src/lights/sampler.rs index 126d8a8..d9303dc 100644 --- a/src/lights/sampler.rs +++ b/src/lights/sampler.rs @@ -1,60 +1,84 @@ use crate::Arena; use shared::core::light::{Light, LightTrait}; -use shared::lights::sampler::PowerLightSampler; +use shared::lights::sampler::{ + BVHLightSampler, LightSampler, PowerLightSampler, UniformLightSampler, +}; use shared::utils::sampling::AliasTable; use shared::spectra::{SampledSpectrum, SampledWavelengths}; -use std::collections::HashMap; +use shared::utils::Ptr; +use shared::Float; use std::sync::Arc; -pub struct PowerSamplerHost { - pub lights: Vec>, - pub light_to_index: HashMap, - pub alias_table: AliasTable, -} - -impl PowerSamplerHost { - pub fn new(lights: &[Arc]) -> Self { - if lights.is_empty() { - return Self { - lights: Vec::new(), - light_to_index: HashMap::new(), - alias_table: AliasTable::new(&[]), - }; +/// Top-level dispatcher matching the C++ LightSampler::Create. +pub fn create_light_sampler( + name: &str, + lights: &[Arc], + arena: &Arena, +) -> LightSampler { + let device_lights = lights_to_slice(lights, arena); + match name { + "uniform" => LightSampler::Uniform(create_uniform(device_lights, lights.len())), + "power" => LightSampler::Power(create_power(lights, device_lights, arena)), + "bvh" => { + log::warn!("BVH light sampler not yet implemented, falling back to power"); + LightSampler::Power(create_power(lights, device_lights, arena)) } - - let mut lights_vec = Vec::with_capacity(lights.len()); - let mut light_to_index = HashMap::with_capacity(lights.len()); - let mut light_power = Vec::with_capacity(lights.len()); - - let lambda = SampledWavelengths::sample_visible(0.5); - - for (i, light) in lights.iter().enumerate() { - lights_vec.push(light.clone()); - - let ptr = Arc::as_ptr(light) as usize; - light_to_index.insert(ptr, i); - - let phi = SampledSpectrum::safe_div(&light.phi(lambda), &lambda.pdf()); - light_power.push(phi.average()); - } - - let alias_table = AliasTable::new(&light_power); - - Self { - lights: lights_vec, - light_to_index, - alias_table, + _ => { + log::error!("Unknown light sampler \"{}\", using power", name); + LightSampler::Power(create_power(lights, device_lights, arena)) } } - // pub fn to_device(&self, arena: &Arena) -> PowerLightSampler { - // let device_lights: Vec = self.lights.iter().map(|l| (**l).clone()).collect(); - // let (lights_ptr, _) = arena.alloc_slice(&device_lights); - // let alias_device = self.alias_table.to_device(arena); - // - // PowerLightSampler { - // lights: lights_ptr, - // lights_len: self.lights.len() as u32, - // alias_table: alias_device, - // } - // } +} + +fn lights_to_slice(lights: &[Arc], arena: &Arena) -> (Ptr, u32) { + if lights.is_empty() { + return (Ptr::null(), 0); + } + let vals: Vec = lights.iter().map(|l| **l).collect(); + let (ptr, _) = arena.alloc_slice(&vals); + (ptr, lights.len() as u32) +} + +fn create_uniform( + (lights, lights_len): (Ptr, u32), + _count: usize, +) -> UniformLightSampler { + UniformLightSampler::new(lights, lights_len) +} + +fn create_power( + host_lights: &[Arc], + (lights, lights_len): (Ptr, u32), + arena: &Arena, +) -> PowerLightSampler { + if host_lights.is_empty() { + return PowerLightSampler { + lights: Ptr::null(), + lights_len: 0, + alias_table: Ptr::null(), + }; + } + + let lambda = SampledWavelengths::sample_visible(0.5); + let mut light_power: Vec = host_lights + .iter() + .map(|l| { + let phi = SampledSpectrum::safe_div(&l.phi(lambda), &lambda.pdf()); + phi.average() + }) + .collect(); + + // If all lights have zero power, treat as uniform + if light_power.iter().sum::() == 0.0 { + light_power.fill(1.0); + } + + let alias_table = AliasTable::new(&light_power); + let alias_ptr = arena.alloc(alias_table); + + PowerLightSampler { + lights, + lights_len, + alias_table: alias_ptr, + } } diff --git a/src/materials/diffuse.rs b/src/materials/diffuse.rs index 2079c4e..de64a43 100644 --- a/src/materials/diffuse.rs +++ b/src/materials/diffuse.rs @@ -1,22 +1,42 @@ -use crate::Arena; use crate::core::image::HostImage; use crate::core::material::CreateMaterial; +use crate::core::texture::SpectrumTexture; +use crate::utils::upload::ArenaUpload; use crate::utils::{FileLoc, TextureParameterDictionary}; +use crate::Arena; use anyhow::Result; use shared::core::material::Material; +use shared::core::spectrum::Spectrum; +use shared::core::texture::SpectrumType; use shared::materials::{DiffuseMaterial, DiffuseTransmissionMaterial}; +use shared::spectra::ConstantSpectrum; +use shared::textures::SpectrumConstantTexture; use std::collections::HashMap; use std::sync::Arc; impl CreateMaterial for DiffuseMaterial { fn create( - _parameters: &TextureParameterDictionary, - _normal_map: Option>, + parameters: &TextureParameterDictionary, + normal_map: Option>, _named_materials: &HashMap, _loc: &FileLoc, - _arena: &Arena, + arena: &Arena, ) -> Result { - todo!() + let reflectance = parameters + .get_spectrum_texture("reflectance", None, SpectrumType::Albedo) + .unwrap_or_else(|| { + Arc::new(SpectrumTexture::Constant( + SpectrumConstantTexture::new(Spectrum::Constant(ConstantSpectrum::new(0.5))), + )) + }); + let displacement = parameters.get_float_texture_or_null("displacement")?; + + let specific = DiffuseMaterial { + reflectance: arena.upload(reflectance), + displacement: arena.upload(displacement), + normal_map: arena.upload(normal_map), + }; + Ok(Material::Diffuse(specific)) } } diff --git a/src/spectra/colorspace.rs b/src/spectra/colorspace.rs index e289966..d6006f2 100644 --- a/src/spectra/colorspace.rs +++ b/src/spectra/colorspace.rs @@ -12,8 +12,8 @@ pub trait CreateRGBColorSpace { r: Point2f, g: Point2f, b: Point2f, - illuminant: &DenselySampledSpectrum, - rgb_to_spectrum_table: &RGBToSpectrumTable, + illuminant: Ptr, + rgb_to_spectrum_table: Ptr, ) -> Self; } @@ -22,19 +22,16 @@ impl CreateRGBColorSpace for RGBColorSpace { r: Point2f, g: Point2f, b: Point2f, - illuminant: &DenselySampledSpectrum, - rgb_to_spectrum_table: &RGBToSpectrumTable, + illuminant: Ptr, + rgb_to_spectrum_table: Ptr, ) -> Self { let stdspec = get_spectra_context(); - let illum_ptr = Ptr::from(illuminant); - let illum_spectrum = Spectrum::Dense(illum_ptr); + let illum_spectrum = Spectrum::Dense(illuminant); let w_xyz: XYZ = illum_spectrum.to_xyz(&stdspec); let w = w_xyz.xy(); - let r_xyz = XYZ::from_xyy(r, Some(1.0)); let g_xyz = XYZ::from_xyy(g, Some(1.0)); let b_xyz = XYZ::from_xyy(b, Some(1.0)); - let rgb_values = [ [r_xyz.x(), g_xyz.x(), b_xyz.x()], [r_xyz.y(), g_xyz.y(), b_xyz.y()], @@ -44,14 +41,13 @@ impl CreateRGBColorSpace for RGBColorSpace { let c: RGB = rgb.inverse().unwrap() * w_xyz; let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]); let rgb_from_xyz = xyz_from_rgb.inverse().expect("singular"); - RGBColorSpace { r, g, b, w, - illuminant: illum_ptr, - rgb_to_spectrum_table: Ptr::from(rgb_to_spectrum_table), + illuminant, + rgb_to_spectrum_table, xyz_from_rgb, rgb_from_xyz, } diff --git a/src/spectra/mod.rs b/src/spectra/mod.rs index e00104d..dd18f4e 100644 --- a/src/spectra/mod.rs +++ b/src/spectra/mod.rs @@ -6,7 +6,7 @@ use shared::core::spectrum::{Spectrum, StandardSpectra}; use shared::spectra::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z}; use shared::spectra::{DenselySampledSpectrum, DeviceStandardColorSpaces, RGBColorSpace}; use shared::Ptr; -use std::sync::{Arc, OnceLock, LazyLock}; +use std::sync::{Arc, LazyLock, OnceLock}; pub mod colorspace; pub mod data; @@ -21,10 +21,6 @@ pub static CIE_Z_DATA: LazyLock = pub static CIE_D65_DATA: LazyLock = LazyLock::new(|| data::create_cie(&CIE_D65)); -fn get_d65_illuminant_buffer() -> Arc { - Arc::new(CIE_D65_DATA.clone()) -} - pub fn cie_x() -> Spectrum { Spectrum::Dense(Ptr::from(&*CIE_X_DATA)) } @@ -47,42 +43,46 @@ pub fn get_spectra_context() -> StandardSpectra { } } +static D65_ILLUMINANT: LazyLock = LazyLock::new(|| CIE_D65_DATA.clone()); pub static SRGB: LazyLock> = LazyLock::new(|| { - let illum = get_d65_illuminant_buffer(); let r = Point2f::new(0.64, 0.33); let g = Point2f::new(0.3, 0.6); let b = Point2f::new(0.15, 0.06); let table_ptr = Ptr::from(&*SRGB_TABLE); + let illum_ptr = Ptr::from(&*D65_ILLUMINANT); - Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr)) + Arc::new(RGBColorSpace::new(r, g, b, illum_ptr, table_ptr)) }); pub static DCI_P3: LazyLock> = LazyLock::new(|| { - let illum = get_d65_illuminant_buffer(); let r = Point2f::new(0.680, 0.320); let g = Point2f::new(0.265, 0.690); let b = Point2f::new(0.150, 0.060); let table_ptr = Ptr::from(&*DCI_P3_TABLE); - Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr)) + let illum_ptr = Ptr::from(&*D65_ILLUMINANT); + + Arc::new(RGBColorSpace::new(r, g, b, illum_ptr, table_ptr)) }); pub static REC2020: LazyLock> = 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_ptr = Ptr::from(&*REC2020_TABLE); - Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr)) + let illum_ptr = Ptr::from(&*D65_ILLUMINANT); + + Arc::new(RGBColorSpace::new(r, g, b, illum_ptr, table_ptr)) }); pub static ACES: LazyLock> = 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 table_ptr = Ptr::from(&ACES_TABLE); - Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr)) + let table_ptr = Ptr::from(&*ACES_TABLE); + let illum_ptr = Ptr::from(&*D65_ILLUMINANT); + + Arc::new(RGBColorSpace::new(r, g, b, illum_ptr, table_ptr)) }); #[derive(Debug, Clone)] @@ -141,4 +141,3 @@ pub fn default_colorspace_ref() -> &'static RGBColorSpace { pub fn default_illuminant() -> Spectrum { Spectrum::Dense(default_colorspace().illuminant) } - diff --git a/src/utils/parameters.rs b/src/utils/parameters.rs index fdc58c3..39c55f4 100644 --- a/src/utils/parameters.rs +++ b/src/utils/parameters.rs @@ -915,7 +915,12 @@ impl TextureParameterDictionary { panic!("[{:?}] Negative RGB values for '{}'", p.loc, name); } - let cs = self.dict.color_space.as_ref().unwrap(); + let cs = self + .dict + .color_space + .as_ref() + .map(|arc| arc.as_ref()) + .unwrap_or_else(|| crate::spectra::default_colorspace_ref()); let s: Spectrum = match stype { SpectrumType::Illuminant => { Spectrum::RGBIlluminant(RGBIlluminantSpectrum::new(cs, rgb))