From 640e17110aa9f056b99fbddd5238f74955b1a86b Mon Sep 17 00:00:00 2001 From: pingu Date: Sun, 25 Jan 2026 18:15:48 +0000 Subject: [PATCH] Continuing cleanup of scene creation, texture ownership, and memory management --- shared/Cargo.toml | 1 + shared/src/core/film.rs | 4 +- shared/src/core/medium.rs | 6 +- shared/src/lights/goniometric.rs | 2 +- shared/src/spectra/colorspace.rs | 13 +- src/core/film.rs | 2 +- src/core/image/io.rs | 12 +- src/core/image/metadata.rs | 2 +- src/core/image/mod.rs | 113 ++++++------ src/core/image/ops.rs | 97 +++++------ src/core/image/pixel.rs | 8 +- src/core/light.rs | 28 +-- src/core/material.rs | 4 +- src/core/medium.rs | 30 ++-- src/core/mod.rs | 1 - src/core/primitive.rs | 1 + src/core/sampler.rs | 40 +---- src/core/scene/builder.rs | 17 +- src/core/scene/scene.rs | 288 +++++++++++++++---------------- src/core/scene/state.rs | 11 +- src/core/texture.rs | 18 +- src/integrators/base.rs | 2 +- src/integrators/path.rs | 15 +- src/integrators/pipeline.rs | 6 +- src/lib.rs | 3 + src/lights/diffuse.rs | 2 +- src/lights/distant.rs | 2 +- src/lights/goniometric.rs | 2 +- src/lights/infinite.rs | 10 +- src/lights/point.rs | 2 +- src/lights/projection.rs | 59 +++---- src/materials/measured.rs | 0 src/samplers/halton.rs | 9 +- src/samplers/independent.rs | 10 +- src/samplers/mod.rs | 14 ++ src/samplers/sobol.rs | 21 +-- src/samplers/stratified.rs | 10 +- src/spectra/dense.rs | 4 +- src/spectra/mod.rs | 13 ++ src/textures/constant.rs | 4 +- src/utils/arena.rs | 16 ++ src/utils/math.rs | 14 +- src/utils/parallel.rs | 1 + src/utils/parameters.rs | 14 +- 44 files changed, 464 insertions(+), 467 deletions(-) create mode 100644 src/materials/measured.rs diff --git a/shared/Cargo.toml b/shared/Cargo.toml index dc25d87..4066c28 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -17,6 +17,7 @@ smallvec = "1.15.1" cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } half = "2.7.1" rand = "0.9.2" +anyhow = "1.0.100" [features] use_f64 = [] diff --git a/shared/src/core/film.rs b/shared/src/core/film.rs index b4dd6da..de554fe 100644 --- a/shared/src/core/film.rs +++ b/shared/src/core/film.rs @@ -96,7 +96,7 @@ impl RGBFilm { ); } } - unsafe { &*self.base.sensor } + &*self.base.sensor } pub fn add_sample( @@ -241,7 +241,7 @@ impl GBufferFilm { ); } } - unsafe { &*self.base.sensor } + &*self.base.sensor } pub fn add_sample( diff --git a/shared/src/core/medium.rs b/shared/src/core/medium.rs index ee704ff..ae9a844 100644 --- a/shared/src/core/medium.rs +++ b/shared/src/core/medium.rs @@ -501,7 +501,7 @@ pub struct GridMedium { pub sigma_s_spec: DenselySampledSpectrum, pub density_grid: SampledGrid, pub phase: HGPhaseFunction, - pub temperature_grid: SampledGrid, + pub temperature_grid: Option>, pub le_spec: DenselySampledSpectrum, pub le_scale: SampledGrid, pub is_emissive: bool, @@ -528,8 +528,8 @@ impl MediumTrait for GridMedium { }; let le = if scale > 0.0 { - let raw_emission = if self.temperature_grid.is_valid() { - let temp = self.temperature_grid.lookup(p); + let raw_emission = if let Some(temp_grid) = &self.temperature_grid { + let temp = temp_grid.lookup(p); BlackbodySpectrum::new(temp).sample(lambda) } else { self.le_spec.sample(lambda) diff --git a/shared/src/lights/goniometric.rs b/shared/src/lights/goniometric.rs index 75054cf..30b446d 100644 --- a/shared/src/lights/goniometric.rs +++ b/shared/src/lights/goniometric.rs @@ -14,7 +14,7 @@ use crate::{Float, PI}; #[derive(Debug, Clone, Copy)] pub struct GoniometricLight { pub base: LightBase, - pub iemit: DenselySampledSpectrum, + pub iemit: Ptr, pub scale: Float, pub image: Ptr, pub distrib: Ptr, diff --git a/shared/src/spectra/colorspace.rs b/shared/src/spectra/colorspace.rs index 3ddb544..7b28c01 100644 --- a/shared/src/spectra/colorspace.rs +++ b/shared/src/spectra/colorspace.rs @@ -4,6 +4,7 @@ use crate::core::pbrt::Float; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum}; use crate::utils::math::SquareMatrix3f; use crate::utils::ptr::Ptr; +use anyhow::{Result, anyhow}; use std::cmp::{Eq, PartialEq}; @@ -18,13 +19,13 @@ pub struct DeviceStandardColorSpaces { impl DeviceStandardColorSpaces { #[cfg(not(target_arch = "nvptx64"))] - pub fn get_named(&self, name: &str) -> Option> { + pub fn get_named(&self, name: &str) -> Result> { match name.to_lowercase().as_str() { - "srgb" => Some(self.srgb), - "dci-p3" => Some(self.dci_p3), - "rec2020" => Some(self.rec2020), - "aces2065-1" => Some(self.aces2065_1), - _ => None, + "srgb" => Ok(self.srgb), + "dci-p3" => Ok(self.dci_p3), + "rec2020" => Ok(self.rec2020), + "aces2065-1" => Ok(self.aces2065_1), + _ => Err(anyhow!("No such spectrum")), } } diff --git a/src/core/film.rs b/src/core/film.rs index 8103116..ac2118b 100644 --- a/src/core/film.rs +++ b/src/core/film.rs @@ -392,7 +392,6 @@ impl FilmBaseHost for FilmBase { } } -#[enum_dispatch] pub trait FilmTrait: Sync { fn base(&self) -> &FilmBase; fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option) -> RGB; @@ -403,6 +402,7 @@ pub trait FilmTrait: Sync { .write(self.get_filename(), metadata) .expect("Something") } + fn get_image(&self, _metadata: &ImageMetadata, splat_scale: Float) -> Image { let write_fp16 = true; let format = if write_fp16 { diff --git a/src/core/image/io.rs b/src/core/image/io.rs index 2673563..413615b 100644 --- a/src/core/image/io.rs +++ b/src/core/image/io.rs @@ -8,7 +8,7 @@ use image_rs::{DynamicImage, ImageReader}; use shared::Float; use shared::core::color::{ColorEncoding, LINEAR}; use shared::core::geometry::Point2i; -use shared::core::image::PixelFormat; +use shared::core::image::{DeviceImage, ImageBase, PixelFormat}; use std::fs::File; use std::io::{BufRead, BufReader, BufWriter, Read, Write}; use std::path::Path; @@ -343,14 +343,8 @@ fn read_pfm(path: &Path) -> Result { vec!["R".into(), "G".into(), "B".into()] }; - let image = Image { - format: PixelFormat::F32, - resolution: Point2i::new(w, h), - channel_names: names, - encoding: LINEAR, - pixels: PixelStorage::F32(pixels), - }; - + let image = Image::new(PixelFormat::F32, Point2i::new(w, h), names, LINEAR); let metadata = ImageMetadata::default(); + Ok(ImageAndMetadata { image, metadata }) } diff --git a/src/core/image/metadata.rs b/src/core/image/metadata.rs index 5d94c93..8fad636 100644 --- a/src/core/image/metadata.rs +++ b/src/core/image/metadata.rs @@ -35,7 +35,7 @@ impl ImageChannelDesc { } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct ImageMetadata { pub render_time_seconds: Option, pub camera_from_world: Option>, diff --git a/src/core/image/mod.rs b/src/core/image/mod.rs index 871bfa4..b0fc15f 100644 --- a/src/core/image/mod.rs +++ b/src/core/image/mod.rs @@ -1,9 +1,9 @@ use crate::utils::containers::Array2D; -use anyhow::Result; +use anyhow::{Result, anyhow}; use half::f16; +use rayon::prelude::ParallelIterator; use shared::Float; -use shared::core::color::ColorEncoding; -use shared::core::color::LINEAR; +use shared::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR}; use shared::core::geometry::{Bounds2f, Point2f, Point2i}; use shared::core::image::{DeviceImage, ImageBase, PixelFormat, Pixels, WrapMode, WrapMode2D}; use shared::utils::math::square; @@ -77,9 +77,9 @@ pub enum PixelStorage { impl PixelStorage { pub fn as_pixels(&self) -> Pixels { match self { - PixelStorage::U8(data) => Pixels::U8(data.as_ptr()), - PixelStorage::F16(data) => Pixels::F16(data.as_ptr() as *const u16), - PixelStorage::F32(data) => Pixels::F32(data.as_ptr()), + PixelStorage::U8(data) => Pixels::U8(data.as_ptr().into()), + PixelStorage::F16(data) => Pixels::F16((data.as_ptr() as *const u16).into()), + PixelStorage::F32(data) => Pixels::F32(data.as_ptr().into()), } } @@ -126,7 +126,7 @@ impl Image { fn from_storage( storage: PixelStorage, resolution: Point2i, - channel_names: &[&str], + channel_names: &[impl AsRef], encoding: ColorEncoding, ) -> Self { let n_channels = channel_names.len() as i32; @@ -145,7 +145,7 @@ impl Image { Self { storage, - channel_names, + channel_names: String::from(channel_names), device, } } @@ -153,7 +153,7 @@ impl Image { pub fn from_u8( data: Vec, resolution: Point2i, - channel_names: &[&str], + channel_names: &[impl AsRef], encoding: ColorEncoding, ) -> Self { Self::from_storage( @@ -167,7 +167,7 @@ impl Image { pub fn from_u8( data: Vec, resolution: Point2i, - channel_names: &[&str], + channel_names: &[impl AsRef], encoding: ColorEncoding, ) -> Self { Self::from_storage( @@ -178,44 +178,56 @@ impl Image { ) } - pub fn from_f16(data: Vec, resolution: Point2i, channel_names: &[&str]) -> Self { + pub fn from_f16( + data: Vec, + resolution: Point2i, + channel_names: &[impl AsRef], + ) -> Self { Self::from_storage( PixelStorage::F16(data.into_boxed_slice()), resolution, channel_names, - ColorEncoding::Linear, + LINEAR, ) } - pub fn from_f32(data: Vec, resolution: Point2i, channel_names: &[&str]) -> Self { + pub fn from_f32( + data: Vec, + resolution: Point2i, + channel_names: &[impl AsRef], + ) -> Self { Self::from_storage( PixelStorage::F32(data.into_boxed_slice()), resolution, channel_names, - ColorEncoding::Linear, + LINEAR, ) } pub fn new( format: PixelFormat, resolution: Point2i, - channel_names: &[&str], + channel_names: &[impl AsRef], encoding: Arc, ) -> Self { let n_channels = channel_names.len(); - let pixel_count = (resolution.x * resolution.y) as usize * n_channels; + let pixel_count = (resolution.x() * resolution.y()) as usize * n_channels; let owned_names: Vec = channel_names.iter().map(|s| s.to_string()).collect(); let storage = match format { - PixelFormat::U8 => PixelStorage::U8(vec![0; pixel_count]), - PixelFormat::F16 => PixelStorage::F16(vec![0; pixel_count]), - PixelFormat::F32 => PixelStorage::F32(vec![0.0; pixel_count]), + PixelFormat::U8 => PixelStorage::U8(vec![0; pixel_count].into()), + PixelFormat::F16 => PixelStorage::F16(vec![0; pixel_count].into()), + PixelFormat::F32 => PixelStorage::F32(vec![0.0; pixel_count].into()), }; Self::from_storage(storage, resolution, owned_names, encoding) } - pub fn new_constant(resolution: Point2i, channel_names: &[&str], values: &[f32]) -> Self { + pub fn new_constant( + resolution: Point2i, + channel_names: &[impl AsRef], + values: &[f32], + ) -> Self { let n_channels = channel_names.len(); if values.len() != n_channels { panic!( @@ -225,7 +237,7 @@ impl Image { ); } - let n_pixels = (resolution.x * resolution.y) as usize; + let n_pixels = (resolution.x() * resolution.y()) as usize; let mut data = Vec::with_capacity(n_pixels * n_channels); @@ -248,11 +260,11 @@ impl Image { } fn n_channels(&self) -> i32 { - self.base.n_channels + self.base().n_channels } pub fn format(&self) -> PixelFormat { - self.device.base.format + self.base().format } pub fn channel_names(&self) -> Vec<&str> { @@ -260,7 +272,7 @@ impl Image { } pub fn encoding(&self) -> ColorEncoding { - self.view.encoding + self.base().encoding } fn pixel_offset(&self, p: Point2i) -> usize { @@ -399,7 +411,10 @@ impl Image { .collect() } - pub fn get_channel_desc(&self, requested_channels: &[&str]) -> Result { + pub fn get_channel_desc( + &self, + requested_channels: &[impl AsRef], + ) -> Result { let mut offset = Vec::with_capacity(requested_channels.len()); for &req in requested_channels.iter() { @@ -408,9 +423,10 @@ impl Image { offset.push(idx); } None => { - return Err(format!( + return Err(anyhow!( "Missing channel '{}'. Available: {:?}", - req, self.channel_names + req, + self.channel_names )); } } @@ -467,7 +483,7 @@ impl Image { } }; - Self::from_storage(new_storage, res, new_names, self.encoding()) + Self::from_storage(new_storage, res, &new_names, self.encoding()) } pub fn get_sampling_distribution(&self, dxd_a: F, domain: Bounds2f) -> Array2D @@ -477,7 +493,7 @@ impl Image { let width = self.resolution().x(); let height = self.resolution().y(); - let mut dist = Array2D::new_dims(width, height); + let mut dist: Array2D = Array2D::new_dims(width, height); dist.values .par_chunks_mut(width as usize) @@ -488,7 +504,7 @@ impl Image { for (x, out_val) in row.iter_mut().enumerate() { let x = x as i32; - let value = self.get_channels_default(Point2i::new(x, y)).average(); + let value = self.get_channels(Point2i::new(x, y)).average(); let u = (x as Float + 0.5) / width as Float; let v = (y as Float + 0.5) / height as Float; @@ -512,15 +528,17 @@ impl Image { ref_img: &Image, generate_mse_image: bool, ) -> (ImageChannelValues, Option) { + let res = self.resolution(); + let mut sum_se: Vec = vec![0.; desc.size()]; let names_ref = self.channel_names_from_desc(&desc); let ref_desc = ref_img .get_channel_desc(&self.channel_names_from_desc(&desc)) .expect("Channels not found in image"); - assert_eq!(self.resolution(), ref_img.resolution()); + assert_eq!(res, ref_img.resolution()); - let width = self.resolution.x() as usize; - let height = self.resolution.y() as usize; + let width = res.x() as usize; + let height = res.y() as usize; let n_channels = desc.offset.len(); let mut mse_pixels = if generate_mse_image { vec![0.0f32; width * height * n_channels] @@ -528,11 +546,11 @@ impl Image { Vec::new() }; - for y in 0..self.resolution().y() { - for x in 0..self.resolution().x() { + for y in 0..res.y() { + for x in 0..res.x() { let v = - self.get_channel_with_desc(Point2i::new(x, y), &desc, WrapMode::Clamp.into()); - let v_ref = self.get_channel_with_desc( + self.get_channels_with_desc(Point2i::new(x, y), &desc, WrapMode::Clamp.into()); + let v_ref = self.get_channels_with_desc( Point2i::new(x, y), &ref_desc, WrapMode::Clamp.into(), @@ -551,17 +569,12 @@ impl Image { } } - let pixel_count = (self.resolution().x() * self.resolution.y()) as f64; + let pixel_count = (res.x() * res.y()) as f64; let mse_values: SmallVec<[Float; 4]> = sum_se.iter().map(|&s| (s / pixel_count) as Float).collect(); let mse_image = if generate_mse_image { - Some(Image::new( - PixelFormat::F32, - self.resolution, - &names_ref, - LINEAR, - )) + Some(Image::new(PixelFormat::F32, res, &names_ref, LINEAR.into())) } else { None }; @@ -570,15 +583,15 @@ impl Image { } pub fn update_view_pointers(&mut self) { - self.view.pixels = match &self._storage { - PixelStorage::U8(vec) => Pixels::U8(vec.as_ptr()), - PixelStorage::F16(vec) => Pixels::F16(vec.as_ptr() as *const u16), - PixelStorage::F32(vec) => Pixels::F32(vec.as_ptr()), + self.device.pixels = match &self.storage { + PixelStorage::U8(vec) => Pixels::U8(vec.as_ptr().into()), + PixelStorage::F16(vec) => Pixels::F16((vec.as_ptr() as *const u16).into()), + PixelStorage::F32(vec) => Pixels::F32(vec.as_ptr().into()), }; } pub fn has_any_infinite_pixels(&self) -> bool { - if self.format() == PixelFormat::Float { + if self.format() == PixelFormat::F32 { return false; } @@ -595,7 +608,7 @@ impl Image { } pub fn has_any_nan_pixels(&self) -> bool { - if self.format() == PixelFormat::Float { + if self.format() == PixelFormat::F32 { return false; } diff --git a/src/core/image/ops.rs b/src/core/image/ops.rs index cf86494..a826000 100644 --- a/src/core/image/ops.rs +++ b/src/core/image/ops.rs @@ -4,7 +4,8 @@ use rayon::prelude::*; use shared::Float; use shared::core::color::ColorEncoding; use shared::core::geometry::{Bounds2i, Point2i}; -use shared::core::image::{PixelFormat, WrapMode, WrapMode2D}; +use shared::core::image::{PixelFormat, Pixels, WrapMode, WrapMode2D}; +use shared::utils::Ptr; use shared::utils::math::windowed_sinc; use std::sync::{Arc, Mutex}; @@ -16,17 +17,20 @@ pub struct ResampleWeight { impl Image { pub fn flip_y(&mut self) { - let res = self.resolution; + let res = self.resolution(); let nc = self.n_channels(); match &mut self.pixels { - PixelStorage::U8(d) => flip_y_kernel(d, res, nc), - PixelStorage::F16(d) => flip_y_kernel(d, res, nc), - PixelStorage::F32(d) => flip_y_kernel(d, res, nc), + Pixels::U8(d) => flip_y_kernel(d, res, nc), + Pixels::F16(d) => flip_y_kernel(d, res, nc), + Pixels::F32(d) => flip_y_kernel(d, res, nc), } } pub fn crop(&self, bounds: Bounds2i) -> Image { + let res = self.resolution(); + let n_channels = self.n_channels(); + let new_res = Point2i::new( bounds.p_max.x() - bounds.p_min.x(), bounds.p_max.y() - bounds.p_min.y(), @@ -36,19 +40,13 @@ impl Image { self.format, new_res, self.channel_names.clone(), - self.encoding, + self.encoding(), ); match (&self.pixels, &mut new_image.pixels) { - (PixelStorage::U8(src), PixelStorage::U8(dst)) => { - crop_kernel(src, dst, self.resolution, bounds, self.n_channels()) - } - (PixelStorage::F16(src), PixelStorage::F16(dst)) => { - crop_kernel(src, dst, self.resolution, bounds, self.n_channels()) - } - (PixelStorage::F32(src), PixelStorage::F32(dst)) => { - crop_kernel(src, dst, self.resolution, bounds, self.n_channels()) - } + (Pixels::U8(src), Pixels::U8(dst)) => crop_kernel(src, dst, res, bounds, n_channels), + (Pixels::F16(src), Pixels::F16(dst)) => crop_kernel(src, dst, res, bounds, n_channels), + (Pixels::F32(src), Pixels::F32(dst)) => crop_kernel(src, dst, res, bounds, n_channels), _ => panic!("Format mismatch in crop"), } @@ -57,34 +55,29 @@ impl Image { pub fn copy_rect_out(&self, extent: Bounds2i, buf: &mut [Float], wrap: WrapMode2D) { match &self.pixels { - PixelStorage::U8(d) => copy_rect_out_kernel(d, self, extent, buf, wrap), - PixelStorage::F16(d) => copy_rect_out_kernel(d, self, extent, buf, wrap), - PixelStorage::F32(d) => copy_rect_out_kernel(d, self, extent, buf, wrap), + Pixels::U8(d) => copy_rect_out_kernel(d, self, extent, buf, wrap), + Pixels::F16(d) => copy_rect_out_kernel(d, self, extent, buf, wrap), + Pixels::F32(d) => copy_rect_out_kernel(d, self, extent, buf, wrap), } } pub fn copy_rect_in(&mut self, extent: Bounds2i, buf: &[Float]) { - let resolution = self.resolution; - let n_channels = self.n_channels(); - let encoding = self.encoding; + let res = self.resolution(); + let n_channels = self.n_channels() as usize; + let encoding = self.encoding(); match &mut self.pixels { - PixelStorage::U8(d) => { - copy_rect_in_kernel(d, resolution, n_channels, encoding, extent, buf) - } - PixelStorage::F16(d) => { - copy_rect_in_kernel(d, resolution, n_channels, encoding, extent, buf) - } - PixelStorage::F32(d) => { - copy_rect_in_kernel(d, resolution, n_channels, encoding, extent, buf) - } + Pixels::U8(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf), + Pixels::F16(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf), + Pixels::F32(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf), } } pub fn float_resize_up(&self, new_res: Point2i, wrap_mode: WrapMode2D) -> Image { - assert!(new_res.x() >= self.resolution.x() && new_res.y() >= self.resolution.y()); + let res = self.resolution(); + assert!(new_res.x() >= res.x() && new_res.y() >= res.y()); assert!( - matches!(self.format, PixelFormat::F32), + matches!(self.format(), PixelFormat::F32), "ResizeUp requires Float format" ); @@ -92,11 +85,11 @@ impl Image { PixelFormat::F32, // Force float output new_res, self.channel_names.clone(), - self.encoding, + self.encoding(), ))); - let x_weights = resample_weights(self.resolution.x() as usize, new_res.x() as usize); - let y_weights = resample_weights(self.resolution.y() as usize, new_res.y() as usize); + let x_weights = resample_weights(res.x() as usize, new_res.x() as usize); + let y_weights = resample_weights(res.y() as usize, new_res.y() as usize); let n_channels = self.n_channels(); let tile_size = 16; @@ -118,7 +111,7 @@ impl Image { &in_buf, in_extent, *out_extent, - n_channels, + n_channels.try_into().unwrap(), &x_weights, &y_weights, ); @@ -141,23 +134,23 @@ impl Image { loop { let prev = levels.last().unwrap(); - let old = prev.resolution; + let old = prev.resolution(); if old.x() == 1 && old.y() == 1 { break; } let new_res = Point2i::new((old.x() / 2).max(1), (old.y() / 2).max(1)); - let mut next = Image::from_vector( - prev.format, + let mut next = Image::new( + prev.format(), new_res, prev.channel_names.clone(), - prev.encoding, + prev.encoding(), ); match &mut next.pixels { - PixelStorage::U8(d) => downsample_kernel(d, new_res, prev, internal_wrap), - PixelStorage::F16(d) => downsample_kernel(d, new_res, prev, internal_wrap), - PixelStorage::F32(d) => downsample_kernel(d, new_res, prev, internal_wrap), + Pixels::U8(d) => downsample_kernel(d, new_res, prev, internal_wrap), + Pixels::F16(d) => downsample_kernel(d, new_res, prev, internal_wrap), + Pixels::F32(d) => downsample_kernel(d, new_res, prev, internal_wrap), } levels.push(next); } @@ -209,9 +202,9 @@ fn copy_rect_out_kernel( wrap: WrapMode2D, ) { let w = (extent.p_max.x() - extent.p_min.x()) as usize; - let channels = image.n_channels(); - let enc = image.encoding; - let res = image.resolution; + let channels = image.n_channels() as usize; + let enc = image.encoding(); + let res = image.resolution(); buf.par_chunks_mut(w * channels) .enumerate() @@ -220,7 +213,6 @@ fn copy_rect_out_kernel( for x_rel in 0..w { let x = extent.p_min.x() + x_rel as i32; - // This allows us to use 'src' directly (Fast Path). if x >= 0 && x < res.x() && y >= 0 && y < res.y() { let offset = (y as usize * res.x() as usize + x as usize) * channels; @@ -228,7 +220,6 @@ fn copy_rect_out_kernel( row_buf[x_rel * channels + c] = T::to_linear(src[offset + c], enc); } } else { - // Slow path: Out of bounds, requires Wrap Mode logic. // We fall back to get_channel which handles the wrapping math. let p = Point2i::new(x, y); for c in 0..channels { @@ -273,17 +264,17 @@ fn copy_rect_in_kernel( } fn downsample_kernel( - dst: &mut [T], + dst: &mut Ptr, dst_res: Point2i, prev: &Image, wrap: WrapMode2D, ) { let w = dst_res.x() as usize; let channels = prev.n_channels(); - let enc = prev.encoding; - let old_res = prev.resolution; + let enc = prev.encoding(); + let old_res = prev.resolution(); - dst.par_chunks_mut(w * channels) + dst.par_chunks_mut(w * channels as usize) .enumerate() .for_each(|(y, row)| { let src_y = y * 2; @@ -305,7 +296,7 @@ fn downsample_kernel( } let avg = if count > 0.0 { sum / count } else { 0.0 }; - row[x * channels + c] = T::from_linear(avg, enc); + row[x * channels as usize + c as usize] = T::from_linear(avg, enc); } } }); diff --git a/src/core/image/pixel.rs b/src/core/image/pixel.rs index 0dfe473..46f1a24 100644 --- a/src/core/image/pixel.rs +++ b/src/core/image/pixel.rs @@ -1,6 +1,6 @@ -use shared::Float; -use shared::core::color::ColorEncoding; use half::f16; +use shared::Float; +use shared::core::color::{ColorEncoding, ColorEncodingTrait}; // Allows writing generic algorithms that work on any image format. pub trait PixelStorage: Copy + Send + Sync + 'static + PartialEq { @@ -34,14 +34,14 @@ impl PixelStorage for u8 { #[inline(always)] fn from_linear(val: Float, enc: ColorEncoding) -> Self { let mut out = [0u8]; - enc.from_linear_slice(&[val], &mut out); + enc.from_linear(&[val], &mut out); out[0] } #[inline(always)] fn to_linear(self, enc: ColorEncoding) -> Float { let mut out = [0.0]; - enc.to_linear_slice(&[self], &mut out); + enc.to_linear(&[self], &mut out); out[0] } } diff --git a/src/core/light.rs b/src/core/light.rs index 9073b55..4888e1f 100644 --- a/src/core/light.rs +++ b/src/core/light.rs @@ -1,7 +1,6 @@ use crate::core::spectrum::SPECTRUM_CACHE; use crate::core::texture::FloatTexture; use crate::spectra::DenselySampledSpectrumBuffer; -use crate::utils::containers::InternCache; use crate::utils::{Arena, FileLoc, ParameterDictionary}; use anyhow::{Result, anyhow}; use shared::core::camera::CameraTransform; @@ -12,11 +11,12 @@ use shared::core::spectrum::Spectrum; use shared::lights::*; use shared::spectra::RGBColorSpace; use shared::utils::Transform; +use std::sync::Arc; -pub fn lookup_spectrum(s: &Spectrum) -> DenselySampledSpectrumBuffer { - let cache = SPECTRUM_CACHE.get_or_init(InternCache::new); +pub fn lookup_spectrum(s: &Spectrum) -> Arc { + let cache = &SPECTRUM_CACHE; let dense_spectrum = DenselySampledSpectrumBuffer::from_spectrum(s); - cache.lookup(dense_spectrum).as_ref() + cache.lookup(dense_spectrum).into() } pub trait CreateLight { @@ -62,7 +62,6 @@ impl LightFactory for Light { ) -> Result { match name { "diffuse" => DiffuseAreaLight::create( - name, arena, render_from_light, medium, @@ -70,7 +69,8 @@ impl LightFactory for Light { loc, shape, alpha_tex, - )?, + colorspace, + ), "point" => PointLight::create( arena, render_from_light, @@ -80,7 +80,7 @@ impl LightFactory for Light { shape, alpha_tex, colorspace, - )?, + ), "spot" => SpotLight::create( arena, render_from_light, @@ -90,7 +90,7 @@ impl LightFactory for Light { shape, alpha_tex, colorspace, - )?, + ), "goniometric" => GoniometricLight::create( arena, render_from_light, @@ -100,7 +100,7 @@ impl LightFactory for Light { shape, alpha_tex, colorspace, - )?, + ), "projection" => ProjectionLight::create( arena, render_from_light, @@ -110,7 +110,7 @@ impl LightFactory for Light { shape, alpha_tex, colorspace, - )?, + ), "distant" => DistantLight::create( arena, render_from_light, @@ -120,17 +120,17 @@ impl LightFactory for Light { shape, alpha_tex, colorspace, - )?, + ), "infinite" => crate::lights::infinite::create( arena, render_from_light, - medium, + medium.into(), camera_transform, parameters, colorspace, loc, - )?, - _ => Err(anyhow!(loc, "unknown light type: \"{}\"", name)), + ), + _ => Err(anyhow!("{}: unknown light type: \"{}\"", loc, name)), } } } diff --git a/src/core/material.rs b/src/core/material.rs index 26005c4..694dfc1 100644 --- a/src/core/material.rs +++ b/src/core/material.rs @@ -23,7 +23,7 @@ pub trait MaterialFactory { name: &str, params: &TextureParameterDictionary, normal_map: Option>, - named_materials: HashMap, + named_materials: Arc>, loc: FileLoc, arena: &mut Arena, ) -> Result; @@ -34,7 +34,7 @@ impl MaterialFactory for Material { name: &str, parameters: &TextureParameterDictionary, normal_map: Option>, - named_materials: HashMap, + named_materials: Arc>, loc: FileLoc, arena: &mut Arena, ) -> Result { diff --git a/src/core/medium.rs b/src/core/medium.rs index 21a3895..72a09c1 100644 --- a/src/core/medium.rs +++ b/src/core/medium.rs @@ -2,7 +2,7 @@ use crate::spectra::dense::DenselySampledSpectrumBuffer; use shared::core::geometry::{Bounds3f, Point3i}; use shared::core::medium::{GridMedium, HGPhaseFunction, HomogeneousMedium, RGBGridMedium}; use shared::core::spectrum::{Spectrum, SpectrumTrait}; -use shared::spectra::{DenselySampledSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum}; +use shared::spectra::{RGBIlluminantSpectrum, RGBUnboundedSpectrum}; use shared::utils::Transform; use shared::utils::containers::SampledGrid; use shared::{Float, core::medium::MajorantGrid}; @@ -89,7 +89,7 @@ pub trait GridMediumCreator { sigma_scale: Float, g: Float, density_grid: SampledGrid, - temperature_grid: SampledGrid, + temperature_grid: Option>, le: &Spectrum, le_scale: SampledGrid, ) -> Self; @@ -105,21 +105,23 @@ impl GridMediumCreator for GridMedium { sigma_scale: Float, g: Float, density_grid: SampledGrid, - temperature_grid: SampledGrid, + temperature_grid: Option>, le: &Spectrum, le_scale: SampledGrid, ) -> Self { - let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(sigma_a); - let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(sigma_s); - let le_spec = DenselySampledSpectrum::from_spectrum(le); + let mut sigma_a_spec = DenselySampledSpectrumBuffer::from_spectrum(sigma_a); + let mut sigma_s_spec = DenselySampledSpectrumBuffer::from_spectrum(sigma_s); + sigma_a_spec.scale(sigma_scale); sigma_s_spec.scale(sigma_scale); + let le_spec = DenselySampledSpectrumBuffer::from_spectrum(le); + let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16)); let is_emissive = if temperature_grid.is_some() { true } else { - le_spec.max_value() > 0. + Spectrum::Dense(le_spec.device()).max_value() > 0. }; for z in 0..majorant_grid.res.z() { @@ -134,12 +136,12 @@ impl GridMediumCreator for GridMedium { Self { bounds: *bounds, render_from_medium: *render_from_medium, - sigma_a_spec, - sigma_s_spec, + sigma_a_spec: sigma_a_spec.device(), + sigma_s_spec: sigma_s_spec.device(), density_grid, phase: HGPhaseFunction::new(g), temperature_grid, - le_spec, + le_spec: le_spec.device(), le_scale, is_emissive, majorant_grid, @@ -169,16 +171,16 @@ impl HomogeneousMediumCreator for HomogeneousMedium { ) -> Self { let mut sigma_a_spec = DenselySampledSpectrumBuffer::from_spectrum(&sigma_a); let mut sigma_s_spec = DenselySampledSpectrumBuffer::from_spectrum(&sigma_s); - let mut le_spec = DenselySampledSpectrum::from_spectrum(&le); + let mut le_spec = DenselySampledSpectrumBuffer::from_spectrum(&le); sigma_a_spec.scale(sigma_scale); sigma_s_spec.scale(sigma_scale); le_spec.scale(le_scale); Self { - sigma_a_spec, - sigma_s_spec, - le_spec, + sigma_a_spec: sigma_a_spec.device(), + sigma_s_spec: sigma_s_spec.device(), + le_spec: le_spec.device(), phase: HGPhaseFunction::new(g), } } diff --git a/src/core/mod.rs b/src/core/mod.rs index f39c4c8..2bddd42 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -10,7 +10,6 @@ pub mod material; pub mod medium; pub mod primitive; pub mod sampler; -pub mod sampler; pub mod scene; pub mod shape; pub mod spectrum; diff --git a/src/core/primitive.rs b/src/core/primitive.rs index b882d61..4c30297 100644 --- a/src/core/primitive.rs +++ b/src/core/primitive.rs @@ -2,6 +2,7 @@ use shared::core::{ light::Light, material::Material, medium::MediumInterface, + texture::GPUFloatTexture primitive::{GeometricPrimitive, SimplePrimitive}, shape::Shape, }; diff --git a/src/core/sampler.rs b/src/core/sampler.rs index a1ee383..35bf9bc 100644 --- a/src/core/sampler.rs +++ b/src/core/sampler.rs @@ -1,4 +1,5 @@ use crate::Arena; +use crate::samplers::CreateSampler; use crate::utils::{FileLoc, ParameterDictionary}; use anyhow::{Result, anyhow}; use shared::core::geometry::Point2i; @@ -7,15 +8,6 @@ use shared::core::sampler::{ StratifiedSampler, ZSobolSampler, }; -pub trait CreateSampler { - fn create( - params: &ParameterDictionary, - full_res: Point2i, - loc: &FileLoc, - arena: &mut Arena, - ) -> Result; -} - pub trait SamplerFactory { fn create( name: &str, @@ -35,30 +27,12 @@ impl SamplerFactory for Sampler { arena: &mut Arena, ) -> Result { match name { - "zsobol" => { - let sampler = ZSobolSampler::create(params, full_res, loc, arena)?; - Ok(Sampler::ZSobol(sampler)) - } - "paddedsobol" => { - let sampler = PaddedSobolSampler::create(params, full_res, loc, arena)?; - Ok(Sampler::PaddedSobol(sampler)) - } - "halton" => { - let sampler = HaltonSampler::create(params, full_res, loc, arena)?; - Ok(Sampler::Halton(sampler)) - } - "sobol" => { - let sampler = SobolSampler::create(params, full_res, loc, arena)?; - Ok(Sampler::Sobol(sampler)) - } - "Independent" => { - let sampler = IndependentSampler::create(params, full_res, loc, arena)?; - Ok(Sampler::Independent(sampler)) - } - "stratified" => { - let sampler = StratifiedSampler::create(params, full_res, loc, arena)?; - Ok(Sampler::Stratified(sampler)) - } + "zsobol" => ZSobolSampler::create(params, full_res, loc, arena), + "paddedsobol" => PaddedSobolSampler::create(params, full_res, loc, arena), + "halton" => HaltonSampler::create(params, full_res, loc, arena), + "sobol" => SobolSampler::create(params, full_res, loc, arena), + "Independent" => IndependentSampler::create(params, full_res, loc, arena), + "stratified" => StratifiedSampler::create(params, full_res, loc, arena), _ => Err(anyhow!("Film type '{}' unknown at {}", name, loc)), } } diff --git a/src/core/scene/builder.rs b/src/core/scene/builder.rs index 1dbcb2b..24a962c 100644 --- a/src/core/scene/builder.rs +++ b/src/core/scene/builder.rs @@ -1,7 +1,9 @@ use super::BasicScene; use super::entities::*; +use crate::spectra::get_colorspace_context; use crate::utils::error::FileLoc; use crate::utils::normalize_utf8; +use crate::utils::parameters::error_exit; use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector}; use crate::utils::parser::ParserTarget; use shared::Float; @@ -186,7 +188,8 @@ impl ParserTarget for BasicSceneBuilder { } fn color_space(&mut self, name: &str, loc: FileLoc) { - let _ = match RGBColorSpace::get_named(name) { + let stdcs = get_colorspace_context(); + let _ = match stdcs.get_named(name) { Ok(cs) => { self.graphics_state.color_space = Some(cs); } @@ -536,14 +539,13 @@ impl ParserTarget for BasicSceneBuilder { ); if type_name != "float" && type_name != "spectrum" { - self.error_exit_deferred( - &loc, + error_exit( + Some(&loc), &format!( "{}: texture type unknown. Must be \"float\" or \"spectrum\".", tex_name ), ); - return; } { @@ -554,8 +556,7 @@ impl ParserTarget for BasicSceneBuilder { }; if names.contains(&name) { - self.error_exit_deferred(&loc, &format!("Redefining texture \"{}\".", name)); - return; + error_exit(Some(&loc), &format!("Redefining texture \"{}\".", name)); } names.insert(name.to_string()); } @@ -582,9 +583,9 @@ impl ParserTarget for BasicSceneBuilder { let entity = SceneEntity { name: name.to_string(), loc, - parameters: params, + parameters: ParameterDictionary::new(*params, None), }; - self.graphics_state.current_material_name = self.scene.add_material(name); + self.graphics_state.current_material_name = self.scene.add_material(entity); } fn make_named_material(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) { todo!() diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index a7f0c8a..1de0bd1 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -11,7 +11,7 @@ use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::utils::arena; use crate::utils::arena::Arena; use crate::utils::error::FileLoc; -use crate::utils::parallel::run_async; +use crate::utils::parallel::{AsyncJob, run_async}; use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary}; use crate::utils::{Upload, resolve_filename}; use parking_lot::Mutex; @@ -28,6 +28,7 @@ use shared::core::sampler::Sampler; use shared::core::shape::Shape; use shared::core::texture::SpectrumType; use shared::spectra::RGBColorSpace; +use shared::utils::Ptr; use std::collections::HashMap; use std::sync::Arc; @@ -101,10 +102,6 @@ impl BasicScene { } } - // ======================================================================== - // Options - // ======================================================================== - pub fn set_options( self: &Arc, filter: SceneEntity, @@ -113,6 +110,7 @@ impl BasicScene { sampler: SceneEntity, integ: SceneEntity, accel: SceneEntity, + arena: &mut Arena, ) { *self.integrator.lock() = Some(integ); *self.accelerator.lock() = Some(accel); @@ -159,7 +157,7 @@ impl BasicScene { &camera.base.name, &camera.base.parameters, &camera.camera_transform, - medium.unwrap(), + *medium.unwrap(), camera_film, &camera.base.loc, arena, @@ -169,10 +167,6 @@ impl BasicScene { 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); @@ -186,7 +180,18 @@ impl BasicScene { state.materials.len() - 1 } - pub fn add_float_texture(&self, name: String, texture: TextureSceneEntity) { + fn add_texture_generic( + &self, + name: String, + texture: TextureSceneEntity, + state: &mut TextureState, + get_serial: impl FnOnce(&mut TextureState) -> &mut Vec<(String, TextureSceneEntity)>, + get_jobs: impl FnOnce(&mut TextureState) -> &mut HashMap>>, + create_fn: F, + ) where + T: Send + 'static, + F: FnOnce(TextureSceneEntity) -> T + Send + 'static, + { if texture.render_from_object.is_animated() { log::info!( "{}: Animated world to texture not supported, using start", @@ -194,113 +199,94 @@ impl BasicScene { ); } - let mut state = self.texture_state.lock(); - if texture.base.name != "imagemap" && texture.base.name != "ptex" { - state.serial_float_textures.push((name, texture)); + get_serial(state).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; + if !self.validate_texture_file(&filename, &texture.base.loc, &mut state.n_missing_textures) + { return; } if state.loading_texture_filenames.contains(&filename) { - state.serial_float_textures.push((name, texture)); + get_serial(state).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.into(), None); - let texture = FloatTexture::create( - &texture_clone.base.name, - &render_from_texture, - tex_dict, - texture_clone.base.loc, - arena, - ) - .expect("Could not create Float texture"); - - Arc::new(texture) - }); - - state.float_texture_jobs.insert(name, job); + state.loading_texture_filenames.insert(filename); + let job = run_async(move || Arc::new(create_fn(texture))); + get_jobs(state).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", "")); + fn validate_texture_file(&self, filename: &str, loc: &FileLoc, n_missing: &mut usize) -> bool { if filename.is_empty() { log::error!( "[{:?}] \"string filename\" not provided for image texture.", - texture.base.loc + loc ); - state.n_missing_textures += 1; - return; + *n_missing += 1; + return false; } - - if !std::path::Path::new(&filename).exists() { - log::error!("[{:?}] {}: file not found.", texture.base.loc, filename); - state.n_missing_textures += 1; - return; + if !std::path::Path::new(filename).exists() { + log::error!("[{:?}] {}: file not found.", loc, filename); + *n_missing += 1; + return false; } + true + } - if state.loading_texture_filenames.contains(&filename) { - state.serial_spectrum_textures.push((name, texture)); - return; - } + pub fn add_float_texture(&self, name: String, texture: TextureSceneEntity, arena: &mut Arena) { + let mut state = self.texture_state.lock(); + self.add_texture_generic( + name, + texture, + &mut state, + |s| &mut s.serial_float_textures, + |s| &mut s.float_texture_jobs, + |tex| { + let render_from_texture = tex.render_from_object.start_transform; + let tex_dict = TextureParameterDictionary::new(tex.base.parameters.into(), None); + FloatTexture::create( + &tex.base.name, + render_from_texture, + tex_dict, + tex.base.loc, + arena, + ) + .expect("Could not create Float texture") + }, + ); + } - 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.into(), None); - let texture = SpectrumTexture::create( - &texture_clone.base.name, - render_from_texture, - tex_dict, - SpectrumType::Albedo, - texture_clone.base.loc, - arena, - ) - .expect("Could not crate spectrum texture."); - - Arc::new(texture) - }); - - state.spectrum_texture_jobs.insert(name, job); + pub fn add_spectrum_texture( + &self, + name: String, + texture: TextureSceneEntity, + arena: &mut Arena, + ) { + let mut state = self.texture_state.lock(); + self.add_texture_generic( + name, + texture, + &mut state, + |s| &mut s.serial_spectrum_textures, + |s| &mut s.spectrum_texture_jobs, + |tex| { + let render_from_texture = tex.render_from_object.start_transform; + let tex_dict = TextureParameterDictionary::new(tex.base.parameters.into(), None); + SpectrumTexture::create( + &tex.base.name, + render_from_texture, + tex_dict, + SpectrumType::Albedo, + tex.base.loc, + arena, + ) + .expect("Could not create spectrum texture") + }, + ); } pub fn add_area_light(&self, light: SceneEntity) -> usize { @@ -328,11 +314,7 @@ impl BasicScene { self.instances.lock().extend(uses); } - // ======================================================================== - // Finalization: Textures - // ======================================================================== - - pub fn create_textures(&self) -> NamedTextures { + pub fn create_textures(&self, arena: &mut Arena) -> NamedTextures { let mut state = self.texture_state.lock(); let mut float_textures: HashMap> = HashMap::new(); @@ -347,18 +329,18 @@ impl BasicScene { spectrum_textures.insert(name, job.wait()); } - // Create serial textures (need access to already-loaded textures) - let named = NamedTextures { - float_textures: float_textures.clone(), - albedo_spectrum_textures: spectrum_textures.clone(), - illuminant_spectrum_textures: spectrum_textures.clone(), - unbounded_spectrum_textures: spectrum_textures.clone(), + // Create serial textures (need access to loaded textures, using shared memory) + let mut named = NamedTextures { + float_textures: Arc::new(float_textures.clone()), + albedo_spectrum_textures: Arc::new(spectrum_textures.clone()), + illuminant_spectrum_textures: Arc::new(spectrum_textures.clone()), + unbounded_spectrum_textures: Arc::new(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.into(), Some(named)); + TextureParameterDictionary::new(entity.base.parameters.into(), Some(&named)); let tex = FloatTexture::create( &entity.base.name, render_from_texture, @@ -367,13 +349,13 @@ impl BasicScene { arena, ) .expect("Could not create float texture"); - float_textures.insert(name, Arc::new(tex)); + Arc::make_mut(&mut named.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.into(), Some(named)); + TextureParameterDictionary::new(entity.base.parameters.into(), Some(&named)); let tex = SpectrumTexture::create( &entity.base.name, render_from_texture, @@ -383,32 +365,21 @@ impl BasicScene { arena, ) .expect("Could not create spectrum texture"); - spectrum_textures.insert(name, Arc::new(tex)); - } - - NamedTextures { - float_textures, - albedo_spectrum_textures: spectrum_textures, - unbounded_spectrum_textures: spectrum_textures, - illuminant_spectrum_textures: spectrum_textures, + Arc::make_mut(&mut named.albedo_spectrum_textures).insert(name, Arc::new(tex)); } + named } - // ======================================================================== - // Finalization: Materials - // ======================================================================== - pub fn create_materials( &self, textures: &NamedTextures, + arena: &mut Arena, ) -> (HashMap, Vec) { 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); + for (filename, job) in state.normal_map_jobs.drain() { + state.normal_maps.insert(filename, job.wait()); } let mut named_materials: HashMap = HashMap::new(); @@ -440,7 +411,7 @@ impl BasicScene { &mat_type, &tex_dict, normal_map, - named_materials, + named_materials.into(), entity.loc, arena, ) @@ -449,24 +420,27 @@ impl BasicScene { named_materials.insert(name.clone(), mat); } - let mut materials: Vec = Vec::with_capacity(state.materials.len()); + let materials: Vec = state + .materials + .iter() + .map(|entity| { + let normal_map = self.get_normal_map(&state, &entity.parameters); + let tex_dict = TextureParameterDictionary::new( + entity.parameters.clone().into(), + Some(textures), + ); - for entity in &state.materials { - let normal_map = self.get_normal_map(&state, &entity.parameters); - let tex_dict = - TextureParameterDictionary::new(entity.parameters.into(), Some(*textures)); - - let mat = Material::create( - &entity.name, - &tex_dict, - normal_map, - named_materials, - entity.loc, - arena, - ) - .expect("Could not create material"); - materials.push(mat); - } + Material::create( + &entity.name, + &tex_dict, + normal_map, + &named_materials, // Reference + entity.loc.clone(), + arena, + ) + .expect("Could not create material") + }) + .collect(); (named_materials, materials) } @@ -478,6 +452,7 @@ impl BasicScene { named_materials: &HashMap, materials: &Vec, shape_lights: &HashMap>, + arena: &mut Arena, ) -> Vec { let shapes = self.shapes.lock(); let animated_shapes = self.animated_shapes.lock(); @@ -493,12 +468,10 @@ impl BasicScene { let mut primitives = Vec::new(); - // Static shapes: parallel load, serial upload - let loaded = self.load_shapes_parallel(&shapes, &lookup); + let loaded = self.load_shapes_parallel(&shapes, &lookup, arena); primitives.extend(self.upload_shapes(arena, &shapes, loaded, &lookup)); - // Animated shapes - let loaded_anim = self.load_animated_shapes_parallel(&animated_shapes, &lookup); + let loaded_anim = self.load_animated_shapes_parallel(&animated_shapes, &lookup, arena); primitives.extend(self.upload_animated_shapes( arena, &animated_shapes, @@ -513,6 +486,7 @@ impl BasicScene { &self, entities: &[ShapeSceneEntity], lookup: &SceneLookup, + arena: &mut Arena, ) -> Vec> { entities .par_iter() @@ -535,6 +509,7 @@ impl BasicScene { &self, entities: &[AnimatedShapeSceneEntity], lookup: &SceneLookup, + arena: &mut Arena, ) -> Vec> { entities .par_iter() @@ -549,6 +524,11 @@ impl BasicScene { sh.transformed_base.base.loc, arena, ) + .map_err(|e| { + log::error!("{}: Failed to create shape: {}", sh.base.loc, e); + e + }) + .ok() }) .collect() } @@ -609,10 +589,10 @@ impl BasicScene { } else { let p = GeometricPrimitive::new( shape_ptr, - Ptr::from(&mtl), - area_light, + mtl.upload(arena), + area_light.upload(arena), mi.clone(), - alpha_tex.clone(), + alpha_tex.upload(arena), ); Primitive::Geometric(p) }; @@ -752,11 +732,13 @@ impl BasicScene { let mut state = self.media_state.lock(); if let Some(medium) = state.map.get(name) { - return Some(medium.clone()); + return Some(Arc::clone(medium)); } if let Some(job) = state.jobs.remove(name) { - let medium = Arc::new(job.wait()); + let job: AsyncJob = job; + let result: Medium = job.wait(); + let medium: Arc = Arc::new(job.wait()); state.map.insert(name.to_string(), medium.clone()); return Some(medium); } diff --git a/src/core/scene/state.rs b/src/core/scene/state.rs index 6449664..882c8e9 100644 --- a/src/core/scene/state.rs +++ b/src/core/scene/state.rs @@ -15,7 +15,7 @@ pub struct TextureState { pub loading_texture_filenames: HashSet, pub float_texture_jobs: HashMap>>, pub spectrum_texture_jobs: HashMap>>, - pub n_missing_textures: i32, + pub n_missing_textures: usize, } #[derive(Debug)] @@ -43,3 +43,12 @@ pub struct SingletonState { pub result: Option>, pub job: Option>, } + +impl Default for SingletonState { + fn default() -> Self { + Self { + result: None, + job: None, + } + } +} diff --git a/src/core/texture.rs b/src/core/texture.rs index efa34cb..7d20824 100644 --- a/src/core/texture.rs +++ b/src/core/texture.rs @@ -69,19 +69,21 @@ impl FloatTexture { arena: &mut Arena, ) -> Result { match name { - "constant" => FloatConstantTexture::create(render_from_texture, params, loc), + "constant" => FloatConstantTexture::create(render_from_texture, params, loc, arena), "scale" => FloatScaledTexture::create(&render_from_texture, ¶ms, &loc, arena), "mix" => FloatMixTexture::create(&render_from_texture, ¶ms, &loc, arena), "directionmix" => { FloatDirectionMixTexture::create(&render_from_texture, ¶ms, &loc, arena) } - "bilerp" => FloatBilerpTexture::create(render_from_texture, params, loc), - "imagemap" => FloatImageTexture::create(render_from_texture, params, loc), - "checkerboard" => FloatCheckerboardTexture::create(render_from_texture, params, loc), - "dots" => FloatDotsTexture::create(render_from_texture, params, loc), - "fbm" => FBmTexture::create(render_from_texture, params, loc), - "wrinkled" => WrinkledTexture::create(render_from_texture, params, loc), - "windy" => WindyTexture::create(render_from_texture, params, loc), + "bilerp" => FloatBilerpTexture::create(render_from_texture, params, loc, arena), + "imagemap" => FloatImageTexture::create(render_from_texture, params, loc, arena), + "checkerboard" => { + FloatCheckerboardTexture::create(render_from_texture, params, loc, arena) + } + "dots" => FloatDotsTexture::create(render_from_texture, params, loc, arena), + "fbm" => FBmTexture::create(render_from_texture, params, loc, arena), + "wrinkled" => WrinkledTexture::create(render_from_texture, params, loc, arena), + "windy" => WindyTexture::create(render_from_texture, params, loc, arena), _ => Err(anyhow!("Float texture type '{}' unknown at {}", name, loc)), } } diff --git a/src/integrators/base.rs b/src/integrators/base.rs index 027ee43..a9c230a 100644 --- a/src/integrators/base.rs +++ b/src/integrators/base.rs @@ -1,7 +1,7 @@ use super::state::PathState; use shared::core::geometry::Ray; use shared::core::interaction::{Interaction, InteractionTrait}; -use shared::core::light::Light; +use shared::core::light::{Light, LightTrait}; use shared::core::primitive::{Primitive, PrimitiveTrait}; use shared::core::shape::ShapeIntersection; use shared::lights::LightSampler; diff --git a/src/integrators/path.rs b/src/integrators/path.rs index 2cd5994..fe61f4e 100644 --- a/src/integrators/path.rs +++ b/src/integrators/path.rs @@ -65,7 +65,7 @@ impl PathConfig { pub struct PathIntegrator { base: IntegratorBase, camera: Arc, - light_sampler: LightSampler, + sampler: LightSampler, config: PathConfig, } @@ -77,14 +77,14 @@ impl PathIntegrator { aggregate: Arc, lights: Vec>, camera: Arc, + sampler: LightSampler, config: PathConfig, ) -> Self { let base = IntegratorBase::new(aggregate, lights.clone()); - let light_sampler = LightSampler::new(&lights); Self { base, camera, - light_sampler, + sampler, config, } } @@ -99,10 +99,7 @@ impl PathIntegrator { ) -> SampledSpectrum { let ctx = LightSampleContext::from(intr); - let Some(sampled) = self - .light_sampler - .sample_with_context(&ctx, sampler.get1d()) - else { + let Some(sampled) = self.sampler.sample_with_context(&ctx, sampler.get1d()) else { return SampledSpectrum::zero(); }; @@ -220,7 +217,7 @@ impl RayIntegratorTrait for PathIntegrator { &mut state, &ray, lambda, - Some(&self.light_sampler), + Some(&self.sampler), self.config.use_mis, ); break; @@ -236,7 +233,7 @@ impl RayIntegratorTrait for PathIntegrator { } else if self.config.use_mis { if !isect.area_light.is_null() { let light = &isect.area_light; - let p_l = self.light_sampler.pmf_with_context(&state.prev_ctx, light) + let p_l = self.sampler.pmf_with_context(&state.prev_ctx, light) * light.pdf_li(&state.prev_ctx, ray.d, true); let w_b = power_heuristic(1, state.prev_pdf, 1, p_l); state.l += state.beta * w_b * le; diff --git a/src/integrators/pipeline.rs b/src/integrators/pipeline.rs index 4d4c8e9..0b11f14 100644 --- a/src/integrators/pipeline.rs +++ b/src/integrators/pipeline.rs @@ -9,6 +9,7 @@ use indicatif::{ProgressBar, ProgressStyle}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use shared::Float; use shared::core::camera::{Camera, CameraTrait}; +use shared::core::film::Film; use shared::core::geometry::{Bounds2i, Point2i, VectorLike}; use shared::core::options::get_options; use shared::core::sampler::get_camera_sample; @@ -235,8 +236,9 @@ pub fn evaluate_pixel_sample( if get_options().disable_wavelength_jitter { lu = 0.5; } + let lambda = camera.get_film().sample_wavelengths(lu); - let film = camera.get_film(); + let mut film: &mut Film = camera.get_film(); let filter = film.get_filter(); let camera_sample = get_camera_sample(sampler, pixel, filter); if let Some(mut camera_ray) = camera.generate_ray_differential(camera_sample, &lambda) { @@ -258,7 +260,7 @@ pub fn evaluate_pixel_sample( l *= camera_ray.weight; let std_spectra = get_spectra_context(); - if l.has_nans() || l.y(&lambda, std_spectra).is_infinite() { + if l.has_nans() || l.y(&lambda, &std_spectra).is_infinite() { l = SampledSpectrum::new(0.); } diff --git a/src/lib.rs b/src/lib.rs index 076370a..bd43710 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#[allow(dead_code)] pub mod core; pub mod filters; pub mod globals; @@ -14,3 +15,5 @@ pub mod utils; pub mod gpu; pub use utils::arena::Arena; +pub use utils::error::FileLoc; +pub use utils::parameters::ParameterDictionary; diff --git a/src/lights/diffuse.rs b/src/lights/diffuse.rs index 25bab00..7f9f6e8 100644 --- a/src/lights/diffuse.rs +++ b/src/lights/diffuse.rs @@ -97,7 +97,7 @@ impl CreateDiffuseLight for DiffuseAreaLight { colorspace, shape, alpha: stored_alpha.expect("Could not retrieve texture"), - lemit: Ptr::from(lemit.device()), + lemit: Ptr::from(&lemit.device()), two_sided, scale, } diff --git a/src/lights/distant.rs b/src/lights/distant.rs index 9606f6f..222f77d 100644 --- a/src/lights/distant.rs +++ b/src/lights/distant.rs @@ -28,7 +28,7 @@ impl CreateDistantLight for DistantLight { let lemit = lookup_spectrum(&le); Self { base, - lemit: Ptr::from(lemit.device()), + lemit: Ptr::from(&lemit.device()), scale, scene_center: Point3f::default(), scene_radius: 0., diff --git a/src/lights/goniometric.rs b/src/lights/goniometric.rs index d4608ac..0d69f94 100644 --- a/src/lights/goniometric.rs +++ b/src/lights/goniometric.rs @@ -46,7 +46,7 @@ impl CreateGoniometricLight for GoniometricLight { let distrib = PiecewiseConstant2D::from_image(&image); Self { base, - iemit: Ptr::from(iemit.device()), + iemit: Ptr::from(&iemit.device()), scale, image: Ptr::from(image.device_image()), distrib: Ptr::from(&distrib.device), diff --git a/src/lights/infinite.rs b/src/lights/infinite.rs index 7bb1281..2ec8e2b 100644 --- a/src/lights/infinite.rs +++ b/src/lights/infinite.rs @@ -129,7 +129,7 @@ pub fn create( _medium: MediumInterface, camera_transform: CameraTransform, parameters: &ParameterDictionary, - colorspace: &RGBColorSpace, + colorspace: Option<&RGBColorSpace>, loc: &FileLoc, ) -> Result { let l = parameters.get_spectrum_array("L", SpectrumType::Illuminant); @@ -152,7 +152,7 @@ pub fn create( scale /= spectrum_to_photometric(l[0]); l[0] } else { - Spectrum::Dense(colorspace.illuminant) + Spectrum::Dense(colorspace.unwrap().illuminant) }; if e_v > 0.0 { @@ -165,7 +165,7 @@ pub fn create( } // Image-based lights - let (image, image_cs) = load_image(&filename, &l, colorspace, loc)?; + let (image, image_cs) = load_image(&filename, &l, colorspace.unwrap(), loc)?; scale /= spectrum_to_photometric(Spectrum::Dense(image_cs.illuminant)); @@ -208,11 +208,11 @@ fn create_image_light( // Extract luminance data let image_ptr = image.upload(arena); - let value = image.clone(); + let value = ℑ let mut data: Vec = (0..n_v) .flat_map(|v| { (0..n_u).map(move |u| { - &value + value .get_channels(Point2i::new(u as i32, v as i32)) .average() }) diff --git a/src/lights/point.rs b/src/lights/point.rs index f94a422..2f73c3b 100644 --- a/src/lights/point.rs +++ b/src/lights/point.rs @@ -36,7 +36,7 @@ impl CreatePointLight for PointLight { medium_interface, ); let iemit = lookup_spectrum(&le); - let i = Ptr::from(iemit.device()); + let i = Ptr::from(&iemit.device()); Self { base, scale, i } } diff --git a/src/lights/projection.rs b/src/lights/projection.rs index cb94bf5..e19cef2 100644 --- a/src/lights/projection.rs +++ b/src/lights/projection.rs @@ -17,7 +17,7 @@ use shared::core::spectrum::Spectrum; use shared::lights::ProjectionLight; use shared::spectra::RGBColorSpace; use shared::utils::math::{radians, square}; -use shared::utils::sampling::DeviceWindowedPiecewiseConstant2D; +use shared::utils::sampling::DeviceiecewiseConstant2D; use shared::utils::{Ptr, Transform}; use std::path::Path; @@ -28,7 +28,6 @@ pub trait CreateProjectionLight { scale: Float, image: Ptr, image_color_space: Ptr, - distrib: Ptr, fov: Float, ) -> Self; } @@ -40,7 +39,6 @@ impl CreateProjectionLight for ProjectionLight { scale: Float, image: Ptr, image_color_space: Ptr, - distrib: Ptr, fov: Float, ) -> Self { let base = LightBase::new( @@ -49,20 +47,39 @@ impl CreateProjectionLight for ProjectionLight { medium_interface, ); - let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap(); - let light_from_screen = screen_from_light.inverse(); - let hither = 1e-3; + let opposite = (radians(fov) / 2.0).tan(); let res = image.resolution(); let aspect = res.x() as Float / res.y() as Float; - let opposite = (radians(fov) / 2.0).tan(); let aspect_ratio = if aspect > 1.0 { aspect } else { 1.0 / aspect }; let a = 4.0 * square(opposite) * aspect_ratio; + let screen_bounds = if aspect > 1.0 { + Bounds2f::from_points(Point2f::new(-aspect, -1.0), Point2f::new(aspect, 1.0)) + } else { + Bounds2f::from_points( + Point2f::new(-1.0, -1.0 / aspect), + Point2f::new(1.0, 1.0 / aspect), + ) + }; + + let hither = 1e-3; + let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap(); + let light_from_screen = screen_from_light.inverse(); + + let dwda = |p: Point2f| { + let w = + Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.0))); + cos_theta(w.normalize()).powi(3) + }; + + let d = image.get_sampling_distribution(dwda, screen_bounds); + let distrib = + PiecewiseConstant2D::new(d.as_slice(), d.x_size() as usize, d.y_size() as usize); Self { base, image, image_color_space, - distrib, + distrib: Ptr::from(&distrib.device), screen_bounds, screen_from_light, light_from_screen, @@ -131,38 +148,12 @@ impl CreateLight for ProjectionLight { let flip = Transform::scale(1., -1., 1.); let render_from_light_flip = render_from_light * flip; - let hither = 1e-3; - let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap(); - let light_from_screen = screen_from_light.inverse(); - - let dwda = |p: Point2f| { - let w = - Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.0))); - cos_theta(w.normalize()).powi(3) - }; - - let res = image.resolution(); - let aspect = res.x() as Float / res.y() as Float; - let screen_bounds = if aspect > 1.0 { - Bounds2f::from_points(Point2f::new(-aspect, -1.0), Point2f::new(aspect, 1.0)) - } else { - Bounds2f::from_points( - Point2f::new(-1.0, -1.0 / aspect), - Point2f::new(1.0, 1.0 / aspect), - ) - }; - - let d = image.get_sampling_distribution(dwda, screen_bounds); - let distrib = - PiecewiseConstant2D::new(d.as_slice(), d.x_size() as usize, d.y_size() as usize); - let specific = ProjectionLight::new( render_from_light_flip, medium.into(), scale, image.upload(arena), colorspace.upload(arena), - distrib.upload(arena), fov, ); diff --git a/src/materials/measured.rs b/src/materials/measured.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/samplers/halton.rs b/src/samplers/halton.rs index f6b1503..968f3e4 100644 --- a/src/samplers/halton.rs +++ b/src/samplers/halton.rs @@ -1,12 +1,9 @@ -use crate::Arena; -use crate::core::sampler::CreateSampler; +use super::*; use crate::utils::math::compute_radical_inverse_permutations; -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, MAX_HALTON_RESOLUTION, RandomizeStrategy}; -use shared::utils::Ptr; pub trait CreateHaltonSampler { fn new( @@ -72,7 +69,7 @@ impl CreateSampler for HaltonSampler { full_res: Point2i, loc: &FileLoc, _arena: &mut Arena, - ) -> Result { + ) -> Result { let options = get_options(); let nsamp = options .quick_render @@ -98,6 +95,6 @@ impl CreateSampler for HaltonSampler { let sampler = HaltonSampler::new(nsamp, full_res, s, seed as u64); // arena.alloc(sampler); - Ok(sampler) + Ok(Sampler::Halton(sampler)) } } diff --git a/src/samplers/independent.rs b/src/samplers/independent.rs index 27e0c3d..27333a7 100644 --- a/src/samplers/independent.rs +++ b/src/samplers/independent.rs @@ -1,8 +1,4 @@ -use crate::Arena; -use crate::core::sampler::CreateSampler; -use crate::utils::{FileLoc, ParameterDictionary}; -use anyhow::Result; -use shared::core::geometry::Point2i; +use super::*; use shared::core::options::get_options; use shared::core::sampler::IndependentSampler; @@ -12,7 +8,7 @@ impl CreateSampler for IndependentSampler { _full_res: Point2i, _loc: &FileLoc, arena: &mut Arena, - ) -> Result { + ) -> Result { let options = get_options(); let nsamp = options .quick_render @@ -22,6 +18,6 @@ impl CreateSampler for IndependentSampler { let seed = params.get_one_int("seed", options.seed); let sampler = Self::new(nsamp, seed as u64); arena.alloc(sampler); - Ok(sampler) + Ok(Sampler::Independent(sampler)) } } diff --git a/src/samplers/mod.rs b/src/samplers/mod.rs index 7a7b1f7..750b786 100644 --- a/src/samplers/mod.rs +++ b/src/samplers/mod.rs @@ -2,3 +2,17 @@ pub mod halton; pub mod independent; pub mod sobol; pub mod stratified; + +use crate::{Arena, FileLoc, ParameterDictionary}; +use anyhow::Result; +use shared::core::geometry::Point2i; +use shared::core::sampler::Sampler; + +pub trait CreateSampler { + fn create( + params: &ParameterDictionary, + full_res: Point2i, + loc: &FileLoc, + arena: &mut Arena, + ) -> Result; +} diff --git a/src/samplers/sobol.rs b/src/samplers/sobol.rs index e469e73..df1aa19 100644 --- a/src/samplers/sobol.rs +++ b/src/samplers/sobol.rs @@ -1,7 +1,4 @@ -use crate::Arena; -use crate::core::sampler::CreateSampler; -use crate::utils::{FileLoc, ParameterDictionary}; -use anyhow::{Result, anyhow}; +use super::*; use shared::core::geometry::Point2i; use shared::core::options::get_options; use shared::core::sampler::{PaddedSobolSampler, RandomizeStrategy, SobolSampler, ZSobolSampler}; @@ -12,7 +9,7 @@ impl CreateSampler for SobolSampler { full_res: Point2i, loc: &FileLoc, _arena: &mut Arena, - ) -> Result { + ) -> Result { let options = get_options(); let nsamp = options .quick_render @@ -32,7 +29,7 @@ impl CreateSampler for SobolSampler { let sampler = Self::new(nsamp, full_res, s, Some(seed as u64)); // arena.alloc(sampler); - Ok(sampler) + Ok(Sampler::Sobol(sampler)) } } @@ -42,7 +39,7 @@ impl CreateSampler for PaddedSobolSampler { _full_res: Point2i, loc: &FileLoc, arena: &mut Arena, - ) -> Result { + ) -> Result { let options = get_options(); let nsamp = options .quick_render @@ -64,8 +61,8 @@ impl CreateSampler for PaddedSobolSampler { }; let sampler = Self::new(nsamp, s, Some(seed as u64)); - arena.alloc(sampler); - Ok(sampler) + // arena.alloc(sampler); + Ok(Sampler::PaddedSobol(sampler)) } } @@ -75,7 +72,7 @@ impl CreateSampler for ZSobolSampler { full_res: Point2i, loc: &FileLoc, arena: &mut Arena, - ) -> Result { + ) -> Result { let options = get_options(); let nsamp = options .quick_render @@ -97,7 +94,7 @@ impl CreateSampler for ZSobolSampler { }; let sampler = ZSobolSampler::new(nsamp, full_res, s, Some(seed as u64)); - arena.alloc(sampler); - Ok(sampler) + // arena.alloc(sampler); + Ok(Sampler::ZSobol(sampler)) } } diff --git a/src/samplers/stratified.rs b/src/samplers/stratified.rs index 1506b7e..cb36099 100644 --- a/src/samplers/stratified.rs +++ b/src/samplers/stratified.rs @@ -1,8 +1,4 @@ -use crate::Arena; -use crate::core::sampler::CreateSampler; -use crate::utils::{FileLoc, ParameterDictionary}; -use anyhow::Result; -use shared::core::geometry::Point2i; +use super::*; use shared::core::options::get_options; use shared::core::sampler::StratifiedSampler; @@ -12,7 +8,7 @@ impl CreateSampler for StratifiedSampler { _full_res: Point2i, _loc: &FileLoc, _arena: &mut Arena, - ) -> Result { + ) -> Result { let options = get_options(); let jitter = params.get_one_bool("jitter", true); let (x_samples, y_samples) = if options.quick_render { @@ -32,6 +28,6 @@ impl CreateSampler for StratifiedSampler { let sampler = Self::new(x_samples, y_samples, Some(seed as u64), jitter); // arena.aloc(sampler); - Ok(sampler) + Ok(Sampler::Stratified(sampler)) } } diff --git a/src/spectra/dense.rs b/src/spectra/dense.rs index 388688b..cfd54c4 100644 --- a/src/spectra/dense.rs +++ b/src/spectra/dense.rs @@ -7,13 +7,15 @@ use shared::spectra::{ use shared::utils::math::square; use std::hash::{Hash, Hasher}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct DenselySampledSpectrumBuffer { pub lambda_min: i32, pub lambda_max: i32, pub values: Vec, } +impl Eq for DenselySampledSpectrumBuffer {} + impl Hash for DenselySampledSpectrumBuffer { fn hash(&self, state: &mut H) { self.lambda_min.hash(state); diff --git a/src/spectra/mod.rs b/src/spectra/mod.rs index 1998d59..d913abf 100644 --- a/src/spectra/mod.rs +++ b/src/spectra/mod.rs @@ -1,5 +1,6 @@ use crate::globals::{ACES_TABLE, DCI_P3_TABLE, REC2020_TABLE, SRGB_TABLE}; use crate::spectra::colorspace::RGBColorSpaceData; +use anyhow::{Result, anyhow}; use shared::core::geometry::Point2f; use shared::core::spectrum::Spectrum; use shared::core::spectrum::StandardSpectra; @@ -105,6 +106,18 @@ pub struct StandardColorSpaces { pub aces2065_1: Arc, } +impl StandardColorSpaces { + pub fn get_named(&self, name: &str) -> Result> { + match name.to_lowercase().as_str() { + "srgb" => Ok(self.srgb.clone()), + "dci-p3" => Ok(self.dci_p3.clone()), + "rec2020" => Ok(self.rec2020.clone()), + "aces2065-1" => Ok(self.aces2065_1.clone()), + _ => Err(anyhow!("No such spectrum")), + } + } +} + pub fn get_colorspace_context() -> StandardColorSpaces { StandardColorSpaces { srgb: SRGB.clone().into(), diff --git a/src/textures/constant.rs b/src/textures/constant.rs index d3b14f1..389096f 100644 --- a/src/textures/constant.rs +++ b/src/textures/constant.rs @@ -1,5 +1,5 @@ -use anyhow::Result; use crate::Arena; +use anyhow::Result; use shared::{ core::texture::{SpectrumType, TextureEvalContext}, textures::{FloatConstantTexture, SpectrumConstantTexture}, @@ -9,7 +9,7 @@ use shared::{ use crate::{ core::texture::{ CreateFloatTexture, CreateSpectrumTexture, FloatTexture, FloatTextureTrait, - SpectrumTextureTrait, SpectrumTextureTrait, SpectrumTexture + SpectrumTexture, SpectrumTextureTrait, }, utils::{FileLoc, TextureParameterDictionary}, }; diff --git a/src/utils/arena.rs b/src/utils/arena.rs index ad5886c..dd5b667 100644 --- a/src/utils/arena.rs +++ b/src/utils/arena.rs @@ -6,6 +6,8 @@ use crate::utils::mipmap::MIPMap; use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D}; use shared::core::color::RGBToSpectrumTable; use shared::core::image::DeviceImage; +use shared::core::light::Light; +use shared::core::material::Material; use shared::core::shape::Shape; use shared::core::spectrum::Spectrum; use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture}; @@ -155,6 +157,13 @@ impl Upload for Shape { } } +impl Upload for Light { + type Target = Light; + fn upload(&self, arena: &mut Arena) -> Ptr { + arena.alloc(self.clone()) + } +} + impl Upload for Image { type Target = DeviceImage; fn upload(&self, arena: &mut Arena) -> Ptr { @@ -169,6 +178,13 @@ impl Upload for Spectrum { } } +impl Upload for Material { + type Target = Material; + fn upload(&self, arena: &mut Arena) -> Ptr { + arena.alloc(self.clone()) + } +} + impl Upload for DenselySampledSpectrumBuffer { type Target = DenselySampledSpectrum; fn upload(&self, arena: &mut Arena) -> Ptr { diff --git a/src/utils/math.rs b/src/utils/math.rs index 5dd318e..a0ca81e 100644 --- a/src/utils/math.rs +++ b/src/utils/math.rs @@ -46,7 +46,7 @@ impl DigitPermutation { } } -pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec, Vec) { +pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec, Vec) { let temp_data: Vec> = PRIMES .iter() .map(|&base| DigitPermutation::new(base as i32, seed).permutations) @@ -68,11 +68,13 @@ pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec, Vec { receiver: Receiver, } diff --git a/src/utils/parameters.rs b/src/utils/parameters.rs index f895592..d29914d 100644 --- a/src/utils/parameters.rs +++ b/src/utils/parameters.rs @@ -234,12 +234,12 @@ impl PBRTParameter for String { } } -#[derive(Default)] +#[derive(Default, Clone)] pub struct NamedTextures { - pub float_textures: HashMap>, - pub albedo_spectrum_textures: HashMap>, - pub unbounded_spectrum_textures: HashMap>, - pub illuminant_spectrum_textures: HashMap>, + pub float_textures: Arc>>, + pub albedo_spectrum_textures: Arc>>, + pub unbounded_spectrum_textures: Arc>>, + pub illuminant_spectrum_textures: Arc>>, } #[derive(Debug, Default, Clone)] @@ -704,11 +704,11 @@ fn read_spectrum_from_file(filename: &str) -> Result { pub struct TextureParameterDictionary { dict: Arc, - textures: Option, + textures: Option<&NamedTextures>, } impl TextureParameterDictionary { - pub fn new(dict: Arc, textures: Option) -> Self { + pub fn new(dict: Arc, textures: Option<&NamedTextures>) -> Self { Self { dict, textures } }