diff --git a/.gitignore b/.gitignore index 13d16e2..6c3e701 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ tests/ *.json *.txt scenes/ +compile.sh diff --git a/Cargo.toml b/Cargo.toml index b3a476b..91bd11c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,3 +67,6 @@ excessive_precision = "allow" approx_constant = "allow" upper_case_acronyms = "allow" wrong_self_convention = "allow" + +[profile.release] +debug = true diff --git a/shared/src/core/material.rs b/shared/src/core/material.rs index 3d90145..011d6bb 100644 --- a/shared/src/core/material.rs +++ b/shared/src/core/material.rs @@ -193,3 +193,4 @@ pub enum Material { ThinDielectric(ThinDielectricMaterial), Mix(MixMaterial), } + diff --git a/shared/src/core/spectrum.rs b/shared/src/core/spectrum.rs index d17ddeb..9648be4 100644 --- a/shared/src/core/spectrum.rs +++ b/shared/src/core/spectrum.rs @@ -40,7 +40,7 @@ pub enum Spectrum { impl Spectrum { pub fn std_illuminant_d65() -> Self { - todo!() + unimplemented!("Use crate::spectra::default_illuminant() on host") } pub fn to_xyz(&self, std: &StandardSpectra) -> XYZ { diff --git a/src/core/aggregates.rs b/src/core/aggregates.rs index a4a1d25..cd3bb53 100644 --- a/src/core/aggregates.rs +++ b/src/core/aggregates.rs @@ -1,13 +1,14 @@ use rayon::prelude::*; -use shared::Float; use shared::core::geometry::{Bounds3f, Point3f, Ray, Vector3f}; use shared::core::primitive::PrimitiveTrait; use shared::core::shape::ShapeIntersection; use shared::utils::math::encode_morton_3; use shared::utils::{find_interval, partition_slice}; +use shared::Float; use std::cmp::Ordering; -use std::sync::Arc; +use std::mem::MaybeUninit; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; +use std::sync::Arc; #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -120,17 +121,17 @@ impl BVHBuildNode { } } -pub struct SharedPrimitiveBuffer<'a> { - ptr: *mut Arc, +pub struct SharedPrimitiveBuffer<'a, P> { + ptr: *mut P, pub offset: &'a AtomicUsize, - _marker: std::marker::PhantomData<&'a mut [Arc]>, + _marker: std::marker::PhantomData<&'a mut [P]>, } -unsafe impl<'a> Sync for SharedPrimitiveBuffer<'a> {} -unsafe impl<'a> Send for SharedPrimitiveBuffer<'a> {} +unsafe impl<'a, P> Sync for SharedPrimitiveBuffer<'a, P> {} +unsafe impl<'a, P> Send for SharedPrimitiveBuffer<'a, P> {} -impl<'a> SharedPrimitiveBuffer<'a> { - pub fn new(slice: &'a mut [Arc], offset: &'a AtomicUsize) -> Self { +impl<'a, P> SharedPrimitiveBuffer<'a, P> { + pub fn new(slice: &'a mut [P], offset: &'a AtomicUsize) -> Self { Self { ptr: slice.as_mut_ptr(), offset, @@ -138,11 +139,10 @@ impl<'a> SharedPrimitiveBuffer<'a> { } } - pub fn append( - &self, - primitives: &[Arc], - indices: &[BVHPrimitiveInfo], - ) -> usize { + pub fn append(&self, primitives: &[P], indices: &[BVHPrimitiveInfo]) -> usize + where + P: Clone, + { let count = indices.len(); let start_index = self.offset.fetch_add(count, AtomicOrdering::Relaxed); @@ -156,16 +156,16 @@ impl<'a> SharedPrimitiveBuffer<'a> { } } -pub struct BVHAggregate { - max_prims_in_node: usize, - primitives: Vec>, - split_method: SplitMethod, - nodes: Vec, +pub struct BVHAggregate { + pub max_prims_in_node: usize, + pub primitives: Vec

, + pub split_method: SplitMethod, + pub nodes: Vec, } -impl BVHAggregate { +impl BVHAggregate

{ pub fn new( - mut primitives: Vec>, + mut primitives: Vec

, max_prims_in_node: usize, split_method: SplitMethod, ) -> Self { @@ -186,7 +186,7 @@ impl BVHAggregate { .map(|(i, p)| BVHPrimitiveInfo::new(i, p.bounds())) .collect(); - let ordered_prims: Vec>; + let ordered_prims: Vec

; let total_nodes_count: usize; let root: Box; @@ -310,7 +310,11 @@ impl BVHAggregate { let m1 = w[0].morton_code & TREELET_MASK; let m2 = w[1].morton_code & TREELET_MASK; // If mask changes, the split is at index i + 1 - if m1 != m2 { Some(i + 1) } else { None } + if m1 != m2 { + Some(i + 1) + } else { + None + } }) .collect(); diff --git a/src/core/camera.rs b/src/core/camera.rs index 8b70809..8985d80 100644 --- a/src/core/camera.rs +++ b/src/core/camera.rs @@ -4,8 +4,7 @@ use crate::core::image::{Image, ImageIO}; use crate::globals::get_options; use crate::utils::read_float_file; use crate::utils::{Arena, FileLoc, ParameterDictionary}; -use anyhow::{Result, anyhow}; -use shared::Ptr; +use anyhow::{anyhow, Result}; use shared::cameras::*; use shared::core::camera::{Camera, CameraBase, CameraTrait, CameraTransform}; use shared::core::color::SRGB; @@ -14,6 +13,7 @@ use shared::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f, Vector3f}; use shared::core::image::PixelFormat; use shared::core::medium::Medium; use shared::utils::math::square; +use shared::Ptr; use shared::{Float, PI}; use std::path::Path; use std::sync::Arc; @@ -25,14 +25,14 @@ pub struct CameraBaseParameters { pub shutter_open: Float, pub shutter_close: Float, pub film: Arc, - pub medium: Arc, + pub medium: Option>, } impl CameraBaseParameters { pub fn new( camera_transform: &CameraTransform, film: Arc, - medium: Arc, + medium: Option>, params: &ParameterDictionary, loc: &FileLoc, ) -> Result { @@ -63,7 +63,10 @@ pub trait CameraBaseFactory { shutter_open: p.shutter_open, shutter_close: p.shutter_close, film: Ptr::from(p.film.clone().as_ref()), - medium: Ptr::from(p.medium.clone().as_ref()), + medium: match p.medium { + Some(ref m) => Ptr::from(m.as_ref()), + None => Ptr::null(), + }, min_pos_differential_x: Vector3f::default(), min_pos_differential_y: Vector3f::default(), min_dir_differential_x: Vector3f::default(), @@ -96,7 +99,7 @@ pub trait CameraFactory { name: &str, params: &ParameterDictionary, camera_transform: &CameraTransform, - medium: Medium, + medium: Option>, film: Arc, loc: &FileLoc, arena: &Arena, @@ -110,7 +113,7 @@ impl CameraFactory for Camera { name: &str, params: &ParameterDictionary, camera_transform: &CameraTransform, - medium: Medium, + medium: Option>, film: Arc, loc: &FileLoc, arena: &Arena, @@ -122,7 +125,7 @@ impl CameraFactory for Camera { "perspective" => { let full_res = film.full_resolution(); let camera_params = - CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc)?; + CameraBaseParameters::new(camera_transform, film, medium, params, loc)?; let base = CameraBase::create(camera_params); let lens_radius = params.get_one_float("lensradius", 0.)?; let focal_distance = params.get_one_float("focaldistance", 1e6)?; @@ -169,7 +172,7 @@ impl CameraFactory for Camera { "orthographic" => { let full_res = film.full_resolution(); let camera_params = - CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc)?; + CameraBaseParameters::new(camera_transform, film, medium, params, loc)?; let base = CameraBase::create(camera_params); let lens_radius = params.get_one_float("lensradius", 0.)?; let focal_distance = params.get_one_float("focaldistance", 1e6)?; @@ -211,7 +214,7 @@ impl CameraFactory for Camera { } "realistic" => { let camera_params = - CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc)?; + CameraBaseParameters::new(camera_transform, film, medium, params, loc)?; let base = CameraBase::create(camera_params); let aperture_diameter = params.get_one_float("aperturediameter", 1.)?; let focal_distance = params.get_one_float("focaldistance", 10.)?; @@ -391,7 +394,7 @@ impl CameraFactory for Camera { "spherical" => { let full_res = film.full_resolution(); let camera_params = - CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc)?; + CameraBaseParameters::new(camera_transform, film, medium, params, loc)?; let base = CameraBase::create(camera_params); let m = params.get_one_string("mapping", "equalarea")?; let mapping = match m.as_str() { diff --git a/src/core/color.rs b/src/core/color.rs index d73a108..42ef9d4 100644 --- a/src/core/color.rs +++ b/src/core/color.rs @@ -21,13 +21,13 @@ impl Deref for RGBToSpectrumTableData { impl RGBToSpectrumTableData { pub fn new(z_nodes: Vec, coeffs: Vec) -> Self { + eprintln!("z_nodes.len() = {}, coeffs.len() = {}", z_nodes.len(), coeffs.len()); assert_eq!(z_nodes.len(), RES as usize); - assert_eq!(coeffs.len(), (RES * RES * RES) as usize * 3 * 3); // bucket*z*y*x*3(coeffs) - let coeffs_struct = Coeffs::from(&[coeffs[0], coeffs[1], coeffs[2]]); + assert_eq!(coeffs.len(), (RES * RES * RES) as usize * 3 * 3); let view = RGBToSpectrumTable { z_nodes: Ptr::from(z_nodes.as_ptr()), - coeffs: Ptr::from(&coeffs_struct), + coeffs: Ptr::from(coeffs.as_ptr() as *const Coeffs), n_nodes: z_nodes.len() as u32, }; diff --git a/src/core/material.rs b/src/core/material.rs index f0ba65d..befee49 100644 --- a/src/core/material.rs +++ b/src/core/material.rs @@ -86,3 +86,21 @@ impl MaterialFactory for Material { } } } + +pub fn default_diffuse_material(arena: &Arena) -> Material { + use shared::core::texture::GPUSpectrumTexture; + use shared::core::texture::SpectrumConstantTexture; + use shared::core::spectrum::{ConstantSpectrum, Spectrum}; + use shared::materials::DiffuseMaterial; + use shared::utils::Ptr; + + let grey = Spectrum::Constant(ConstantSpectrum { c: 0.5 }); + let tex = GPUSpectrumTexture::Constant(SpectrumConstantTexture::new(grey)); + let tex_ptr = arena.alloc(tex); + + Material::Diffuse(DiffuseMaterial { + normal_map: Ptr::null(), + displacement: Ptr::null(), + reflectance: tex_ptr, + }) +} diff --git a/src/core/scene/builder.rs b/src/core/scene/builder.rs index 2e6a6a9..b8c44b6 100644 --- a/src/core/scene/builder.rs +++ b/src/core/scene/builder.rs @@ -640,6 +640,7 @@ impl ParserTarget for BasicSceneBuilder { loc: FileLoc, arena: Arc, ) -> Result<(), ParserError> { + eprintln!("TEXTURE: name='{}' type='{}' tex='{}'", orig_name, type_name, tex_name); let name = normalize_utf8(orig_name); self.verify_world("Texture", &loc)?; let dict = ParameterDictionary::from_array( diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index fd1198f..039da42 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -3,17 +3,17 @@ use super::state::*; use crate::core::camera::CameraFactory; use crate::core::film::FilmFactory; use crate::core::filter::FilterFactory; -use crate::core::image::{Image, io::ImageIO}; +use crate::core::image::{io::ImageIO, Image}; use crate::core::material::MaterialFactory; use crate::core::primitive::{CreateGeometricPrimitive, CreateSimplePrimitive}; use crate::core::sampler::SamplerFactory; use crate::core::shape::ShapeFactory; use crate::core::texture::{FloatTexture, SpectrumTexture}; -use crate::utils::parallel::{AsyncJob, run_async}; +use crate::utils::parallel::{run_async, AsyncJob}; use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary}; -use crate::utils::{Upload, resolve_filename}; +use crate::utils::{resolve_filename, Upload}; use crate::{Arena, FileLoc}; -use anyhow::{Result, anyhow}; +use anyhow::{anyhow, Result}; use parking_lot::Mutex; use rayon::prelude::*; use shared::core::camera::{Camera, CameraTransform}; @@ -46,16 +46,28 @@ impl<'a> SceneLookup<'a> { return None; } self.media.get(name).cloned().or_else(|| { - panic!("{}: medium '{}' not defined", loc, name); + log::error!("{}: medium '{}' not defined", loc, name); + None }) } - pub fn resolve_material(&self, mat_ref: &MaterialRef, _loc: &FileLoc) -> Option { + pub fn resolve_material(&self, mat_ref: &MaterialRef, loc: &FileLoc) -> Option { match mat_ref { - MaterialRef::Name(name) => { - Some(*self.named_materials.get(name).expect("Material not found")) + MaterialRef::Name(name) => match self.named_materials.get(name) { + Some(m) => Some(*m), + None => { + log::error!("{}: named material '{}' not found", loc, name); + None + } + }, + MaterialRef::Index(idx) => { + if *idx < self.materials.len() { + Some(self.materials[*idx]) + } else { + log::error!("{}: material index {} out of bounds", loc, idx); + None + } } - MaterialRef::Index(idx) => Some(self.materials[*idx]), MaterialRef::None => None, } } @@ -167,7 +179,7 @@ impl BasicScene { &camera.base.name, &camera.base.parameters, &camera.camera_transform, - *medium.unwrap(), + medium, camera_film, &camera.base.loc, &arena_camera, @@ -235,7 +247,7 @@ impl BasicScene { fn validate_texture_file(&self, filename: &str, loc: &FileLoc, n_missing: &mut usize) -> bool { if filename.is_empty() { - log::error!( + eprintln!( "[{:?}] \"string filename\" not provided for image texture.", loc ); @@ -243,7 +255,7 @@ impl BasicScene { return false; } if !std::path::Path::new(filename).exists() { - log::error!("[{:?}] {}: file not found.", loc, filename); + eprintln!("[{:?}] {}: file not found.", loc, filename); *n_missing += 1; return false; } @@ -538,7 +550,7 @@ impl BasicScene { } /// Create area lights for shapes that reference one. Produces a map from - /// shape index to a vec of lights (one per sub-shape, e.g. per triangle). + /// shape index to a vec of lights. /// Must be called after shapes are loaded but before upload_shapes. pub fn create_area_lights( &self, @@ -569,6 +581,9 @@ impl BasicScene { &textures.float_textures, ); + 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 @@ -588,9 +603,7 @@ impl BasicScene { &al_entity.parameters, &al_entity.loc, shape, - alpha_tex - .as_ref() - .expect("Alpha texture required for area light"), + alpha_ref, colorspace_ref.map(|cs| cs.as_ref()), arena, ) { @@ -647,7 +660,7 @@ impl BasicScene { primitives } - fn load_shapes_parallel( + pub fn load_shapes_parallel( &self, entities: &[ShapeSceneEntity], lookup: &SceneLookup, @@ -655,7 +668,7 @@ impl BasicScene { ) -> Vec> { entities .par_iter() - .filter_map(|sh| { + .map(|sh| { Shape::create( &sh.base.name, *sh.render_from_object.as_ref(), @@ -666,7 +679,10 @@ impl BasicScene { sh.base.loc.clone(), arena, ) - .ok() + .unwrap_or_else(|e| { + eprintln!("Shape '{}' failed: {}", sh.base.name, e); + Vec::new() + }) }) .collect() } @@ -717,18 +733,18 @@ impl BasicScene { let mtl = lookup .resolve_material(&entity.material, &entity.base.loc) - .unwrap(); + .unwrap_or_else(|| crate::core::material::default_diffuse_material(arena)); - let mi = MediumInterface::new( - lookup + let mi = MediumInterface { + inside: lookup .find_medium(&entity.inside_medium, &entity.base.loc) - .unwrap() - .as_ref(), - lookup + .map(|m| Ptr::from(m.as_ref())) + .unwrap_or(Ptr::null()), + outside: lookup .find_medium(&entity.outside_medium, &entity.base.loc) - .unwrap() - .as_ref(), - ); + .map(|m| Ptr::from(m.as_ref())) + .unwrap_or(Ptr::null()), + }; let shape_lights_opt = lookup.shape_lights.get(&i); @@ -872,20 +888,13 @@ impl BasicScene { textures: &HashMap>, ) -> Option> { let name = params.get_texture("alpha"); + if name.is_empty() { + return None; + } 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> { diff --git a/src/core/shape.rs b/src/core/shape.rs index 49cecf8..56e470b 100644 --- a/src/core/shape.rs +++ b/src/core/shape.rs @@ -1,9 +1,10 @@ use crate::core::texture::FloatTexture; use crate::shapes::{BilinearPatchMesh, TriangleMesh}; -use crate::utils::{Arena, FileLoc, ParameterDictionary}; -use anyhow::{Result, anyhow}; +use crate::utils::{Arena, FileLoc, ParameterDictionary, resolve_filename}; +use anyhow::{anyhow, bail, Result}; use parking_lot::Mutex; use shared::core::shape::*; +use shared::Ptr; use shared::shapes::*; use shared::utils::Transform; use std::collections::HashMap; @@ -95,20 +96,31 @@ impl ShapeFactory for Shape { arena, ), "plymesh" => { - // let filename = resolve_filename(¶meters.get_one_string("filename", "")); - // let ply_mesh = TriQuadMesh::read_ply(filename); - // let mut edge_length = parameters.get_one_float("edgelength", 1.); - // edge_length *= get_options().displacement_edge_scale; - // let displacement_tex_name = parameters.get_texture("displacement"); - TriangleShape::create( - render_from_object, - object_from_render, - reverse_orientation, - parameters, - float_textures, - loc, - arena, - ) + let filename = resolve_filename(¶meters.get_one_string("filename", "")?); + if filename.is_empty() { + bail!("{}: plymesh requires \"filename\" parameter", loc); + } + + let tri_mesh = + TriangleMesh::from_ply(&filename, &render_from_object, reverse_orientation)?; + + let host_arc = Arc::new(tri_mesh); + let mut global_store = ALL_TRIANGLE_MESHES.lock(); + global_store.push(host_arc.clone()); + drop(global_store); + + let n_tris = host_arc.device.n_triangles; + let mesh_ptr = Ptr::from(&host_arc.device); + let shapes: Vec = (0..n_tris) + .map(|i| { + Shape::Triangle(TriangleShape { + mesh: mesh_ptr, + tri_index: i as i32, + }) + }) + .collect(); + + Ok(shapes) } _ => Err(anyhow!("Unknown shape name")), } diff --git a/src/films/rgb.rs b/src/films/rgb.rs index 2a568e1..e63f49b 100644 --- a/src/films/rgb.rs +++ b/src/films/rgb.rs @@ -70,10 +70,7 @@ impl CreateFilm for RGBFilm { loc: &FileLoc, _arena: &Arena, ) -> Result { - let colorspace = params.color_space.as_ref().cloned().unwrap_or_else(|| { - let stdcs = crate::spectra::get_colorspace_device(); - Arc::new(*stdcs.srgb) - }); + let colorspace = params.color_space.as_ref().cloned().unwrap_or_else(crate::spectra::default_colorspace_arc); let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?; let write_fp16 = params.get_one_bool("savefp16", true)?; let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; diff --git a/src/globals.rs b/src/globals.rs index 78c94d8..9c44d76 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -1,4 +1,5 @@ use crate::core::color::RGBToSpectrumTableData; +use shared::core::color::RES; use bytemuck::cast_slice; use once_cell::sync::Lazy; use shared::Float; @@ -20,72 +21,53 @@ pub fn get_options() -> &'static PBRTOptions { }) } +fn aligned_cast(bytes: &[u8]) -> &[Float] { + match bytemuck::try_cast_slice(bytes) { + Ok(s) => s, + Err(_) => { + let v: Vec = bytemuck::pod_collect_to_vec(bytes); + Box::leak(v.into_boxed_slice()) + } + } +} + + static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../data/srgb_scale.dat"); static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../data/srgb_coeffs.dat"); - -pub static SRGB_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(SRGB_SCALE_BYTES)); - -pub static SRGB_COEFFS: Lazy<&[Float]> = - Lazy::new(|| match bytemuck::try_cast_slice(SRGB_COEFFS_BYTES) { - Ok(s) => s, - Err(_) => { - let v: Vec = bytemuck::pod_collect_to_vec(SRGB_COEFFS_BYTES); - Box::leak(v.into_boxed_slice()) - } - }); - static DCI_P3_SCALE_BYTES: &[u8] = include_bytes!("../data/dcip3_scale.dat"); static DCI_P3_COEFFS_BYTES: &[u8] = include_bytes!("../data/dcip3_coeffs.dat"); -pub static DCI_P3_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(DCI_P3_SCALE_BYTES)); -pub static DCI_P3_COEFFS: Lazy<&[Float]> = - Lazy::new(|| match bytemuck::try_cast_slice(DCI_P3_COEFFS_BYTES) { - Ok(s) => s, - Err(_) => { - let v: Vec = bytemuck::pod_collect_to_vec(DCI_P3_COEFFS_BYTES); - Box::leak(v.into_boxed_slice()) - } - }); - static ACES_SCALE_BYTES: &[u8] = include_bytes!("../data/aces_scale.dat"); static ACES_COEFFS_BYTES: &[u8] = include_bytes!("../data/aces_coeffs.dat"); - -pub static ACES_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(ACES_SCALE_BYTES)); - -pub static ACES_COEFFS: Lazy<&[Float]> = - Lazy::new(|| match bytemuck::try_cast_slice(ACES_COEFFS_BYTES) { - Ok(s) => s, - Err(_) => { - let v: Vec = bytemuck::pod_collect_to_vec(ACES_COEFFS_BYTES); - Box::leak(v.into_boxed_slice()) - } - }); - static REC2020_SCALE_BYTES: &[u8] = include_bytes!("../data/rec2020_scale.dat"); static REC2020_COEFFS_BYTES: &[u8] = include_bytes!("../data/rec2020_coeffs.dat"); -pub static REC2020_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(REC2020_SCALE_BYTES)); -pub static REC2020_COEFFS: Lazy<&[Float]> = - Lazy::new(|| match bytemuck::try_cast_slice(REC2020_COEFFS_BYTES) { - Ok(s) => s, - Err(_) => { - let v: Vec = bytemuck::pod_collect_to_vec(REC2020_COEFFS_BYTES); - Box::leak(v.into_boxed_slice()) - } - }); + +fn strip_to_len(bytes: &[u8], expected_len: usize) -> &'static [Float] { + let all: Vec = bytemuck::pod_collect_to_vec(bytes); + let skip = all.len() - expected_len; + let data = all[skip..].to_vec(); + Box::leak(data.into_boxed_slice()) +} + +const COEFFS_LEN: usize = (RES * RES * RES) as usize * 3 * 3; + +pub static SRGB_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(SRGB_SCALE_BYTES, RES as usize)); +pub static SRGB_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(SRGB_COEFFS_BYTES, COEFFS_LEN)); + +pub static DCI_P3_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(DCI_P3_SCALE_BYTES, RES as usize)); +pub static DCI_P3_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(DCI_P3_COEFFS_BYTES, COEFFS_LEN)); + +pub static ACES_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(ACES_SCALE_BYTES, RES as usize)); +pub static ACES_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(ACES_COEFFS_BYTES, COEFFS_LEN)); + +pub static REC2020_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(REC2020_SCALE_BYTES, RES as usize)); +pub static REC2020_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(REC2020_COEFFS_BYTES, COEFFS_LEN)); pub static SRGB_TABLE: Lazy = Lazy::new(|| RGBToSpectrumTableData::new(SRGB_SCALE.to_vec(), SRGB_COEFFS.to_vec())); - pub static DCI_P3_TABLE: Lazy = Lazy::new(|| RGBToSpectrumTableData::new(DCI_P3_SCALE.to_vec(), DCI_P3_COEFFS.to_vec())); - pub static REC2020_TABLE: Lazy = Lazy::new(|| RGBToSpectrumTableData::new(REC2020_SCALE.to_vec(), REC2020_COEFFS.to_vec())); - pub static ACES_TABLE: Lazy = Lazy::new(|| RGBToSpectrumTableData::new(ACES_SCALE.to_vec(), ACES_COEFFS.to_vec())); - -// pub static ACES_TABLE: Lazy = Lazy::new(|| { -// RGBToSpectrumTableData::load(Path::new("data/"), "aces2065_1") -// .expect("Failed to load ACES table") -// }); diff --git a/src/lights/diffuse.rs b/src/lights/diffuse.rs index 076d479..220b38b 100644 --- a/src/lights/diffuse.rs +++ b/src/lights/diffuse.rs @@ -29,7 +29,9 @@ pub fn create( arena: &Arena, ) -> Result { let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant); - let illum_spec = Spectrum::Dense(colorspace.unwrap().illuminant); + let default_cs = crate::spectra::default_colorspace(); + let cs = colorspace.unwrap_or(&default_cs); + let illum_spec = Spectrum::Dense(cs.illuminant); let mut scale = params.get_one_float("scale", 1.)?; let two_sided = params.get_one_bool("twosided", false)?; diff --git a/src/lights/distant.rs b/src/lights/distant.rs index 25bf957..9adddbb 100644 --- a/src/lights/distant.rs +++ b/src/lights/distant.rs @@ -46,10 +46,12 @@ pub fn create( colorspace: Option<&RGBColorSpace>, _arena: &Arena, ) -> Result { + let default_cs = crate::spectra::default_colorspace(); + let cs = colorspace.unwrap_or(&default_cs); let l = parameters .get_one_spectrum( "L", - Some(Spectrum::Dense(colorspace.unwrap().illuminant)), + Some(Spectrum::Dense(cs.illuminant)), SpectrumType::Illuminant, ) .unwrap(); diff --git a/src/lights/goniometric.rs b/src/lights/goniometric.rs index 1da2b84..a0dd2a8 100644 --- a/src/lights/goniometric.rs +++ b/src/lights/goniometric.rs @@ -28,10 +28,13 @@ pub fn create( colorspace: Option<&RGBColorSpace>, arena: &Arena, ) -> Result { + + let default_cs = crate::spectra::default_colorspace(); + let cs = colorspace.unwrap_or(&default_cs); let i = params .get_one_spectrum( "I", - Some(Spectrum::Dense(colorspace.unwrap().illuminant)), + Some(Spectrum::Dense(cs.illuminant)), SpectrumType::Illuminant, ) .expect("Could not retrieve spectrum"); diff --git a/src/lights/infinite.rs b/src/lights/infinite.rs index cc62b55..2b6d057 100644 --- a/src/lights/infinite.rs +++ b/src/lights/infinite.rs @@ -1,14 +1,14 @@ -use crate::Arena; use crate::core::image::{Image, ImageIO}; use crate::core::light::lookup_spectrum; use crate::core::spectrum::spectrum_to_photometric; use crate::spectra::get_spectra_context; use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D}; -use crate::utils::{FileLoc, ParameterDictionary, Upload, resolve_filename}; -use anyhow::{Result, anyhow}; +use crate::utils::{resolve_filename, FileLoc, ParameterDictionary, Upload}; +use crate::Arena; +use anyhow::{anyhow, Result}; use rayon::prelude::*; use shared::core::camera::CameraTransform; -use shared::core::geometry::{Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike, cos_theta}; +use shared::core::geometry::{cos_theta, Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike}; use shared::core::image::{DeviceImage, WrapMode}; use shared::core::light::{Light, LightBase, LightType}; use shared::core::medium::{Medium, MediumInterface}; @@ -131,6 +131,8 @@ pub fn create( loc: &FileLoc, arena: &Arena, ) -> Result { + let default_cs = crate::spectra::default_colorspace(); + let cs = colorspace.unwrap_or(&default_cs); 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")?; @@ -154,7 +156,7 @@ pub fn create( scale /= spectrum_to_photometric(l[0]); l[0] } else { - Spectrum::Dense(colorspace.unwrap().illuminant) + Spectrum::Dense(cs.illuminant) }; if e_v > 0.0 { @@ -167,7 +169,7 @@ pub fn create( } // Image-based lights - let (image, image_cs) = load_image(&filename, &l, colorspace.unwrap(), loc)?; + let (image, image_cs) = load_image(&filename, &l, cs, loc)?; scale /= spectrum_to_photometric(Spectrum::Dense(image_cs.illuminant)); diff --git a/src/lights/point.rs b/src/lights/point.rs index 9fc642e..8c8db4e 100644 --- a/src/lights/point.rs +++ b/src/lights/point.rs @@ -52,10 +52,13 @@ pub fn create( colorspace: Option<&RGBColorSpace>, arena: &Arena, ) -> Result { + let default_cs = crate::spectra::default_colorspace(); + let cs = colorspace.unwrap_or(&default_cs); + let l = parameters .get_one_spectrum( "L", - Some(Spectrum::Dense(colorspace.unwrap().illuminant)), + Some(Spectrum::Dense(cs.illuminant)), SpectrumType::Illuminant, ) .unwrap(); diff --git a/src/lights/spot.rs b/src/lights/spot.rs index f9ad29d..95fae0d 100644 --- a/src/lights/spot.rs +++ b/src/lights/spot.rs @@ -63,10 +63,12 @@ pub fn create( colorspace: Option<&RGBColorSpace>, arena: &Arena, ) -> Result { + let default_cs = crate::spectra::default_colorspace(); + let cs = colorspace.unwrap_or(&default_cs); let i = parameters .get_one_spectrum( "I", - Some(Spectrum::Dense(colorspace.unwrap().illuminant)), + Some(Spectrum::Dense(cs.illuminant)), SpectrumType::Illuminant, ) .expect("No spectrum"); diff --git a/src/shapes/mesh.rs b/src/shapes/mesh.rs index 4056c5c..9b761c5 100644 --- a/src/shapes/mesh.rs +++ b/src/shapes/mesh.rs @@ -1,6 +1,6 @@ // use crate::Arena; use crate::utils::sampling::PiecewiseConstant2D; -use anyhow::{Context, Result as AnyResult, bail}; +use anyhow::{bail, Context, Result as AnyResult}; use ply_rs::parser::Parser; use ply_rs::ply::{DefaultElement, Property}; use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f, VectorLike}; @@ -299,9 +299,15 @@ impl TriQuadMesh { .with_context(|| format!("Couldn't open PLY file \"{}\"", filename_display))?; let p = Parser::::new(); - let ply = p - .read_ply(&mut f) - .with_context(|| format!("Unable to read/parse PLY file \"{}\"", filename_display))?; + let ply = if path.extension().and_then(|e| e.to_str()) == Some("gz") { + let decoder = flate2::read::GzDecoder::new(f); + let mut buf = std::io::BufReader::new(decoder); + p.read_ply(&mut buf) + } else { + let mut buf = std::io::BufReader::new(f); + p.read_ply(&mut buf) + } + .with_context(|| format!("Unable to read/parse PLY file \"{}\"", filename_display))?; let mut mesh = TriQuadMesh::default(); diff --git a/src/spectra/data.rs b/src/spectra/data.rs index 83f44c4..6c54f63 100644 --- a/src/spectra/data.rs +++ b/src/spectra/data.rs @@ -6,7 +6,17 @@ use std::collections::HashMap; use std::sync::LazyLock; pub fn create_cie_buffer(data: &[Float]) -> DenselySampledSpectrumBuffer { - let buffer = PiecewiseLinearSpectrumBuffer::from_interleaved(data, false); + let (start_lambda, step) = match data.len() { + 471 => (360.0, 1.0), + 95 => (300.0, 5.0), + n => panic!("Unexpected CIE data length: {}", n), + }; + + let lambdas: Vec = (0..data.len()) + .map(|i| start_lambda + i as Float * step) + .collect(); + + let buffer = PiecewiseLinearSpectrumBuffer::new(lambdas, data.to_vec()); let spec = Spectrum::Piecewise(buffer.device); DenselySampledSpectrumBuffer::from_spectrum(&spec) } diff --git a/src/spectra/mod.rs b/src/spectra/mod.rs index 94f7075..ddb7f71 100644 --- a/src/spectra/mod.rs +++ b/src/spectra/mod.rs @@ -4,6 +4,7 @@ use anyhow::{Result, anyhow}; use shared::core::geometry::Point2f; use shared::core::spectrum::Spectrum; use shared::core::spectrum::StandardSpectra; +use shared::spectra::RGBColorSpace; use shared::spectra::DeviceStandardColorSpaces; use shared::spectra::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z}; use shared::utils::Ptr; @@ -60,7 +61,7 @@ pub static SRGB: LazyLock> = LazyLock::new(|| { 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.clone()); + let table_ptr = Ptr::from(&SRGB_TABLE.view); Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr)) }); @@ -71,7 +72,7 @@ pub static DCI_P3: LazyLock> = LazyLock::new(|| { let g = Point2f::new(0.265, 0.690); let b = Point2f::new(0.150, 0.060); - let table_ptr = Ptr::from(&DCI_P3_TABLE.clone()); + let table_ptr = Ptr::from(&DCI_P3_TABLE.view); Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr)) }); @@ -82,7 +83,7 @@ pub static REC2020: LazyLock> = LazyLock::new(|| { let g = Point2f::new(0.170, 0.797); let b = Point2f::new(0.131, 0.046); - let table_ptr = Ptr::from(&REC2020_TABLE.clone()); + let table_ptr = Ptr::from(&REC2020_TABLE.view); Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr)) }); @@ -92,7 +93,7 @@ pub static ACES: LazyLock> = LazyLock::new(|| { let g = Point2f::new(0.0000, 1.0000); let b = Point2f::new(0.0001, -0.0770); - let table_ptr = Ptr::from(&ACES_TABLE.clone()); + let table_ptr = Ptr::from(&ACES_TABLE.view); Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr)) }); @@ -133,3 +134,16 @@ pub fn get_colorspace_device() -> DeviceStandardColorSpaces { aces2065_1: Ptr::from(&ACES.view), } } + +pub fn default_colorspace() -> RGBColorSpace { + let stdcs = get_colorspace_device(); + *stdcs.srgb +} + +pub fn default_colorspace_arc() -> Arc { + Arc::new(default_colorspace()) +} + +pub fn default_illuminant() -> Spectrum { + Spectrum::Dense(default_colorspace().illuminant) +} diff --git a/src/textures/image.rs b/src/textures/image.rs index 7134c48..a817d04 100644 --- a/src/textures/image.rs +++ b/src/textures/image.rs @@ -1,15 +1,14 @@ -use crate::Arena; +use crate::core::texture::{get_texture_cache, CreateTextureMapping, TexInfo}; use crate::core::texture::{ CreateFloatTexture, CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTexture, SpectrumTextureTrait, }; -use crate::core::texture::{TexInfo, get_texture_cache}; use crate::utils::mipmap::{MIPMap, MIPMapFilterOptions}; use crate::utils::{FileLoc, TextureParameterDictionary}; +use crate::Arena; use anyhow::Result; -use shared::Float; -use shared::core::color::ColorEncoding; use shared::core::color::RGB; +use shared::core::color::{ColorEncoding, SRGBEncoding}; use shared::core::geometry::Vector2f; use shared::core::image::WrapMode; use shared::core::spectrum::SpectrumTrait; @@ -19,6 +18,7 @@ use shared::spectra::{ SampledWavelengths, }; use shared::utils::Transform; +use shared::Float; use std::path::Path; use std::sync::Arc; // use crate::utils::{FileLoc, TextureParameterDictionary}; @@ -156,12 +156,40 @@ impl SpectrumTextureTrait for SpectrumImageTexture { impl CreateSpectrumTexture for SpectrumImageTexture { fn create( - _render_from_texture: Transform, - _parameters: TextureParameterDictionary, - _spectrum_type: SpectrumType, - _loc: FileLoc, + render_from_texture: Transform, + parameters: TextureParameterDictionary, + spectrum_type: SpectrumType, + loc: FileLoc, ) -> Result { - todo!() + let mapping = TextureMapping2D::create(¶meters, &render_from_texture, &loc)?; + + let filename = crate::utils::resolve_filename(¶meters.get_one_string("filename", "")?); + let scale = parameters.get_one_float("scale", 1.0)?; + let invert = parameters.get_one_bool("invert", false)?; + + let filter_options = MIPMapFilterOptions::default(); + let wrap_str = parameters.get_one_string("wrap", "repeat")?; + let wrap_mode = match wrap_str.as_str() { + "repeat" => WrapMode::Repeat, + "clamp" => WrapMode::Clamp, + "black" => WrapMode::Black, + _ => WrapMode::Repeat, + }; + + let encoding = ColorEncoding::SRGB(SRGBEncoding); + + let tex = SpectrumImageTexture::new( + mapping, + filename, + filter_options, + wrap_mode, + scale, + invert, + encoding, + spectrum_type, + ); + + Ok(SpectrumTexture::Image(tex)) } } diff --git a/src/utils/arena.rs b/src/utils/arena.rs index a65be97..d996ac0 100644 --- a/src/utils/arena.rs +++ b/src/utils/arena.rs @@ -15,11 +15,11 @@ use shared::core::spectrum::Spectrum; use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture}; use shared::spectra::{DenselySampledSpectrum, DeviceStandardColorSpaces, RGBColorSpace}; use shared::textures::*; -use shared::utils::Ptr; use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh}; use shared::utils::sampling::{ DevicePiecewiseConstant1D, DevicePiecewiseConstant2D, DeviceWindowedPiecewiseConstant2D, }; +use shared::utils::Ptr; use std::alloc::Layout; use std::collections::HashMap; use std::slice::from_raw_parts; @@ -174,7 +174,7 @@ impl Upload for SpectrumTexture { 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(), + color_space: tex.base.mipmap.color_space.clone().unwrap_or_else(crate::spectra::default_colorspace), spectrum_type: tex.spectrum_type, }; GPUSpectrumTexture::Image(gpu_img) @@ -288,17 +288,17 @@ impl Upload for RGBToSpectrumTable { fn upload(&self, arena: &Arena) -> Ptr { 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 n_coeffs = 3 * (n_nodes as usize).pow(3); + let coeffs_slice = unsafe { from_raw_parts(self.coeffs.as_raw(), n_coeffs) }; let (c_ptr, _) = arena.alloc_slice(coeffs_slice); - let shared_table = RGBToSpectrumTable { + arena.alloc(RGBToSpectrumTable { z_nodes: z_ptr, coeffs: c_ptr, n_nodes: self.n_nodes, - }; - - arena.alloc(shared_table) + }) } } diff --git a/src/utils/file.rs b/src/utils/file.rs index 70a91d5..4d51dd5 100644 --- a/src/utils/file.rs +++ b/src/utils/file.rs @@ -6,7 +6,7 @@ use std::sync::OnceLock; static SEARCH_DIRECTORY: OnceLock = OnceLock::new(); -fn set_search_directory(filename: &str) { +pub fn set_search_directory(filename: &str) { let path = Path::new(filename); let dir = if path.is_dir() { path.to_path_buf() diff --git a/src/utils/parameters.rs b/src/utils/parameters.rs index f693ce8..f81ddaa 100644 --- a/src/utils/parameters.rs +++ b/src/utils/parameters.rs @@ -513,7 +513,7 @@ impl ParameterDictionary { pub fn get_texture(&self, name: &str) -> String { for p in &self.params { - if p.name == name || p.type_name != "texture" { + if p.name == name && p.type_name == "texture" { if p.strings.len() != 1 { panic!( "[{:?}] Expected 1 texture name for {}, found {}", @@ -526,7 +526,7 @@ impl ParameterDictionary { return p.strings[0].clone(); } } - return "".to_string(); + String::new() } pub fn report_unused(&self) { diff --git a/src/utils/parser.rs b/src/utils/parser.rs index a5a47e2..0bbc9ef 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -788,6 +788,8 @@ impl<'a> SceneParser<'a> { .unwrap_or(Path::new(".")) .to_path_buf(); + crate::utils::file::set_search_directory(current_dir.to_str().unwrap_or(".")); + Self { target, file_stack: vec![root],