Continuing cleanup of scene creation, texture ownership, and memory management

This commit is contained in:
pingu 2026-01-25 18:15:48 +00:00
parent 64a139d108
commit 640e17110a
44 changed files with 464 additions and 467 deletions

View file

@ -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 } cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
half = "2.7.1" half = "2.7.1"
rand = "0.9.2" rand = "0.9.2"
anyhow = "1.0.100"
[features] [features]
use_f64 = [] use_f64 = []

View file

@ -96,7 +96,7 @@ impl RGBFilm {
); );
} }
} }
unsafe { &*self.base.sensor } &*self.base.sensor
} }
pub fn add_sample( pub fn add_sample(
@ -241,7 +241,7 @@ impl GBufferFilm {
); );
} }
} }
unsafe { &*self.base.sensor } &*self.base.sensor
} }
pub fn add_sample( pub fn add_sample(

View file

@ -501,7 +501,7 @@ pub struct GridMedium {
pub sigma_s_spec: DenselySampledSpectrum, pub sigma_s_spec: DenselySampledSpectrum,
pub density_grid: SampledGrid<Float>, pub density_grid: SampledGrid<Float>,
pub phase: HGPhaseFunction, pub phase: HGPhaseFunction,
pub temperature_grid: SampledGrid<Float>, pub temperature_grid: Option<SampledGrid<Float>>,
pub le_spec: DenselySampledSpectrum, pub le_spec: DenselySampledSpectrum,
pub le_scale: SampledGrid<Float>, pub le_scale: SampledGrid<Float>,
pub is_emissive: bool, pub is_emissive: bool,
@ -528,8 +528,8 @@ impl MediumTrait for GridMedium {
}; };
let le = if scale > 0.0 { let le = if scale > 0.0 {
let raw_emission = if self.temperature_grid.is_valid() { let raw_emission = if let Some(temp_grid) = &self.temperature_grid {
let temp = self.temperature_grid.lookup(p); let temp = temp_grid.lookup(p);
BlackbodySpectrum::new(temp).sample(lambda) BlackbodySpectrum::new(temp).sample(lambda)
} else { } else {
self.le_spec.sample(lambda) self.le_spec.sample(lambda)

View file

@ -14,7 +14,7 @@ use crate::{Float, PI};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct GoniometricLight { pub struct GoniometricLight {
pub base: LightBase, pub base: LightBase,
pub iemit: DenselySampledSpectrum, pub iemit: Ptr<DenselySampledSpectrum>,
pub scale: Float, pub scale: Float,
pub image: Ptr<DeviceImage>, pub image: Ptr<DeviceImage>,
pub distrib: Ptr<DevicePiecewiseConstant2D>, pub distrib: Ptr<DevicePiecewiseConstant2D>,

View file

@ -4,6 +4,7 @@ use crate::core::pbrt::Float;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum};
use crate::utils::math::SquareMatrix3f; use crate::utils::math::SquareMatrix3f;
use crate::utils::ptr::Ptr; use crate::utils::ptr::Ptr;
use anyhow::{Result, anyhow};
use std::cmp::{Eq, PartialEq}; use std::cmp::{Eq, PartialEq};
@ -18,13 +19,13 @@ pub struct DeviceStandardColorSpaces {
impl DeviceStandardColorSpaces { impl DeviceStandardColorSpaces {
#[cfg(not(target_arch = "nvptx64"))] #[cfg(not(target_arch = "nvptx64"))]
pub fn get_named(&self, name: &str) -> Option<Ptr<RGBColorSpace>> { pub fn get_named(&self, name: &str) -> Result<Ptr<RGBColorSpace>> {
match name.to_lowercase().as_str() { match name.to_lowercase().as_str() {
"srgb" => Some(self.srgb), "srgb" => Ok(self.srgb),
"dci-p3" => Some(self.dci_p3), "dci-p3" => Ok(self.dci_p3),
"rec2020" => Some(self.rec2020), "rec2020" => Ok(self.rec2020),
"aces2065-1" => Some(self.aces2065_1), "aces2065-1" => Ok(self.aces2065_1),
_ => None, _ => Err(anyhow!("No such spectrum")),
} }
} }

View file

@ -392,7 +392,6 @@ impl FilmBaseHost for FilmBase {
} }
} }
#[enum_dispatch]
pub trait FilmTrait: Sync { pub trait FilmTrait: Sync {
fn base(&self) -> &FilmBase; fn base(&self) -> &FilmBase;
fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB; fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB;
@ -403,6 +402,7 @@ pub trait FilmTrait: Sync {
.write(self.get_filename(), metadata) .write(self.get_filename(), metadata)
.expect("Something") .expect("Something")
} }
fn get_image(&self, _metadata: &ImageMetadata, splat_scale: Float) -> Image { fn get_image(&self, _metadata: &ImageMetadata, splat_scale: Float) -> Image {
let write_fp16 = true; let write_fp16 = true;
let format = if write_fp16 { let format = if write_fp16 {

View file

@ -8,7 +8,7 @@ use image_rs::{DynamicImage, ImageReader};
use shared::Float; use shared::Float;
use shared::core::color::{ColorEncoding, LINEAR}; use shared::core::color::{ColorEncoding, LINEAR};
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
use shared::core::image::PixelFormat; use shared::core::image::{DeviceImage, ImageBase, PixelFormat};
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Read, Write}; use std::io::{BufRead, BufReader, BufWriter, Read, Write};
use std::path::Path; use std::path::Path;
@ -343,14 +343,8 @@ fn read_pfm(path: &Path) -> Result<ImageAndMetadata> {
vec!["R".into(), "G".into(), "B".into()] vec!["R".into(), "G".into(), "B".into()]
}; };
let image = Image { let image = Image::new(PixelFormat::F32, Point2i::new(w, h), names, LINEAR);
format: PixelFormat::F32,
resolution: Point2i::new(w, h),
channel_names: names,
encoding: LINEAR,
pixels: PixelStorage::F32(pixels),
};
let metadata = ImageMetadata::default(); let metadata = ImageMetadata::default();
Ok(ImageAndMetadata { image, metadata }) Ok(ImageAndMetadata { image, metadata })
} }

View file

@ -35,7 +35,7 @@ impl ImageChannelDesc {
} }
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Clone)]
pub struct ImageMetadata { pub struct ImageMetadata {
pub render_time_seconds: Option<Float>, pub render_time_seconds: Option<Float>,
pub camera_from_world: Option<SquareMatrix<Float, 4>>, pub camera_from_world: Option<SquareMatrix<Float, 4>>,

View file

@ -1,9 +1,9 @@
use crate::utils::containers::Array2D; use crate::utils::containers::Array2D;
use anyhow::Result; use anyhow::{Result, anyhow};
use half::f16; use half::f16;
use rayon::prelude::ParallelIterator;
use shared::Float; use shared::Float;
use shared::core::color::ColorEncoding; use shared::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
use shared::core::color::LINEAR;
use shared::core::geometry::{Bounds2f, Point2f, Point2i}; use shared::core::geometry::{Bounds2f, Point2f, Point2i};
use shared::core::image::{DeviceImage, ImageBase, PixelFormat, Pixels, WrapMode, WrapMode2D}; use shared::core::image::{DeviceImage, ImageBase, PixelFormat, Pixels, WrapMode, WrapMode2D};
use shared::utils::math::square; use shared::utils::math::square;
@ -77,9 +77,9 @@ pub enum PixelStorage {
impl PixelStorage { impl PixelStorage {
pub fn as_pixels(&self) -> Pixels { pub fn as_pixels(&self) -> Pixels {
match self { match self {
PixelStorage::U8(data) => Pixels::U8(data.as_ptr()), PixelStorage::U8(data) => Pixels::U8(data.as_ptr().into()),
PixelStorage::F16(data) => Pixels::F16(data.as_ptr() as *const u16), PixelStorage::F16(data) => Pixels::F16((data.as_ptr() as *const u16).into()),
PixelStorage::F32(data) => Pixels::F32(data.as_ptr()), PixelStorage::F32(data) => Pixels::F32(data.as_ptr().into()),
} }
} }
@ -126,7 +126,7 @@ impl Image {
fn from_storage( fn from_storage(
storage: PixelStorage, storage: PixelStorage,
resolution: Point2i, resolution: Point2i,
channel_names: &[&str], channel_names: &[impl AsRef<str>],
encoding: ColorEncoding, encoding: ColorEncoding,
) -> Self { ) -> Self {
let n_channels = channel_names.len() as i32; let n_channels = channel_names.len() as i32;
@ -145,7 +145,7 @@ impl Image {
Self { Self {
storage, storage,
channel_names, channel_names: String::from(channel_names),
device, device,
} }
} }
@ -153,7 +153,7 @@ impl Image {
pub fn from_u8( pub fn from_u8(
data: Vec<u8>, data: Vec<u8>,
resolution: Point2i, resolution: Point2i,
channel_names: &[&str], channel_names: &[impl AsRef<str>],
encoding: ColorEncoding, encoding: ColorEncoding,
) -> Self { ) -> Self {
Self::from_storage( Self::from_storage(
@ -167,7 +167,7 @@ impl Image {
pub fn from_u8( pub fn from_u8(
data: Vec<u8>, data: Vec<u8>,
resolution: Point2i, resolution: Point2i,
channel_names: &[&str], channel_names: &[impl AsRef<str>],
encoding: ColorEncoding, encoding: ColorEncoding,
) -> Self { ) -> Self {
Self::from_storage( Self::from_storage(
@ -178,44 +178,56 @@ impl Image {
) )
} }
pub fn from_f16(data: Vec<half::f16>, resolution: Point2i, channel_names: &[&str]) -> Self { pub fn from_f16(
data: Vec<half::f16>,
resolution: Point2i,
channel_names: &[impl AsRef<str>],
) -> Self {
Self::from_storage( Self::from_storage(
PixelStorage::F16(data.into_boxed_slice()), PixelStorage::F16(data.into_boxed_slice()),
resolution, resolution,
channel_names, channel_names,
ColorEncoding::Linear, LINEAR,
) )
} }
pub fn from_f32(data: Vec<f32>, resolution: Point2i, channel_names: &[&str]) -> Self { pub fn from_f32(
data: Vec<f32>,
resolution: Point2i,
channel_names: &[impl AsRef<str>],
) -> Self {
Self::from_storage( Self::from_storage(
PixelStorage::F32(data.into_boxed_slice()), PixelStorage::F32(data.into_boxed_slice()),
resolution, resolution,
channel_names, channel_names,
ColorEncoding::Linear, LINEAR,
) )
} }
pub fn new( pub fn new(
format: PixelFormat, format: PixelFormat,
resolution: Point2i, resolution: Point2i,
channel_names: &[&str], channel_names: &[impl AsRef<str>],
encoding: Arc<ColorEncoding>, encoding: Arc<ColorEncoding>,
) -> Self { ) -> Self {
let n_channels = channel_names.len(); let n_channels = channel_names.len();
let pixel_count = (resolution.x * resolution.y) as usize * n_channels; let pixel_count = (resolution.x() * resolution.y()) as usize * n_channels;
let owned_names: Vec<String> = channel_names.iter().map(|s| s.to_string()).collect(); let owned_names: Vec<String> = channel_names.iter().map(|s| s.to_string()).collect();
let storage = match format { let storage = match format {
PixelFormat::U8 => PixelStorage::U8(vec![0; pixel_count]), PixelFormat::U8 => PixelStorage::U8(vec![0; pixel_count].into()),
PixelFormat::F16 => PixelStorage::F16(vec![0; pixel_count]), PixelFormat::F16 => PixelStorage::F16(vec![0; pixel_count].into()),
PixelFormat::F32 => PixelStorage::F32(vec![0.0; pixel_count]), PixelFormat::F32 => PixelStorage::F32(vec![0.0; pixel_count].into()),
}; };
Self::from_storage(storage, resolution, owned_names, encoding) Self::from_storage(storage, resolution, owned_names, encoding)
} }
pub fn new_constant(resolution: Point2i, channel_names: &[&str], values: &[f32]) -> Self { pub fn new_constant(
resolution: Point2i,
channel_names: &[impl AsRef<str>],
values: &[f32],
) -> Self {
let n_channels = channel_names.len(); let n_channels = channel_names.len();
if values.len() != n_channels { if values.len() != n_channels {
panic!( 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); let mut data = Vec::with_capacity(n_pixels * n_channels);
@ -248,11 +260,11 @@ impl Image {
} }
fn n_channels(&self) -> i32 { fn n_channels(&self) -> i32 {
self.base.n_channels self.base().n_channels
} }
pub fn format(&self) -> PixelFormat { pub fn format(&self) -> PixelFormat {
self.device.base.format self.base().format
} }
pub fn channel_names(&self) -> Vec<&str> { pub fn channel_names(&self) -> Vec<&str> {
@ -260,7 +272,7 @@ impl Image {
} }
pub fn encoding(&self) -> ColorEncoding { pub fn encoding(&self) -> ColorEncoding {
self.view.encoding self.base().encoding
} }
fn pixel_offset(&self, p: Point2i) -> usize { fn pixel_offset(&self, p: Point2i) -> usize {
@ -399,7 +411,10 @@ impl Image {
.collect() .collect()
} }
pub fn get_channel_desc(&self, requested_channels: &[&str]) -> Result<ImageChannelDesc> { pub fn get_channel_desc(
&self,
requested_channels: &[impl AsRef<str>],
) -> Result<ImageChannelDesc> {
let mut offset = Vec::with_capacity(requested_channels.len()); let mut offset = Vec::with_capacity(requested_channels.len());
for &req in requested_channels.iter() { for &req in requested_channels.iter() {
@ -408,9 +423,10 @@ impl Image {
offset.push(idx); offset.push(idx);
} }
None => { None => {
return Err(format!( return Err(anyhow!(
"Missing channel '{}'. Available: {:?}", "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<F>(&self, dxd_a: F, domain: Bounds2f) -> Array2D<Float> pub fn get_sampling_distribution<F>(&self, dxd_a: F, domain: Bounds2f) -> Array2D<Float>
@ -477,7 +493,7 @@ impl Image {
let width = self.resolution().x(); let width = self.resolution().x();
let height = self.resolution().y(); let height = self.resolution().y();
let mut dist = Array2D::new_dims(width, height); let mut dist: Array2D<Float> = Array2D::new_dims(width, height);
dist.values dist.values
.par_chunks_mut(width as usize) .par_chunks_mut(width as usize)
@ -488,7 +504,7 @@ impl Image {
for (x, out_val) in row.iter_mut().enumerate() { for (x, out_val) in row.iter_mut().enumerate() {
let x = x as i32; 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 u = (x as Float + 0.5) / width as Float;
let v = (y as Float + 0.5) / height as Float; let v = (y as Float + 0.5) / height as Float;
@ -512,15 +528,17 @@ impl Image {
ref_img: &Image, ref_img: &Image,
generate_mse_image: bool, generate_mse_image: bool,
) -> (ImageChannelValues, Option<Image>) { ) -> (ImageChannelValues, Option<Image>) {
let res = self.resolution();
let mut sum_se: Vec<f64> = vec![0.; desc.size()]; let mut sum_se: Vec<f64> = vec![0.; desc.size()];
let names_ref = self.channel_names_from_desc(&desc); let names_ref = self.channel_names_from_desc(&desc);
let ref_desc = ref_img let ref_desc = ref_img
.get_channel_desc(&self.channel_names_from_desc(&desc)) .get_channel_desc(&self.channel_names_from_desc(&desc))
.expect("Channels not found in image"); .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 width = res.x() as usize;
let height = self.resolution.y() as usize; let height = res.y() as usize;
let n_channels = desc.offset.len(); let n_channels = desc.offset.len();
let mut mse_pixels = if generate_mse_image { let mut mse_pixels = if generate_mse_image {
vec![0.0f32; width * height * n_channels] vec![0.0f32; width * height * n_channels]
@ -528,11 +546,11 @@ impl Image {
Vec::new() Vec::new()
}; };
for y in 0..self.resolution().y() { for y in 0..res.y() {
for x in 0..self.resolution().x() { for x in 0..res.x() {
let v = let v =
self.get_channel_with_desc(Point2i::new(x, y), &desc, WrapMode::Clamp.into()); self.get_channels_with_desc(Point2i::new(x, y), &desc, WrapMode::Clamp.into());
let v_ref = self.get_channel_with_desc( let v_ref = self.get_channels_with_desc(
Point2i::new(x, y), Point2i::new(x, y),
&ref_desc, &ref_desc,
WrapMode::Clamp.into(), 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]> = let mse_values: SmallVec<[Float; 4]> =
sum_se.iter().map(|&s| (s / pixel_count) as Float).collect(); sum_se.iter().map(|&s| (s / pixel_count) as Float).collect();
let mse_image = if generate_mse_image { let mse_image = if generate_mse_image {
Some(Image::new( Some(Image::new(PixelFormat::F32, res, &names_ref, LINEAR.into()))
PixelFormat::F32,
self.resolution,
&names_ref,
LINEAR,
))
} else { } else {
None None
}; };
@ -570,15 +583,15 @@ impl Image {
} }
pub fn update_view_pointers(&mut self) { pub fn update_view_pointers(&mut self) {
self.view.pixels = match &self._storage { self.device.pixels = match &self.storage {
PixelStorage::U8(vec) => Pixels::U8(vec.as_ptr()), PixelStorage::U8(vec) => Pixels::U8(vec.as_ptr().into()),
PixelStorage::F16(vec) => Pixels::F16(vec.as_ptr() as *const u16), PixelStorage::F16(vec) => Pixels::F16((vec.as_ptr() as *const u16).into()),
PixelStorage::F32(vec) => Pixels::F32(vec.as_ptr()), PixelStorage::F32(vec) => Pixels::F32(vec.as_ptr().into()),
}; };
} }
pub fn has_any_infinite_pixels(&self) -> bool { pub fn has_any_infinite_pixels(&self) -> bool {
if self.format() == PixelFormat::Float { if self.format() == PixelFormat::F32 {
return false; return false;
} }
@ -595,7 +608,7 @@ impl Image {
} }
pub fn has_any_nan_pixels(&self) -> bool { pub fn has_any_nan_pixels(&self) -> bool {
if self.format() == PixelFormat::Float { if self.format() == PixelFormat::F32 {
return false; return false;
} }

View file

@ -4,7 +4,8 @@ use rayon::prelude::*;
use shared::Float; use shared::Float;
use shared::core::color::ColorEncoding; use shared::core::color::ColorEncoding;
use shared::core::geometry::{Bounds2i, Point2i}; 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 shared::utils::math::windowed_sinc;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -16,17 +17,20 @@ pub struct ResampleWeight {
impl Image { impl Image {
pub fn flip_y(&mut self) { pub fn flip_y(&mut self) {
let res = self.resolution; let res = self.resolution();
let nc = self.n_channels(); let nc = self.n_channels();
match &mut self.pixels { match &mut self.pixels {
PixelStorage::U8(d) => flip_y_kernel(d, res, nc), Pixels::U8(d) => flip_y_kernel(d, res, nc),
PixelStorage::F16(d) => flip_y_kernel(d, res, nc), Pixels::F16(d) => flip_y_kernel(d, res, nc),
PixelStorage::F32(d) => flip_y_kernel(d, res, nc), Pixels::F32(d) => flip_y_kernel(d, res, nc),
} }
} }
pub fn crop(&self, bounds: Bounds2i) -> Image { pub fn crop(&self, bounds: Bounds2i) -> Image {
let res = self.resolution();
let n_channels = self.n_channels();
let new_res = Point2i::new( let new_res = Point2i::new(
bounds.p_max.x() - bounds.p_min.x(), bounds.p_max.x() - bounds.p_min.x(),
bounds.p_max.y() - bounds.p_min.y(), bounds.p_max.y() - bounds.p_min.y(),
@ -36,19 +40,13 @@ impl Image {
self.format, self.format,
new_res, new_res,
self.channel_names.clone(), self.channel_names.clone(),
self.encoding, self.encoding(),
); );
match (&self.pixels, &mut new_image.pixels) { match (&self.pixels, &mut new_image.pixels) {
(PixelStorage::U8(src), PixelStorage::U8(dst)) => { (Pixels::U8(src), Pixels::U8(dst)) => crop_kernel(src, dst, res, bounds, n_channels),
crop_kernel(src, dst, self.resolution, bounds, self.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),
(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())
}
_ => panic!("Format mismatch in crop"), _ => panic!("Format mismatch in crop"),
} }
@ -57,34 +55,29 @@ impl Image {
pub fn copy_rect_out(&self, extent: Bounds2i, buf: &mut [Float], wrap: WrapMode2D) { pub fn copy_rect_out(&self, extent: Bounds2i, buf: &mut [Float], wrap: WrapMode2D) {
match &self.pixels { match &self.pixels {
PixelStorage::U8(d) => copy_rect_out_kernel(d, self, extent, buf, wrap), Pixels::U8(d) => copy_rect_out_kernel(d, self, extent, buf, wrap),
PixelStorage::F16(d) => copy_rect_out_kernel(d, self, extent, buf, wrap), Pixels::F16(d) => copy_rect_out_kernel(d, self, extent, buf, wrap),
PixelStorage::F32(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]) { pub fn copy_rect_in(&mut self, extent: Bounds2i, buf: &[Float]) {
let resolution = self.resolution; let res = self.resolution();
let n_channels = self.n_channels(); let n_channels = self.n_channels() as usize;
let encoding = self.encoding; let encoding = self.encoding();
match &mut self.pixels { match &mut self.pixels {
PixelStorage::U8(d) => { Pixels::U8(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf),
copy_rect_in_kernel(d, resolution, 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),
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)
}
} }
} }
pub fn float_resize_up(&self, new_res: Point2i, wrap_mode: WrapMode2D) -> Image { 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!( assert!(
matches!(self.format, PixelFormat::F32), matches!(self.format(), PixelFormat::F32),
"ResizeUp requires Float format" "ResizeUp requires Float format"
); );
@ -92,11 +85,11 @@ impl Image {
PixelFormat::F32, // Force float output PixelFormat::F32, // Force float output
new_res, new_res,
self.channel_names.clone(), self.channel_names.clone(),
self.encoding, self.encoding(),
))); )));
let x_weights = resample_weights(self.resolution.x() as usize, new_res.x() as usize); let x_weights = resample_weights(res.x() as usize, new_res.x() as usize);
let y_weights = resample_weights(self.resolution.y() as usize, new_res.y() as usize); let y_weights = resample_weights(res.y() as usize, new_res.y() as usize);
let n_channels = self.n_channels(); let n_channels = self.n_channels();
let tile_size = 16; let tile_size = 16;
@ -118,7 +111,7 @@ impl Image {
&in_buf, &in_buf,
in_extent, in_extent,
*out_extent, *out_extent,
n_channels, n_channels.try_into().unwrap(),
&x_weights, &x_weights,
&y_weights, &y_weights,
); );
@ -141,23 +134,23 @@ impl Image {
loop { loop {
let prev = levels.last().unwrap(); let prev = levels.last().unwrap();
let old = prev.resolution; let old = prev.resolution();
if old.x() == 1 && old.y() == 1 { if old.x() == 1 && old.y() == 1 {
break; break;
} }
let new_res = Point2i::new((old.x() / 2).max(1), (old.y() / 2).max(1)); let new_res = Point2i::new((old.x() / 2).max(1), (old.y() / 2).max(1));
let mut next = Image::from_vector( let mut next = Image::new(
prev.format, prev.format(),
new_res, new_res,
prev.channel_names.clone(), prev.channel_names.clone(),
prev.encoding, prev.encoding(),
); );
match &mut next.pixels { match &mut next.pixels {
PixelStorage::U8(d) => downsample_kernel(d, new_res, prev, internal_wrap), Pixels::U8(d) => downsample_kernel(d, new_res, prev, internal_wrap),
PixelStorage::F16(d) => downsample_kernel(d, new_res, prev, internal_wrap), Pixels::F16(d) => downsample_kernel(d, new_res, prev, internal_wrap),
PixelStorage::F32(d) => downsample_kernel(d, new_res, prev, internal_wrap), Pixels::F32(d) => downsample_kernel(d, new_res, prev, internal_wrap),
} }
levels.push(next); levels.push(next);
} }
@ -209,9 +202,9 @@ fn copy_rect_out_kernel<T: PixelStorage>(
wrap: WrapMode2D, wrap: WrapMode2D,
) { ) {
let w = (extent.p_max.x() - extent.p_min.x()) as usize; let w = (extent.p_max.x() - extent.p_min.x()) as usize;
let channels = image.n_channels(); let channels = image.n_channels() as usize;
let enc = image.encoding; let enc = image.encoding();
let res = image.resolution; let res = image.resolution();
buf.par_chunks_mut(w * channels) buf.par_chunks_mut(w * channels)
.enumerate() .enumerate()
@ -220,7 +213,6 @@ fn copy_rect_out_kernel<T: PixelStorage>(
for x_rel in 0..w { for x_rel in 0..w {
let x = extent.p_min.x() + x_rel as i32; 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() { if x >= 0 && x < res.x() && y >= 0 && y < res.y() {
let offset = (y as usize * res.x() as usize + x as usize) * channels; let offset = (y as usize * res.x() as usize + x as usize) * channels;
@ -228,7 +220,6 @@ fn copy_rect_out_kernel<T: PixelStorage>(
row_buf[x_rel * channels + c] = T::to_linear(src[offset + c], enc); row_buf[x_rel * channels + c] = T::to_linear(src[offset + c], enc);
} }
} else { } else {
// Slow path: Out of bounds, requires Wrap Mode logic.
// We fall back to get_channel which handles the wrapping math. // We fall back to get_channel which handles the wrapping math.
let p = Point2i::new(x, y); let p = Point2i::new(x, y);
for c in 0..channels { for c in 0..channels {
@ -273,17 +264,17 @@ fn copy_rect_in_kernel<T: PixelStorage>(
} }
fn downsample_kernel<T: PixelStorage>( fn downsample_kernel<T: PixelStorage>(
dst: &mut [T], dst: &mut Ptr<T>,
dst_res: Point2i, dst_res: Point2i,
prev: &Image, prev: &Image,
wrap: WrapMode2D, wrap: WrapMode2D,
) { ) {
let w = dst_res.x() as usize; let w = dst_res.x() as usize;
let channels = prev.n_channels(); let channels = prev.n_channels();
let enc = prev.encoding; let enc = prev.encoding();
let old_res = prev.resolution; let old_res = prev.resolution();
dst.par_chunks_mut(w * channels) dst.par_chunks_mut(w * channels as usize)
.enumerate() .enumerate()
.for_each(|(y, row)| { .for_each(|(y, row)| {
let src_y = y * 2; let src_y = y * 2;
@ -305,7 +296,7 @@ fn downsample_kernel<T: PixelStorage>(
} }
let avg = if count > 0.0 { sum / count } else { 0.0 }; 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);
} }
} }
}); });

View file

@ -1,6 +1,6 @@
use shared::Float;
use shared::core::color::ColorEncoding;
use half::f16; use half::f16;
use shared::Float;
use shared::core::color::{ColorEncoding, ColorEncodingTrait};
// Allows writing generic algorithms that work on any image format. // Allows writing generic algorithms that work on any image format.
pub trait PixelStorage: Copy + Send + Sync + 'static + PartialEq { pub trait PixelStorage: Copy + Send + Sync + 'static + PartialEq {
@ -34,14 +34,14 @@ impl PixelStorage for u8 {
#[inline(always)] #[inline(always)]
fn from_linear(val: Float, enc: ColorEncoding) -> Self { fn from_linear(val: Float, enc: ColorEncoding) -> Self {
let mut out = [0u8]; let mut out = [0u8];
enc.from_linear_slice(&[val], &mut out); enc.from_linear(&[val], &mut out);
out[0] out[0]
} }
#[inline(always)] #[inline(always)]
fn to_linear(self, enc: ColorEncoding) -> Float { fn to_linear(self, enc: ColorEncoding) -> Float {
let mut out = [0.0]; let mut out = [0.0];
enc.to_linear_slice(&[self], &mut out); enc.to_linear(&[self], &mut out);
out[0] out[0]
} }
} }

View file

@ -1,7 +1,6 @@
use crate::core::spectrum::SPECTRUM_CACHE; use crate::core::spectrum::SPECTRUM_CACHE;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::spectra::DenselySampledSpectrumBuffer; use crate::spectra::DenselySampledSpectrumBuffer;
use crate::utils::containers::InternCache;
use crate::utils::{Arena, FileLoc, ParameterDictionary}; use crate::utils::{Arena, FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use shared::core::camera::CameraTransform; use shared::core::camera::CameraTransform;
@ -12,11 +11,12 @@ use shared::core::spectrum::Spectrum;
use shared::lights::*; use shared::lights::*;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::Transform; use shared::utils::Transform;
use std::sync::Arc;
pub fn lookup_spectrum(s: &Spectrum) -> DenselySampledSpectrumBuffer { pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrumBuffer> {
let cache = SPECTRUM_CACHE.get_or_init(InternCache::new); let cache = &SPECTRUM_CACHE;
let dense_spectrum = DenselySampledSpectrumBuffer::from_spectrum(s); let dense_spectrum = DenselySampledSpectrumBuffer::from_spectrum(s);
cache.lookup(dense_spectrum).as_ref() cache.lookup(dense_spectrum).into()
} }
pub trait CreateLight { pub trait CreateLight {
@ -62,7 +62,6 @@ impl LightFactory for Light {
) -> Result<Self> { ) -> Result<Self> {
match name { match name {
"diffuse" => DiffuseAreaLight::create( "diffuse" => DiffuseAreaLight::create(
name,
arena, arena,
render_from_light, render_from_light,
medium, medium,
@ -70,7 +69,8 @@ impl LightFactory for Light {
loc, loc,
shape, shape,
alpha_tex, alpha_tex,
)?, colorspace,
),
"point" => PointLight::create( "point" => PointLight::create(
arena, arena,
render_from_light, render_from_light,
@ -80,7 +80,7 @@ impl LightFactory for Light {
shape, shape,
alpha_tex, alpha_tex,
colorspace, colorspace,
)?, ),
"spot" => SpotLight::create( "spot" => SpotLight::create(
arena, arena,
render_from_light, render_from_light,
@ -90,7 +90,7 @@ impl LightFactory for Light {
shape, shape,
alpha_tex, alpha_tex,
colorspace, colorspace,
)?, ),
"goniometric" => GoniometricLight::create( "goniometric" => GoniometricLight::create(
arena, arena,
render_from_light, render_from_light,
@ -100,7 +100,7 @@ impl LightFactory for Light {
shape, shape,
alpha_tex, alpha_tex,
colorspace, colorspace,
)?, ),
"projection" => ProjectionLight::create( "projection" => ProjectionLight::create(
arena, arena,
render_from_light, render_from_light,
@ -110,7 +110,7 @@ impl LightFactory for Light {
shape, shape,
alpha_tex, alpha_tex,
colorspace, colorspace,
)?, ),
"distant" => DistantLight::create( "distant" => DistantLight::create(
arena, arena,
render_from_light, render_from_light,
@ -120,17 +120,17 @@ impl LightFactory for Light {
shape, shape,
alpha_tex, alpha_tex,
colorspace, colorspace,
)?, ),
"infinite" => crate::lights::infinite::create( "infinite" => crate::lights::infinite::create(
arena, arena,
render_from_light, render_from_light,
medium, medium.into(),
camera_transform, camera_transform,
parameters, parameters,
colorspace, colorspace,
loc, loc,
)?, ),
_ => Err(anyhow!(loc, "unknown light type: \"{}\"", name)), _ => Err(anyhow!("{}: unknown light type: \"{}\"", loc, name)),
} }
} }
} }

View file

@ -23,7 +23,7 @@ pub trait MaterialFactory {
name: &str, name: &str,
params: &TextureParameterDictionary, params: &TextureParameterDictionary,
normal_map: Option<Arc<Image>>, normal_map: Option<Arc<Image>>,
named_materials: HashMap<String, Material>, named_materials: Arc<HashMap<String, Material>>,
loc: FileLoc, loc: FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Self>; ) -> Result<Self>;
@ -34,7 +34,7 @@ impl MaterialFactory for Material {
name: &str, name: &str,
parameters: &TextureParameterDictionary, parameters: &TextureParameterDictionary,
normal_map: Option<Arc<Image>>, normal_map: Option<Arc<Image>>,
named_materials: HashMap<String, Material>, named_materials: Arc<HashMap<String, Material>>,
loc: FileLoc, loc: FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Material> { ) -> Result<Material> {

View file

@ -2,7 +2,7 @@ use crate::spectra::dense::DenselySampledSpectrumBuffer;
use shared::core::geometry::{Bounds3f, Point3i}; use shared::core::geometry::{Bounds3f, Point3i};
use shared::core::medium::{GridMedium, HGPhaseFunction, HomogeneousMedium, RGBGridMedium}; use shared::core::medium::{GridMedium, HGPhaseFunction, HomogeneousMedium, RGBGridMedium};
use shared::core::spectrum::{Spectrum, SpectrumTrait}; use shared::core::spectrum::{Spectrum, SpectrumTrait};
use shared::spectra::{DenselySampledSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum}; use shared::spectra::{RGBIlluminantSpectrum, RGBUnboundedSpectrum};
use shared::utils::Transform; use shared::utils::Transform;
use shared::utils::containers::SampledGrid; use shared::utils::containers::SampledGrid;
use shared::{Float, core::medium::MajorantGrid}; use shared::{Float, core::medium::MajorantGrid};
@ -89,7 +89,7 @@ pub trait GridMediumCreator {
sigma_scale: Float, sigma_scale: Float,
g: Float, g: Float,
density_grid: SampledGrid<Float>, density_grid: SampledGrid<Float>,
temperature_grid: SampledGrid<Float>, temperature_grid: Option<SampledGrid<Float>>,
le: &Spectrum, le: &Spectrum,
le_scale: SampledGrid<Float>, le_scale: SampledGrid<Float>,
) -> Self; ) -> Self;
@ -105,21 +105,23 @@ impl GridMediumCreator for GridMedium {
sigma_scale: Float, sigma_scale: Float,
g: Float, g: Float,
density_grid: SampledGrid<Float>, density_grid: SampledGrid<Float>,
temperature_grid: SampledGrid<Float>, temperature_grid: Option<SampledGrid<Float>>,
le: &Spectrum, le: &Spectrum,
le_scale: SampledGrid<Float>, le_scale: SampledGrid<Float>,
) -> Self { ) -> Self {
let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(sigma_a); let mut sigma_a_spec = DenselySampledSpectrumBuffer::from_spectrum(sigma_a);
let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(sigma_s); let mut sigma_s_spec = DenselySampledSpectrumBuffer::from_spectrum(sigma_s);
let le_spec = DenselySampledSpectrum::from_spectrum(le);
sigma_a_spec.scale(sigma_scale); sigma_a_spec.scale(sigma_scale);
sigma_s_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 mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
let is_emissive = if temperature_grid.is_some() { let is_emissive = if temperature_grid.is_some() {
true true
} else { } else {
le_spec.max_value() > 0. Spectrum::Dense(le_spec.device()).max_value() > 0.
}; };
for z in 0..majorant_grid.res.z() { for z in 0..majorant_grid.res.z() {
@ -134,12 +136,12 @@ impl GridMediumCreator for GridMedium {
Self { Self {
bounds: *bounds, bounds: *bounds,
render_from_medium: *render_from_medium, render_from_medium: *render_from_medium,
sigma_a_spec, sigma_a_spec: sigma_a_spec.device(),
sigma_s_spec, sigma_s_spec: sigma_s_spec.device(),
density_grid, density_grid,
phase: HGPhaseFunction::new(g), phase: HGPhaseFunction::new(g),
temperature_grid, temperature_grid,
le_spec, le_spec: le_spec.device(),
le_scale, le_scale,
is_emissive, is_emissive,
majorant_grid, majorant_grid,
@ -169,16 +171,16 @@ impl HomogeneousMediumCreator for HomogeneousMedium {
) -> Self { ) -> Self {
let mut sigma_a_spec = DenselySampledSpectrumBuffer::from_spectrum(&sigma_a); let mut sigma_a_spec = DenselySampledSpectrumBuffer::from_spectrum(&sigma_a);
let mut sigma_s_spec = DenselySampledSpectrumBuffer::from_spectrum(&sigma_s); 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_a_spec.scale(sigma_scale);
sigma_s_spec.scale(sigma_scale); sigma_s_spec.scale(sigma_scale);
le_spec.scale(le_scale); le_spec.scale(le_scale);
Self { Self {
sigma_a_spec, sigma_a_spec: sigma_a_spec.device(),
sigma_s_spec, sigma_s_spec: sigma_s_spec.device(),
le_spec, le_spec: le_spec.device(),
phase: HGPhaseFunction::new(g), phase: HGPhaseFunction::new(g),
} }
} }

View file

@ -10,7 +10,6 @@ pub mod material;
pub mod medium; pub mod medium;
pub mod primitive; pub mod primitive;
pub mod sampler; pub mod sampler;
pub mod sampler;
pub mod scene; pub mod scene;
pub mod shape; pub mod shape;
pub mod spectrum; pub mod spectrum;

View file

@ -2,6 +2,7 @@ use shared::core::{
light::Light, light::Light,
material::Material, material::Material,
medium::MediumInterface, medium::MediumInterface,
texture::GPUFloatTexture
primitive::{GeometricPrimitive, SimplePrimitive}, primitive::{GeometricPrimitive, SimplePrimitive},
shape::Shape, shape::Shape,
}; };

View file

@ -1,4 +1,5 @@
use crate::Arena; use crate::Arena;
use crate::samplers::CreateSampler;
use crate::utils::{FileLoc, ParameterDictionary}; use crate::utils::{FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
@ -7,15 +8,6 @@ use shared::core::sampler::{
StratifiedSampler, ZSobolSampler, StratifiedSampler, ZSobolSampler,
}; };
pub trait CreateSampler {
fn create(
params: &ParameterDictionary,
full_res: Point2i,
loc: &FileLoc,
arena: &mut Arena,
) -> Result<Self>;
}
pub trait SamplerFactory { pub trait SamplerFactory {
fn create( fn create(
name: &str, name: &str,
@ -35,30 +27,12 @@ impl SamplerFactory for Sampler {
arena: &mut Arena, arena: &mut Arena,
) -> Result<Self> { ) -> Result<Self> {
match name { match name {
"zsobol" => { "zsobol" => ZSobolSampler::create(params, full_res, loc, arena),
let sampler = ZSobolSampler::create(params, full_res, loc, arena)?; "paddedsobol" => PaddedSobolSampler::create(params, full_res, loc, arena),
Ok(Sampler::ZSobol(sampler)) "halton" => HaltonSampler::create(params, full_res, loc, arena),
} "sobol" => SobolSampler::create(params, full_res, loc, arena),
"paddedsobol" => { "Independent" => IndependentSampler::create(params, full_res, loc, arena),
let sampler = PaddedSobolSampler::create(params, full_res, loc, arena)?; "stratified" => StratifiedSampler::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))
}
_ => Err(anyhow!("Film type '{}' unknown at {}", name, loc)), _ => Err(anyhow!("Film type '{}' unknown at {}", name, loc)),
} }
} }

View file

@ -1,7 +1,9 @@
use super::BasicScene; use super::BasicScene;
use super::entities::*; use super::entities::*;
use crate::spectra::get_colorspace_context;
use crate::utils::error::FileLoc; use crate::utils::error::FileLoc;
use crate::utils::normalize_utf8; use crate::utils::normalize_utf8;
use crate::utils::parameters::error_exit;
use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector}; use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector};
use crate::utils::parser::ParserTarget; use crate::utils::parser::ParserTarget;
use shared::Float; use shared::Float;
@ -186,7 +188,8 @@ impl ParserTarget for BasicSceneBuilder {
} }
fn color_space(&mut self, name: &str, loc: FileLoc) { 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) => { Ok(cs) => {
self.graphics_state.color_space = Some(cs); self.graphics_state.color_space = Some(cs);
} }
@ -536,14 +539,13 @@ impl ParserTarget for BasicSceneBuilder {
); );
if type_name != "float" && type_name != "spectrum" { if type_name != "float" && type_name != "spectrum" {
self.error_exit_deferred( error_exit(
&loc, Some(&loc),
&format!( &format!(
"{}: texture type unknown. Must be \"float\" or \"spectrum\".", "{}: texture type unknown. Must be \"float\" or \"spectrum\".",
tex_name tex_name
), ),
); );
return;
} }
{ {
@ -554,8 +556,7 @@ impl ParserTarget for BasicSceneBuilder {
}; };
if names.contains(&name) { if names.contains(&name) {
self.error_exit_deferred(&loc, &format!("Redefining texture \"{}\".", name)); error_exit(Some(&loc), &format!("Redefining texture \"{}\".", name));
return;
} }
names.insert(name.to_string()); names.insert(name.to_string());
} }
@ -582,9 +583,9 @@ impl ParserTarget for BasicSceneBuilder {
let entity = SceneEntity { let entity = SceneEntity {
name: name.to_string(), name: name.to_string(),
loc, 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) { fn make_named_material(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
todo!() todo!()

View file

@ -11,7 +11,7 @@ use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::utils::arena; use crate::utils::arena;
use crate::utils::arena::Arena; use crate::utils::arena::Arena;
use crate::utils::error::FileLoc; 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::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
use crate::utils::{Upload, resolve_filename}; use crate::utils::{Upload, resolve_filename};
use parking_lot::Mutex; use parking_lot::Mutex;
@ -28,6 +28,7 @@ use shared::core::sampler::Sampler;
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::core::texture::SpectrumType; use shared::core::texture::SpectrumType;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::Ptr;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -101,10 +102,6 @@ impl BasicScene {
} }
} }
// ========================================================================
// Options
// ========================================================================
pub fn set_options( pub fn set_options(
self: &Arc<Self>, self: &Arc<Self>,
filter: SceneEntity, filter: SceneEntity,
@ -113,6 +110,7 @@ impl BasicScene {
sampler: SceneEntity, sampler: SceneEntity,
integ: SceneEntity, integ: SceneEntity,
accel: SceneEntity, accel: SceneEntity,
arena: &mut Arena,
) { ) {
*self.integrator.lock() = Some(integ); *self.integrator.lock() = Some(integ);
*self.accelerator.lock() = Some(accel); *self.accelerator.lock() = Some(accel);
@ -159,7 +157,7 @@ impl BasicScene {
&camera.base.name, &camera.base.name,
&camera.base.parameters, &camera.base.parameters,
&camera.camera_transform, &camera.camera_transform,
medium.unwrap(), *medium.unwrap(),
camera_film, camera_film,
&camera.base.loc, &camera.base.loc,
arena, arena,
@ -169,10 +167,6 @@ impl BasicScene {
self.camera_state.lock().job = Some(camera_job); self.camera_state.lock().job = Some(camera_job);
} }
// ========================================================================
// Add methods
// ========================================================================
pub fn add_named_material(&self, name: &str, material: SceneEntity) { pub fn add_named_material(&self, name: &str, material: SceneEntity) {
let mut state = self.material_state.lock(); let mut state = self.material_state.lock();
self.start_loading_normal_maps(&mut state, &material.parameters); self.start_loading_normal_maps(&mut state, &material.parameters);
@ -186,7 +180,18 @@ impl BasicScene {
state.materials.len() - 1 state.materials.len() - 1
} }
pub fn add_float_texture(&self, name: String, texture: TextureSceneEntity) { fn add_texture_generic<T, F>(
&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<String, AsyncJob<Arc<T>>>,
create_fn: F,
) where
T: Send + 'static,
F: FnOnce(TextureSceneEntity) -> T + Send + 'static,
{
if texture.render_from_object.is_animated() { if texture.render_from_object.is_animated() {
log::info!( log::info!(
"{}: Animated world to texture not supported, using start", "{}: 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" { if texture.base.name != "imagemap" && texture.base.name != "ptex" {
state.serial_float_textures.push((name, texture)); get_serial(state).push((name, texture));
return; return;
} }
let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", "")); let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", ""));
if filename.is_empty() { if !self.validate_texture_file(&filename, &texture.base.loc, &mut state.n_missing_textures)
log::error!( {
"[{:?}] \"string filename\" not provided for image texture.",
texture.base.loc
);
state.n_missing_textures += 1;
return;
}
if !std::path::Path::new(&filename).exists() {
log::error!("[{:?}] {}: file not found.", texture.base.loc, filename);
state.n_missing_textures += 1;
return; return;
} }
if state.loading_texture_filenames.contains(&filename) { if state.loading_texture_filenames.contains(&filename) {
state.serial_float_textures.push((name, texture)); get_serial(state).push((name, texture));
return; return;
} }
state.loading_texture_filenames.insert(filename.clone()); state.loading_texture_filenames.insert(filename);
let job = run_async(move || Arc::new(create_fn(texture)));
let texture_clone = texture.clone(); get_jobs(state).insert(name, job);
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);
} }
pub fn add_spectrum_texture(&self, name: String, texture: TextureSceneEntity) { fn validate_texture_file(&self, filename: &str, loc: &FileLoc, n_missing: &mut usize) -> bool {
if texture.render_from_object.is_animated() {
log::info!(
"{}: Animated world to texture not supported, using start",
texture.base.loc
);
}
let mut state = self.texture_state.lock();
if texture.base.name != "imagemap" && texture.base.name != "ptex" {
state.serial_spectrum_textures.push((name, texture));
return;
}
let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", ""));
if filename.is_empty() { if filename.is_empty() {
log::error!( log::error!(
"[{:?}] \"string filename\" not provided for image texture.", "[{:?}] \"string filename\" not provided for image texture.",
texture.base.loc loc
); );
state.n_missing_textures += 1; *n_missing += 1;
return; return false;
} }
if !std::path::Path::new(filename).exists() {
if !std::path::Path::new(&filename).exists() { log::error!("[{:?}] {}: file not found.", loc, filename);
log::error!("[{:?}] {}: file not found.", texture.base.loc, filename); *n_missing += 1;
state.n_missing_textures += 1; return false;
return;
} }
true
}
if state.loading_texture_filenames.contains(&filename) { pub fn add_float_texture(&self, name: String, texture: TextureSceneEntity, arena: &mut Arena) {
state.serial_spectrum_textures.push((name, texture)); let mut state = self.texture_state.lock();
return; 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()); pub fn add_spectrum_texture(
&self,
let texture_clone = texture.clone(); name: String,
let job = run_async(move || { texture: TextureSceneEntity,
let render_from_texture = texture_clone.render_from_object.start_transform; arena: &mut Arena,
let tex_dict = ) {
TextureParameterDictionary::new(texture_clone.base.parameters.into(), None); let mut state = self.texture_state.lock();
let texture = SpectrumTexture::create( self.add_texture_generic(
&texture_clone.base.name, name,
render_from_texture, texture,
tex_dict, &mut state,
SpectrumType::Albedo, |s| &mut s.serial_spectrum_textures,
texture_clone.base.loc, |s| &mut s.spectrum_texture_jobs,
arena, |tex| {
) let render_from_texture = tex.render_from_object.start_transform;
.expect("Could not crate spectrum texture."); let tex_dict = TextureParameterDictionary::new(tex.base.parameters.into(), None);
SpectrumTexture::create(
Arc::new(texture) &tex.base.name,
}); render_from_texture,
tex_dict,
state.spectrum_texture_jobs.insert(name, job); SpectrumType::Albedo,
tex.base.loc,
arena,
)
.expect("Could not create spectrum texture")
},
);
} }
pub fn add_area_light(&self, light: SceneEntity) -> usize { pub fn add_area_light(&self, light: SceneEntity) -> usize {
@ -328,11 +314,7 @@ impl BasicScene {
self.instances.lock().extend(uses); self.instances.lock().extend(uses);
} }
// ======================================================================== pub fn create_textures(&self, arena: &mut Arena) -> NamedTextures {
// Finalization: Textures
// ========================================================================
pub fn create_textures(&self) -> NamedTextures {
let mut state = self.texture_state.lock(); let mut state = self.texture_state.lock();
let mut float_textures: HashMap<String, Arc<FloatTexture>> = HashMap::new(); let mut float_textures: HashMap<String, Arc<FloatTexture>> = HashMap::new();
@ -347,18 +329,18 @@ impl BasicScene {
spectrum_textures.insert(name, job.wait()); spectrum_textures.insert(name, job.wait());
} }
// Create serial textures (need access to already-loaded textures) // Create serial textures (need access to loaded textures, using shared memory)
let named = NamedTextures { let mut named = NamedTextures {
float_textures: float_textures.clone(), float_textures: Arc::new(float_textures.clone()),
albedo_spectrum_textures: spectrum_textures.clone(), albedo_spectrum_textures: Arc::new(spectrum_textures.clone()),
illuminant_spectrum_textures: spectrum_textures.clone(), illuminant_spectrum_textures: Arc::new(spectrum_textures.clone()),
unbounded_spectrum_textures: spectrum_textures.clone(), unbounded_spectrum_textures: Arc::new(spectrum_textures.clone()),
}; };
for (name, entity) in state.serial_float_textures.drain(..) { for (name, entity) in state.serial_float_textures.drain(..) {
let render_from_texture = entity.render_from_object.start_transform; let render_from_texture = entity.render_from_object.start_transform;
let tex_dict = let tex_dict =
TextureParameterDictionary::new(entity.base.parameters.into(), Some(named)); TextureParameterDictionary::new(entity.base.parameters.into(), Some(&named));
let tex = FloatTexture::create( let tex = FloatTexture::create(
&entity.base.name, &entity.base.name,
render_from_texture, render_from_texture,
@ -367,13 +349,13 @@ impl BasicScene {
arena, arena,
) )
.expect("Could not create float texture"); .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(..) { for (name, entity) in state.serial_spectrum_textures.drain(..) {
let render_from_texture = entity.render_from_object.start_transform; let render_from_texture = entity.render_from_object.start_transform;
let tex_dict = let tex_dict =
TextureParameterDictionary::new(entity.base.parameters.into(), Some(named)); TextureParameterDictionary::new(entity.base.parameters.into(), Some(&named));
let tex = SpectrumTexture::create( let tex = SpectrumTexture::create(
&entity.base.name, &entity.base.name,
render_from_texture, render_from_texture,
@ -383,32 +365,21 @@ impl BasicScene {
arena, arena,
) )
.expect("Could not create spectrum texture"); .expect("Could not create spectrum texture");
spectrum_textures.insert(name, Arc::new(tex)); Arc::make_mut(&mut named.albedo_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,
} }
named
} }
// ========================================================================
// Finalization: Materials
// ========================================================================
pub fn create_materials( pub fn create_materials(
&self, &self,
textures: &NamedTextures, textures: &NamedTextures,
arena: &mut Arena,
) -> (HashMap<String, Material>, Vec<Material>) { ) -> (HashMap<String, Material>, Vec<Material>) {
let mut state = self.material_state.lock(); let mut state = self.material_state.lock();
// Resolve normal map jobs // Resolve normal map jobs
let jobs: Vec<_> = state.normal_map_jobs.drain().collect(); for (filename, job) in state.normal_map_jobs.drain() {
for (filename, job) in jobs { state.normal_maps.insert(filename, job.wait());
let image = job.wait();
state.normal_maps.insert(filename, image);
} }
let mut named_materials: HashMap<String, Material> = HashMap::new(); let mut named_materials: HashMap<String, Material> = HashMap::new();
@ -440,7 +411,7 @@ impl BasicScene {
&mat_type, &mat_type,
&tex_dict, &tex_dict,
normal_map, normal_map,
named_materials, named_materials.into(),
entity.loc, entity.loc,
arena, arena,
) )
@ -449,24 +420,27 @@ impl BasicScene {
named_materials.insert(name.clone(), mat); named_materials.insert(name.clone(), mat);
} }
let mut materials: Vec<Material> = Vec::with_capacity(state.materials.len()); let materials: Vec<Material> = 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 { Material::create(
let normal_map = self.get_normal_map(&state, &entity.parameters); &entity.name,
let tex_dict = &tex_dict,
TextureParameterDictionary::new(entity.parameters.into(), Some(*textures)); normal_map,
&named_materials, // Reference
let mat = Material::create( entity.loc.clone(),
&entity.name, arena,
&tex_dict, )
normal_map, .expect("Could not create material")
named_materials, })
entity.loc, .collect();
arena,
)
.expect("Could not create material");
materials.push(mat);
}
(named_materials, materials) (named_materials, materials)
} }
@ -478,6 +452,7 @@ impl BasicScene {
named_materials: &HashMap<String, Material>, named_materials: &HashMap<String, Material>,
materials: &Vec<Material>, materials: &Vec<Material>,
shape_lights: &HashMap<usize, Vec<Light>>, shape_lights: &HashMap<usize, Vec<Light>>,
arena: &mut Arena,
) -> Vec<Primitive> { ) -> Vec<Primitive> {
let shapes = self.shapes.lock(); let shapes = self.shapes.lock();
let animated_shapes = self.animated_shapes.lock(); let animated_shapes = self.animated_shapes.lock();
@ -493,12 +468,10 @@ impl BasicScene {
let mut primitives = Vec::new(); let mut primitives = Vec::new();
// Static shapes: parallel load, serial upload let loaded = self.load_shapes_parallel(&shapes, &lookup, arena);
let loaded = self.load_shapes_parallel(&shapes, &lookup);
primitives.extend(self.upload_shapes(arena, &shapes, loaded, &lookup)); primitives.extend(self.upload_shapes(arena, &shapes, loaded, &lookup));
// Animated shapes let loaded_anim = self.load_animated_shapes_parallel(&animated_shapes, &lookup, arena);
let loaded_anim = self.load_animated_shapes_parallel(&animated_shapes, &lookup);
primitives.extend(self.upload_animated_shapes( primitives.extend(self.upload_animated_shapes(
arena, arena,
&animated_shapes, &animated_shapes,
@ -513,6 +486,7 @@ impl BasicScene {
&self, &self,
entities: &[ShapeSceneEntity], entities: &[ShapeSceneEntity],
lookup: &SceneLookup, lookup: &SceneLookup,
arena: &mut Arena,
) -> Vec<Vec<Shape>> { ) -> Vec<Vec<Shape>> {
entities entities
.par_iter() .par_iter()
@ -535,6 +509,7 @@ impl BasicScene {
&self, &self,
entities: &[AnimatedShapeSceneEntity], entities: &[AnimatedShapeSceneEntity],
lookup: &SceneLookup, lookup: &SceneLookup,
arena: &mut Arena,
) -> Vec<Vec<Shape>> { ) -> Vec<Vec<Shape>> {
entities entities
.par_iter() .par_iter()
@ -549,6 +524,11 @@ impl BasicScene {
sh.transformed_base.base.loc, sh.transformed_base.base.loc,
arena, arena,
) )
.map_err(|e| {
log::error!("{}: Failed to create shape: {}", sh.base.loc, e);
e
})
.ok()
}) })
.collect() .collect()
} }
@ -609,10 +589,10 @@ impl BasicScene {
} else { } else {
let p = GeometricPrimitive::new( let p = GeometricPrimitive::new(
shape_ptr, shape_ptr,
Ptr::from(&mtl), mtl.upload(arena),
area_light, area_light.upload(arena),
mi.clone(), mi.clone(),
alpha_tex.clone(), alpha_tex.upload(arena),
); );
Primitive::Geometric(p) Primitive::Geometric(p)
}; };
@ -752,11 +732,13 @@ impl BasicScene {
let mut state = self.media_state.lock(); let mut state = self.media_state.lock();
if let Some(medium) = state.map.get(name) { 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) { if let Some(job) = state.jobs.remove(name) {
let medium = Arc::new(job.wait()); let job: AsyncJob<Medium> = job;
let result: Medium = job.wait();
let medium: Arc<Medium> = Arc::new(job.wait());
state.map.insert(name.to_string(), medium.clone()); state.map.insert(name.to_string(), medium.clone());
return Some(medium); return Some(medium);
} }

View file

@ -15,7 +15,7 @@ pub struct TextureState {
pub loading_texture_filenames: HashSet<String>, pub loading_texture_filenames: HashSet<String>,
pub float_texture_jobs: HashMap<String, AsyncJob<Arc<FloatTexture>>>, pub float_texture_jobs: HashMap<String, AsyncJob<Arc<FloatTexture>>>,
pub spectrum_texture_jobs: HashMap<String, AsyncJob<Arc<SpectrumTexture>>>, pub spectrum_texture_jobs: HashMap<String, AsyncJob<Arc<SpectrumTexture>>>,
pub n_missing_textures: i32, pub n_missing_textures: usize,
} }
#[derive(Debug)] #[derive(Debug)]
@ -43,3 +43,12 @@ pub struct SingletonState<T> {
pub result: Option<Arc<T>>, pub result: Option<Arc<T>>,
pub job: Option<AsyncJob<T>>, pub job: Option<AsyncJob<T>>,
} }
impl<T> Default for SingletonState<T> {
fn default() -> Self {
Self {
result: None,
job: None,
}
}
}

View file

@ -69,19 +69,21 @@ impl FloatTexture {
arena: &mut Arena, arena: &mut Arena,
) -> Result<Self> { ) -> Result<Self> {
match name { 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, &params, &loc, arena), "scale" => FloatScaledTexture::create(&render_from_texture, &params, &loc, arena),
"mix" => FloatMixTexture::create(&render_from_texture, &params, &loc, arena), "mix" => FloatMixTexture::create(&render_from_texture, &params, &loc, arena),
"directionmix" => { "directionmix" => {
FloatDirectionMixTexture::create(&render_from_texture, &params, &loc, arena) FloatDirectionMixTexture::create(&render_from_texture, &params, &loc, arena)
} }
"bilerp" => FloatBilerpTexture::create(render_from_texture, params, loc), "bilerp" => FloatBilerpTexture::create(render_from_texture, params, loc, arena),
"imagemap" => FloatImageTexture::create(render_from_texture, params, loc), "imagemap" => FloatImageTexture::create(render_from_texture, params, loc, arena),
"checkerboard" => FloatCheckerboardTexture::create(render_from_texture, params, loc), "checkerboard" => {
"dots" => FloatDotsTexture::create(render_from_texture, params, loc), FloatCheckerboardTexture::create(render_from_texture, params, loc, arena)
"fbm" => FBmTexture::create(render_from_texture, params, loc), }
"wrinkled" => WrinkledTexture::create(render_from_texture, params, loc), "dots" => FloatDotsTexture::create(render_from_texture, params, loc, arena),
"windy" => WindyTexture::create(render_from_texture, params, loc), "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)), _ => Err(anyhow!("Float texture type '{}' unknown at {}", name, loc)),
} }
} }

View file

@ -1,7 +1,7 @@
use super::state::PathState; use super::state::PathState;
use shared::core::geometry::Ray; use shared::core::geometry::Ray;
use shared::core::interaction::{Interaction, InteractionTrait}; 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::primitive::{Primitive, PrimitiveTrait};
use shared::core::shape::ShapeIntersection; use shared::core::shape::ShapeIntersection;
use shared::lights::LightSampler; use shared::lights::LightSampler;

View file

@ -65,7 +65,7 @@ impl PathConfig {
pub struct PathIntegrator { pub struct PathIntegrator {
base: IntegratorBase, base: IntegratorBase,
camera: Arc<Camera>, camera: Arc<Camera>,
light_sampler: LightSampler, sampler: LightSampler,
config: PathConfig, config: PathConfig,
} }
@ -77,14 +77,14 @@ impl PathIntegrator {
aggregate: Arc<Primitive>, aggregate: Arc<Primitive>,
lights: Vec<Arc<Light>>, lights: Vec<Arc<Light>>,
camera: Arc<Camera>, camera: Arc<Camera>,
sampler: LightSampler,
config: PathConfig, config: PathConfig,
) -> Self { ) -> Self {
let base = IntegratorBase::new(aggregate, lights.clone()); let base = IntegratorBase::new(aggregate, lights.clone());
let light_sampler = LightSampler::new(&lights);
Self { Self {
base, base,
camera, camera,
light_sampler, sampler,
config, config,
} }
} }
@ -99,10 +99,7 @@ impl PathIntegrator {
) -> SampledSpectrum { ) -> SampledSpectrum {
let ctx = LightSampleContext::from(intr); let ctx = LightSampleContext::from(intr);
let Some(sampled) = self let Some(sampled) = self.sampler.sample_with_context(&ctx, sampler.get1d()) else {
.light_sampler
.sample_with_context(&ctx, sampler.get1d())
else {
return SampledSpectrum::zero(); return SampledSpectrum::zero();
}; };
@ -220,7 +217,7 @@ impl RayIntegratorTrait for PathIntegrator {
&mut state, &mut state,
&ray, &ray,
lambda, lambda,
Some(&self.light_sampler), Some(&self.sampler),
self.config.use_mis, self.config.use_mis,
); );
break; break;
@ -236,7 +233,7 @@ impl RayIntegratorTrait for PathIntegrator {
} else if self.config.use_mis { } else if self.config.use_mis {
if !isect.area_light.is_null() { if !isect.area_light.is_null() {
let light = &isect.area_light; 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); * light.pdf_li(&state.prev_ctx, ray.d, true);
let w_b = power_heuristic(1, state.prev_pdf, 1, p_l); let w_b = power_heuristic(1, state.prev_pdf, 1, p_l);
state.l += state.beta * w_b * le; state.l += state.beta * w_b * le;

View file

@ -9,6 +9,7 @@ use indicatif::{ProgressBar, ProgressStyle};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use shared::Float; use shared::Float;
use shared::core::camera::{Camera, CameraTrait}; use shared::core::camera::{Camera, CameraTrait};
use shared::core::film::Film;
use shared::core::geometry::{Bounds2i, Point2i, VectorLike}; use shared::core::geometry::{Bounds2i, Point2i, VectorLike};
use shared::core::options::get_options; use shared::core::options::get_options;
use shared::core::sampler::get_camera_sample; use shared::core::sampler::get_camera_sample;
@ -235,8 +236,9 @@ pub fn evaluate_pixel_sample<T: RayIntegratorTrait>(
if get_options().disable_wavelength_jitter { if get_options().disable_wavelength_jitter {
lu = 0.5; lu = 0.5;
} }
let lambda = camera.get_film().sample_wavelengths(lu); 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 filter = film.get_filter();
let camera_sample = get_camera_sample(sampler, pixel, filter); let camera_sample = get_camera_sample(sampler, pixel, filter);
if let Some(mut camera_ray) = camera.generate_ray_differential(camera_sample, &lambda) { if let Some(mut camera_ray) = camera.generate_ray_differential(camera_sample, &lambda) {
@ -258,7 +260,7 @@ pub fn evaluate_pixel_sample<T: RayIntegratorTrait>(
l *= camera_ray.weight; l *= camera_ray.weight;
let std_spectra = get_spectra_context(); 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.); l = SampledSpectrum::new(0.);
} }

View file

@ -1,3 +1,4 @@
#[allow(dead_code)]
pub mod core; pub mod core;
pub mod filters; pub mod filters;
pub mod globals; pub mod globals;
@ -14,3 +15,5 @@ pub mod utils;
pub mod gpu; pub mod gpu;
pub use utils::arena::Arena; pub use utils::arena::Arena;
pub use utils::error::FileLoc;
pub use utils::parameters::ParameterDictionary;

View file

@ -97,7 +97,7 @@ impl CreateDiffuseLight for DiffuseAreaLight {
colorspace, colorspace,
shape, shape,
alpha: stored_alpha.expect("Could not retrieve texture"), alpha: stored_alpha.expect("Could not retrieve texture"),
lemit: Ptr::from(lemit.device()), lemit: Ptr::from(&lemit.device()),
two_sided, two_sided,
scale, scale,
} }

View file

@ -28,7 +28,7 @@ impl CreateDistantLight for DistantLight {
let lemit = lookup_spectrum(&le); let lemit = lookup_spectrum(&le);
Self { Self {
base, base,
lemit: Ptr::from(lemit.device()), lemit: Ptr::from(&lemit.device()),
scale, scale,
scene_center: Point3f::default(), scene_center: Point3f::default(),
scene_radius: 0., scene_radius: 0.,

View file

@ -46,7 +46,7 @@ impl CreateGoniometricLight for GoniometricLight {
let distrib = PiecewiseConstant2D::from_image(&image); let distrib = PiecewiseConstant2D::from_image(&image);
Self { Self {
base, base,
iemit: Ptr::from(iemit.device()), iemit: Ptr::from(&iemit.device()),
scale, scale,
image: Ptr::from(image.device_image()), image: Ptr::from(image.device_image()),
distrib: Ptr::from(&distrib.device), distrib: Ptr::from(&distrib.device),

View file

@ -129,7 +129,7 @@ pub fn create(
_medium: MediumInterface, _medium: MediumInterface,
camera_transform: CameraTransform, camera_transform: CameraTransform,
parameters: &ParameterDictionary, parameters: &ParameterDictionary,
colorspace: &RGBColorSpace, colorspace: Option<&RGBColorSpace>,
loc: &FileLoc, loc: &FileLoc,
) -> Result<Light> { ) -> Result<Light> {
let l = parameters.get_spectrum_array("L", SpectrumType::Illuminant); let l = parameters.get_spectrum_array("L", SpectrumType::Illuminant);
@ -152,7 +152,7 @@ pub fn create(
scale /= spectrum_to_photometric(l[0]); scale /= spectrum_to_photometric(l[0]);
l[0] l[0]
} else { } else {
Spectrum::Dense(colorspace.illuminant) Spectrum::Dense(colorspace.unwrap().illuminant)
}; };
if e_v > 0.0 { if e_v > 0.0 {
@ -165,7 +165,7 @@ pub fn create(
} }
// Image-based lights // 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)); scale /= spectrum_to_photometric(Spectrum::Dense(image_cs.illuminant));
@ -208,11 +208,11 @@ fn create_image_light(
// Extract luminance data // Extract luminance data
let image_ptr = image.upload(arena); let image_ptr = image.upload(arena);
let value = image.clone(); let value = &image;
let mut data: Vec<Float> = (0..n_v) let mut data: Vec<Float> = (0..n_v)
.flat_map(|v| { .flat_map(|v| {
(0..n_u).map(move |u| { (0..n_u).map(move |u| {
&value value
.get_channels(Point2i::new(u as i32, v as i32)) .get_channels(Point2i::new(u as i32, v as i32))
.average() .average()
}) })

View file

@ -36,7 +36,7 @@ impl CreatePointLight for PointLight {
medium_interface, medium_interface,
); );
let iemit = lookup_spectrum(&le); let iemit = lookup_spectrum(&le);
let i = Ptr::from(iemit.device()); let i = Ptr::from(&iemit.device());
Self { base, scale, i } Self { base, scale, i }
} }

View file

@ -17,7 +17,7 @@ use shared::core::spectrum::Spectrum;
use shared::lights::ProjectionLight; use shared::lights::ProjectionLight;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::math::{radians, square}; use shared::utils::math::{radians, square};
use shared::utils::sampling::DeviceWindowedPiecewiseConstant2D; use shared::utils::sampling::DeviceiecewiseConstant2D;
use shared::utils::{Ptr, Transform}; use shared::utils::{Ptr, Transform};
use std::path::Path; use std::path::Path;
@ -28,7 +28,6 @@ pub trait CreateProjectionLight {
scale: Float, scale: Float,
image: Ptr<DeviceImage>, image: Ptr<DeviceImage>,
image_color_space: Ptr<RGBColorSpace>, image_color_space: Ptr<RGBColorSpace>,
distrib: Ptr<DeviceWindowedPiecewiseConstant2D>,
fov: Float, fov: Float,
) -> Self; ) -> Self;
} }
@ -40,7 +39,6 @@ impl CreateProjectionLight for ProjectionLight {
scale: Float, scale: Float,
image: Ptr<DeviceImage>, image: Ptr<DeviceImage>,
image_color_space: Ptr<RGBColorSpace>, image_color_space: Ptr<RGBColorSpace>,
distrib: Ptr<DeviceWindowedPiecewiseConstant2D>,
fov: Float, fov: Float,
) -> Self { ) -> Self {
let base = LightBase::new( let base = LightBase::new(
@ -49,20 +47,39 @@ impl CreateProjectionLight for ProjectionLight {
medium_interface, medium_interface,
); );
let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap(); let opposite = (radians(fov) / 2.0).tan();
let light_from_screen = screen_from_light.inverse();
let hither = 1e-3;
let res = image.resolution(); let res = image.resolution();
let aspect = res.x() as Float / res.y() as Float; 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 aspect_ratio = if aspect > 1.0 { aspect } else { 1.0 / aspect };
let a = 4.0 * square(opposite) * aspect_ratio; 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 { Self {
base, base,
image, image,
image_color_space, image_color_space,
distrib, distrib: Ptr::from(&distrib.device),
screen_bounds, screen_bounds,
screen_from_light, screen_from_light,
light_from_screen, light_from_screen,
@ -131,38 +148,12 @@ impl CreateLight for ProjectionLight {
let flip = Transform::scale(1., -1., 1.); let flip = Transform::scale(1., -1., 1.);
let render_from_light_flip = render_from_light * flip; 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( let specific = ProjectionLight::new(
render_from_light_flip, render_from_light_flip,
medium.into(), medium.into(),
scale, scale,
image.upload(arena), image.upload(arena),
colorspace.upload(arena), colorspace.upload(arena),
distrib.upload(arena),
fov, fov,
); );

View file

View file

@ -1,12 +1,9 @@
use crate::Arena; use super::*;
use crate::core::sampler::CreateSampler;
use crate::utils::math::compute_radical_inverse_permutations; use crate::utils::math::compute_radical_inverse_permutations;
use crate::utils::{FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
use shared::core::options::get_options; use shared::core::options::get_options;
use shared::core::sampler::{HaltonSampler, MAX_HALTON_RESOLUTION, RandomizeStrategy}; use shared::core::sampler::{HaltonSampler, MAX_HALTON_RESOLUTION, RandomizeStrategy};
use shared::utils::Ptr;
pub trait CreateHaltonSampler { pub trait CreateHaltonSampler {
fn new( fn new(
@ -72,7 +69,7 @@ impl CreateSampler for HaltonSampler {
full_res: Point2i, full_res: Point2i,
loc: &FileLoc, loc: &FileLoc,
_arena: &mut Arena, _arena: &mut Arena,
) -> Result<Self> { ) -> Result<Sampler> {
let options = get_options(); let options = get_options();
let nsamp = options let nsamp = options
.quick_render .quick_render
@ -98,6 +95,6 @@ impl CreateSampler for HaltonSampler {
let sampler = HaltonSampler::new(nsamp, full_res, s, seed as u64); let sampler = HaltonSampler::new(nsamp, full_res, s, seed as u64);
// arena.alloc(sampler); // arena.alloc(sampler);
Ok(sampler) Ok(Sampler::Halton(sampler))
} }
} }

View file

@ -1,8 +1,4 @@
use crate::Arena; use super::*;
use crate::core::sampler::CreateSampler;
use crate::utils::{FileLoc, ParameterDictionary};
use anyhow::Result;
use shared::core::geometry::Point2i;
use shared::core::options::get_options; use shared::core::options::get_options;
use shared::core::sampler::IndependentSampler; use shared::core::sampler::IndependentSampler;
@ -12,7 +8,7 @@ impl CreateSampler for IndependentSampler {
_full_res: Point2i, _full_res: Point2i,
_loc: &FileLoc, _loc: &FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Self> { ) -> Result<Sampler> {
let options = get_options(); let options = get_options();
let nsamp = options let nsamp = options
.quick_render .quick_render
@ -22,6 +18,6 @@ impl CreateSampler for IndependentSampler {
let seed = params.get_one_int("seed", options.seed); let seed = params.get_one_int("seed", options.seed);
let sampler = Self::new(nsamp, seed as u64); let sampler = Self::new(nsamp, seed as u64);
arena.alloc(sampler); arena.alloc(sampler);
Ok(sampler) Ok(Sampler::Independent(sampler))
} }
} }

View file

@ -2,3 +2,17 @@ pub mod halton;
pub mod independent; pub mod independent;
pub mod sobol; pub mod sobol;
pub mod stratified; 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<Sampler>;
}

View file

@ -1,7 +1,4 @@
use crate::Arena; use super::*;
use crate::core::sampler::CreateSampler;
use crate::utils::{FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow};
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
use shared::core::options::get_options; use shared::core::options::get_options;
use shared::core::sampler::{PaddedSobolSampler, RandomizeStrategy, SobolSampler, ZSobolSampler}; use shared::core::sampler::{PaddedSobolSampler, RandomizeStrategy, SobolSampler, ZSobolSampler};
@ -12,7 +9,7 @@ impl CreateSampler for SobolSampler {
full_res: Point2i, full_res: Point2i,
loc: &FileLoc, loc: &FileLoc,
_arena: &mut Arena, _arena: &mut Arena,
) -> Result<Self> { ) -> Result<Sampler> {
let options = get_options(); let options = get_options();
let nsamp = options let nsamp = options
.quick_render .quick_render
@ -32,7 +29,7 @@ impl CreateSampler for SobolSampler {
let sampler = Self::new(nsamp, full_res, s, Some(seed as u64)); let sampler = Self::new(nsamp, full_res, s, Some(seed as u64));
// arena.alloc(sampler); // arena.alloc(sampler);
Ok(sampler) Ok(Sampler::Sobol(sampler))
} }
} }
@ -42,7 +39,7 @@ impl CreateSampler for PaddedSobolSampler {
_full_res: Point2i, _full_res: Point2i,
loc: &FileLoc, loc: &FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Self> { ) -> Result<Sampler> {
let options = get_options(); let options = get_options();
let nsamp = options let nsamp = options
.quick_render .quick_render
@ -64,8 +61,8 @@ impl CreateSampler for PaddedSobolSampler {
}; };
let sampler = Self::new(nsamp, s, Some(seed as u64)); let sampler = Self::new(nsamp, s, Some(seed as u64));
arena.alloc(sampler); // arena.alloc(sampler);
Ok(sampler) Ok(Sampler::PaddedSobol(sampler))
} }
} }
@ -75,7 +72,7 @@ impl CreateSampler for ZSobolSampler {
full_res: Point2i, full_res: Point2i,
loc: &FileLoc, loc: &FileLoc,
arena: &mut Arena, arena: &mut Arena,
) -> Result<Self> { ) -> Result<Sampler> {
let options = get_options(); let options = get_options();
let nsamp = options let nsamp = options
.quick_render .quick_render
@ -97,7 +94,7 @@ impl CreateSampler for ZSobolSampler {
}; };
let sampler = ZSobolSampler::new(nsamp, full_res, s, Some(seed as u64)); let sampler = ZSobolSampler::new(nsamp, full_res, s, Some(seed as u64));
arena.alloc(sampler); // arena.alloc(sampler);
Ok(sampler) Ok(Sampler::ZSobol(sampler))
} }
} }

View file

@ -1,8 +1,4 @@
use crate::Arena; use super::*;
use crate::core::sampler::CreateSampler;
use crate::utils::{FileLoc, ParameterDictionary};
use anyhow::Result;
use shared::core::geometry::Point2i;
use shared::core::options::get_options; use shared::core::options::get_options;
use shared::core::sampler::StratifiedSampler; use shared::core::sampler::StratifiedSampler;
@ -12,7 +8,7 @@ impl CreateSampler for StratifiedSampler {
_full_res: Point2i, _full_res: Point2i,
_loc: &FileLoc, _loc: &FileLoc,
_arena: &mut Arena, _arena: &mut Arena,
) -> Result<Self> { ) -> Result<Sampler> {
let options = get_options(); let options = get_options();
let jitter = params.get_one_bool("jitter", true); let jitter = params.get_one_bool("jitter", true);
let (x_samples, y_samples) = if options.quick_render { let (x_samples, y_samples) = if options.quick_render {
@ -32,6 +28,6 @@ impl CreateSampler for StratifiedSampler {
let sampler = Self::new(x_samples, y_samples, Some(seed as u64), jitter); let sampler = Self::new(x_samples, y_samples, Some(seed as u64), jitter);
// arena.aloc(sampler); // arena.aloc(sampler);
Ok(sampler) Ok(Sampler::Stratified(sampler))
} }
} }

View file

@ -7,13 +7,15 @@ use shared::spectra::{
use shared::utils::math::square; use shared::utils::math::square;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
pub struct DenselySampledSpectrumBuffer { pub struct DenselySampledSpectrumBuffer {
pub lambda_min: i32, pub lambda_min: i32,
pub lambda_max: i32, pub lambda_max: i32,
pub values: Vec<Float>, pub values: Vec<Float>,
} }
impl Eq for DenselySampledSpectrumBuffer {}
impl Hash for DenselySampledSpectrumBuffer { impl Hash for DenselySampledSpectrumBuffer {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.lambda_min.hash(state); self.lambda_min.hash(state);

View file

@ -1,5 +1,6 @@
use crate::globals::{ACES_TABLE, DCI_P3_TABLE, REC2020_TABLE, SRGB_TABLE}; use crate::globals::{ACES_TABLE, DCI_P3_TABLE, REC2020_TABLE, SRGB_TABLE};
use crate::spectra::colorspace::RGBColorSpaceData; use crate::spectra::colorspace::RGBColorSpaceData;
use anyhow::{Result, anyhow};
use shared::core::geometry::Point2f; use shared::core::geometry::Point2f;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::core::spectrum::StandardSpectra; use shared::core::spectrum::StandardSpectra;
@ -105,6 +106,18 @@ pub struct StandardColorSpaces {
pub aces2065_1: Arc<RGBColorSpaceData>, pub aces2065_1: Arc<RGBColorSpaceData>,
} }
impl StandardColorSpaces {
pub fn get_named(&self, name: &str) -> Result<Arc<RGBColorSpaceData>> {
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 { pub fn get_colorspace_context() -> StandardColorSpaces {
StandardColorSpaces { StandardColorSpaces {
srgb: SRGB.clone().into(), srgb: SRGB.clone().into(),

View file

@ -1,5 +1,5 @@
use anyhow::Result;
use crate::Arena; use crate::Arena;
use anyhow::Result;
use shared::{ use shared::{
core::texture::{SpectrumType, TextureEvalContext}, core::texture::{SpectrumType, TextureEvalContext},
textures::{FloatConstantTexture, SpectrumConstantTexture}, textures::{FloatConstantTexture, SpectrumConstantTexture},
@ -9,7 +9,7 @@ use shared::{
use crate::{ use crate::{
core::texture::{ core::texture::{
CreateFloatTexture, CreateSpectrumTexture, FloatTexture, FloatTextureTrait, CreateFloatTexture, CreateSpectrumTexture, FloatTexture, FloatTextureTrait,
SpectrumTextureTrait, SpectrumTextureTrait, SpectrumTexture SpectrumTexture, SpectrumTextureTrait,
}, },
utils::{FileLoc, TextureParameterDictionary}, utils::{FileLoc, TextureParameterDictionary},
}; };

View file

@ -6,6 +6,8 @@ use crate::utils::mipmap::MIPMap;
use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D}; use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D};
use shared::core::color::RGBToSpectrumTable; use shared::core::color::RGBToSpectrumTable;
use shared::core::image::DeviceImage; use shared::core::image::DeviceImage;
use shared::core::light::Light;
use shared::core::material::Material;
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture}; use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture};
@ -155,6 +157,13 @@ impl Upload for Shape {
} }
} }
impl Upload for Light {
type Target = Light;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
arena.alloc(self.clone())
}
}
impl Upload for Image { impl Upload for Image {
type Target = DeviceImage; type Target = DeviceImage;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> { fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
@ -169,6 +178,13 @@ impl Upload for Spectrum {
} }
} }
impl Upload for Material {
type Target = Material;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
arena.alloc(self.clone())
}
}
impl Upload for DenselySampledSpectrumBuffer { impl Upload for DenselySampledSpectrumBuffer {
type Target = DenselySampledSpectrum; type Target = DenselySampledSpectrum;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> { fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {

View file

@ -46,7 +46,7 @@ impl DigitPermutation {
} }
} }
pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec<u16>, Vec<DigitPermutation>) { pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec<u16>, Vec<DeviceDigitPermutation>) {
let temp_data: Vec<Vec<u16>> = PRIMES let temp_data: Vec<Vec<u16>> = PRIMES
.iter() .iter()
.map(|&base| DigitPermutation::new(base as i32, seed).permutations) .map(|&base| DigitPermutation::new(base as i32, seed).permutations)
@ -68,11 +68,13 @@ pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec<u16>, Vec<DigitPe
// let ptr_to_data = storage_base_ptr.add(current_offset); // let ptr_to_data = storage_base_ptr.add(current_offset);
views.push(DigitPermutation::new( views.push(
base as i32, DigitPermutation::new(
n_digits as u64, base as i32,
// Ptr::from(ptr_to_data), n_digits as u64,
)); )
.device,
);
// current_offset += len; // current_offset += len;
} }

View file

@ -42,6 +42,7 @@ where
}); });
} }
#[derive(Debug)]
pub struct AsyncJob<T> { pub struct AsyncJob<T> {
receiver: Receiver<T>, receiver: Receiver<T>,
} }

View file

@ -234,12 +234,12 @@ impl PBRTParameter for String {
} }
} }
#[derive(Default)] #[derive(Default, Clone)]
pub struct NamedTextures { pub struct NamedTextures {
pub float_textures: HashMap<String, Arc<FloatTexture>>, pub float_textures: Arc<HashMap<String, Arc<FloatTexture>>>,
pub albedo_spectrum_textures: HashMap<String, Arc<SpectrumTexture>>, pub albedo_spectrum_textures: Arc<HashMap<String, Arc<SpectrumTexture>>>,
pub unbounded_spectrum_textures: HashMap<String, Arc<SpectrumTexture>>, pub unbounded_spectrum_textures: Arc<HashMap<String, Arc<SpectrumTexture>>>,
pub illuminant_spectrum_textures: HashMap<String, Arc<SpectrumTexture>>, pub illuminant_spectrum_textures: Arc<HashMap<String, Arc<SpectrumTexture>>>,
} }
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
@ -704,11 +704,11 @@ fn read_spectrum_from_file(filename: &str) -> Result<Spectrum, String> {
pub struct TextureParameterDictionary { pub struct TextureParameterDictionary {
dict: Arc<ParameterDictionary>, dict: Arc<ParameterDictionary>,
textures: Option<NamedTextures>, textures: Option<&NamedTextures>,
} }
impl TextureParameterDictionary { impl TextureParameterDictionary {
pub fn new(dict: Arc<ParameterDictionary>, textures: Option<NamedTextures>) -> Self { pub fn new(dict: Arc<ParameterDictionary>, textures: Option<&NamedTextures>) -> Self {
Self { dict, textures } Self { dict, textures }
} }