diff --git a/shared/src/core/image.rs b/shared/src/core/image.rs index af48ddd..f0e9258 100644 --- a/shared/src/core/image.rs +++ b/shared/src/core/image.rs @@ -167,6 +167,17 @@ impl Pixels { } } + pub fn as_u8(&self) -> &[u8] { + &self.data + } + + pub fn as_f16(&mut self) -> &[u16] { + assert_eq!(self.format, PixelFormat::F16); + unsafe { + core::slice::from_raw_parts(self.data.as_ptr() as *const u16, self.data.len() / 2) + } + } + pub fn as_u8_mut(&mut self) -> &mut [u8] { &mut self.data } diff --git a/shared/src/shapes/mod.rs b/shared/src/shapes/mod.rs index 00be086..4dcf298 100644 --- a/shared/src/shapes/mod.rs +++ b/shared/src/shapes/mod.rs @@ -12,4 +12,4 @@ pub use cylinder::*; pub use disk::*; pub use sphere::*; pub use triangle::*; -pub use mesh::*; +pub use mesh::{TriangleMesh, BilinearPatchMesh}; diff --git a/shared/src/spectra/simple.rs b/shared/src/spectra/simple.rs index c90951c..c2223c7 100644 --- a/shared/src/spectra/simple.rs +++ b/shared/src/spectra/simple.rs @@ -3,7 +3,8 @@ use super::sampled::{LAMBDA_MAX, LAMBDA_MIN}; use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::spectra::{SampledSpectrum, SampledWavelengths, N_SPECTRUM_SAMPLES}; use crate::utils::find_interval; -use crate::{gvec, gvec_with_capacity, Float, GVec, Ptr}; +use crate::utils::math::square; +use crate::{gvec, gvec_from_slice, gvec_with_capacity, Float, GVec, Ptr}; use core::hash::{Hash, Hasher}; use num_traits::Float as NumFloat; @@ -88,6 +89,41 @@ impl DenselySampledSpectrum { } } + pub fn generate_cie_d(temperature: Float) -> Self { + let cct = temperature * 1.4388 / 1.4380; + + if cct < 4000.0 { + return Self::from_function( + |lambda| BlackbodySpectrum::new(cct).evaluate(lambda), + LAMBDA_MIN, + LAMBDA_MAX, + ); + } + + let x = if cct < 7000. { + -4.607 * 1e9 / cct.powi(3) + 2.9678 * 1e6 / square(cct) + 0.09911 * 1e3 / cct + 0.244063 + } else { + -2.0064 * 1e9 / cct.powi(3) + 1.9018 * 1e6 / square(cct) + 0.24748 * 1e3 / cct + 0.23704 + }; + let y = -3. * x + 2.87 * x - 0.275; + let m = 0.0241 + 0.2562 * x - 0.7341 * y; + let m1 = (-1.3515 - 1.7703 * x + 5.9114 * y) / m; + let m2 = (0.0300 - 31.4424 * x + 30.0717 * y) / m; + + let mut coarse_values = gvec_with_capacity(N_CIES); + for i in 0..N_CIES { + coarse_values.push((CIE_S0[i] + CIE_S1[i] * m1 + CIE_S2[i] * m2) * 0.01); + } + + let temp_pls = PiecewiseLinearSpectrum { + lambdas: gvec_from_slice(&CIE_S_LAMBDA), + values: gvec_from_slice(&coarse_values), + count: N_CIES as u32, + }; + + Self::from_function(|lambda| temp_pls.evaluate(lambda), LAMBDA_MIN, LAMBDA_MAX) + } + pub fn scale(&mut self, s: Float) { for v in &mut self.values { *v *= s; @@ -183,7 +219,6 @@ impl PiecewiseLinearSpectrum { self.count.try_into().unwrap() } - #[inline(always)] pub fn lambda(&self, idx: u32) -> Float { unsafe { *self.lambdas.as_ptr().add(idx as usize) } diff --git a/src/core/aggregates.rs b/src/core/aggregates.rs index 821d51e..6ddf4ac 100644 --- a/src/core/aggregates.rs +++ b/src/core/aggregates.rs @@ -1,5 +1,5 @@ use rayon::prelude::*; -use shared::core::aggregates::{BVHAggregate, LinearBVHNode}; +use shared::core::aggregates::{BVHAggregate as DeviceBVHAggregate, LinearBVHNode}; use shared::core::geometry::{Bounds3f, Point3f, Ray, Vector3f}; use shared::core::primitive::{Primitive, PrimitiveTrait}; use shared::core::shape::ShapeIntersection; @@ -890,8 +890,6 @@ impl BVHAggregate

{ impl BVHAggregate { pub fn to_device(&self, arena: &mut Arena) -> DeviceBVHAggregate { - let (prims_ptr, _) = arena.alloc_slice(&self.primitives); - let shared_nodes: Vec = self.nodes .iter() .map(|n| shared::core::aggregates::LinearBVHNode { @@ -902,13 +900,12 @@ impl BVHAggregate { pad: 0, }) .collect(); - let (nodes_ptr, _) = arena.alloc_slice(&shared_nodes); DeviceBVHAggregate { max_prims_in_node: self.max_prims_in_node as u32, - primitives: prims_ptr, + primitives: gvec_from_slice(&self.primitives), primitive_count: self.primitives.len() as u32, - nodes: nodes_ptr, + nodes: gvec_from_slice(&self.shared_nodes), node_count: self.nodes.len() as u32, } } diff --git a/src/core/camera.rs b/src/core/camera.rs index e2ecf19..8d33e9f 100644 --- a/src/core/camera.rs +++ b/src/core/camera.rs @@ -1,8 +1,7 @@ -use crate::core::image::ImageMetadata; -use crate::core::image::{Image, ImageIO}; +use crate::core::image::{HostImage, ImageIO, ImageMetadata}; use crate::globals::get_options; use crate::utils::read_float_file; -use crate::{Arena, FileLoc, ParameterDictionary}; +use crate::{Arena, FileLoc, ParameterDictionary, ArenaUpload}; use anyhow::{anyhow, Result}; use shared::cameras::*; use shared::core::camera::{Camera, CameraBase, CameraTrait, CameraTransform}; @@ -232,8 +231,8 @@ impl CameraFactory for Camera { } let builtin_res = 256; - let rasterize = |vert: &[Point2f]| -> Image { - let mut image = Image::new( + let rasterize = |vert: &[Point2f]| -> HostImage { + let mut image = HostImage::new( PixelFormat::F32, Point2i::new(builtin_res, builtin_res), &["Y"], @@ -279,12 +278,12 @@ impl CameraFactory for Camera { }; let aperture_name = params.get_one_string("aperture", "")?; - let mut aperture_image: Option = None; + let mut aperture_image: Option = None; if !aperture_name.is_empty() { match aperture_name.as_str() { "gaussian" => { - let mut img = Image::new( + let mut img = HostImage::new( PixelFormat::F32, Point2i::new(builtin_res, builtin_res), &["Y"], @@ -306,7 +305,7 @@ impl CameraFactory for Camera { aperture_image = Some(img); } "square" => { - let mut img = Image::new( + let mut img = HostImage::new( PixelFormat::F32, Point2i::new(builtin_res, builtin_res), &["Y"], @@ -316,7 +315,7 @@ impl CameraFactory for Camera { let high = (0.75 * builtin_res as Float) as i32; for y in low..high { for x in low..high { - img.set_channel(Point2i::new(x, y), 0, 4.0); + img.inner.set_channel(Point2i::new(x, y), 0, 4.0); } } aperture_image = Some(img); @@ -353,9 +352,9 @@ impl CameraFactory for Camera { aperture_image = Some(rasterize(&vert)); } _ => { - if let Ok(im) = Image::read(Path::new(&aperture_name), None) { + if let Ok(im) = HostImage::read(Path::new(&aperture_name), None) { if im.image.n_channels() > 1 { - let mut mono = Image::new( + let mut mono = HostImage::new( PixelFormat::F32, im.image.resolution(), &["Y"], @@ -366,7 +365,7 @@ impl CameraFactory for Camera { for x in 0..res.x() { let avg = im.image.get_channels(Point2i::new(x, y)).average(); - mono.set_channel(Point2i::new(x, y), 0, avg); + mono.inner.set_channel(Point2i::new(x, y), 0, avg); } } aperture_image = Some(mono); @@ -383,7 +382,8 @@ impl CameraFactory for Camera { &lens_params, focal_distance, aperture_diameter, - Ptr::from(&*aperture_image.unwrap()), + arena.upload(aperture_image) + ); arena.alloc(camera); diff --git a/src/core/color.rs b/src/core/color.rs index 48880f1..cf6e700 100644 --- a/src/core/color.rs +++ b/src/core/color.rs @@ -15,6 +15,7 @@ impl CreateRGBToSpectrumTable for RGBToSpectrumTable { assert_eq!(z_nodes.len(), RES as usize); assert_eq!(coeffs.len(), (RES * RES * RES) as usize * 3 * 3); Self { + n_nodes: z_nodes.len().try_into().unwrap(), z_nodes: gvec_from_slice(z_nodes), coeffs: gvec_from_slice(unsafe { core::slice::from_raw_parts( diff --git a/src/core/film.rs b/src/core/film.rs index 6a9b04e..e468541 100644 --- a/src/core/film.rs +++ b/src/core/film.rs @@ -87,8 +87,10 @@ impl CreatePixelSensor for PixelSensor { DenselySampledSpectrum::generate_cie_d(white_balance_temp) }; + let d_ptr = arena.alloc(d_illum); + let sensor_illum: Option> = if white_balance_temp != 0. { - Some(Spectrum::Dense(d_illum.device()).into()) + Some(Spectrum::Dense(d_ptr).into()) } else { None }; @@ -150,8 +152,8 @@ impl CreatePixelSensor for PixelSensor { let g_bar = DenselySampledSpectrum::from_spectrum(g); let b_bar = DenselySampledSpectrum::from_spectrum(b); let r_ptr = arena.alloc(r_bar); - let g_ptr = arena.alloc(r_bar); - let v_ptr = arena.alloc(r_bar); + let g_ptr = arena.alloc(g_bar); + let b_ptr = arena.alloc(b_bar); let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES]; let swatches = get_swatches(); @@ -203,6 +205,7 @@ impl CreatePixelSensor for PixelSensor { output_colorspace: &RGBColorSpace, sensor_illum: Option<&Spectrum>, imaging_ratio: Float, + arena: &Arena ) -> Self { let spectra = get_spectra_context(); let r_bar = CIE_X_DATA.clone(); diff --git a/src/core/filter.rs b/src/core/filter.rs index 65ed690..c5a471c 100644 --- a/src/core/filter.rs +++ b/src/core/filter.rs @@ -1,7 +1,7 @@ use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::{FileLoc, ParameterDictionary}; use crate::Arena; -use anyhow::{anyhow, Result}; +use anyhow::{bail, Result}; use shared::core::filter::Filter; use shared::core::geometry::{Bounds2f, Point2f, Vector2f}; use shared::filters::*; @@ -28,13 +28,14 @@ impl FilterFactory for Filter { let xw = params.get_one_float("xradius", 0.5)?; let yw = params.get_one_float("yradius", 0.5)?; let filter = BoxFilter::new(Vector2f::new(xw, yw)); - Ok(Filter::Box(filter)) + Ok(Filter::Box(arena.alloc(filter))) } "gaussian" => { let xw = params.get_one_float("xradius", 1.5)?; let yw = params.get_one_float("yradius", 1.5)?; let sigma = params.get_one_float("sigma", 0.5)?; let filter = GaussianFilter::new(Vector2f::new(xw, yw), sigma); + Ok(Filter::Gaussian(arena.alloc(filter))) } "mitchell" => { let xw = params.get_one_float("xradius", 2.)?; @@ -42,22 +43,22 @@ impl FilterFactory for Filter { let b = params.get_one_float("B", 1. / 3.)?; let c = params.get_one_float("C", 1. / 3.)?; let filter = MitchellFilter::new(Vector2f::new(xw, yw), b, c); - Ok(Filter::Mitchell(filter)) + Ok(Filter::Mitchell(arena.alloc(filter))) } "sinc" => { let xw = params.get_one_float("xradius", 4.)?; let yw = params.get_one_float("yradius", 4.)?; let tau = params.get_one_float("tau", 3.)?; let filter = LanczosSincFilter::new(Vector2f::new(xw, yw), tau); - Ok(Filter::LanczosSinc(filter)) + Ok(Filter::LanczosSinc(arena.alloc(filter))) } "triangle" => { let xw = params.get_one_float("xradius", 2.)?; let yw = params.get_one_float("yradius", 2.)?; let filter = TriangleFilter::new(Vector2f::new(xw, yw)); - Ok(Filter::Triangle(filter)) + Ok(Filter::Triangle(arena.alloc(filter))) } - _ => Err(anyhow!("Film type '{}' unknown at {}", name, loc)), + _ => bail!("Film type '{}' unknown at {}", name, loc), } } } diff --git a/src/core/image/io.rs b/src/core/image/io.rs index 197d75f..0845060 100644 --- a/src/core/image/io.rs +++ b/src/core/image/io.rs @@ -1,15 +1,14 @@ use super::{HostImage, ImageAndMetadata, ImageMetadata}; -use crate::core::image::{PixelStorage, WrapMode}; -use crate::utils::error::ImageError; +use crate::core::image::WrapMode; use anyhow::{Context, Result, bail}; use exr::prelude::{read_first_rgba_layer_from_file, write_rgba_file}; -use image_rs::{DynamicImage, ImageReader}; use shared::Float; use shared::core::color::{ColorEncoding, LINEAR, SRGB}; use shared::core::geometry::Point2i; use shared::core::image::PixelFormat; use std::fs::File; use std::io::{BufRead, BufReader, BufWriter, Read, Write}; +use image_rs::{DynamicImage, ImageReader}; use std::path::Path; pub trait ImageIO { @@ -40,55 +39,39 @@ impl ImageIO for HostImage { fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<()> { let path = Path::new(filename); let ext = path.extension().and_then(|s| s.to_str()).unwrap_or(""); - let res = match ext.to_lowercase().as_str() { + match ext.to_lowercase().as_str() { "exr" => self.write_exr(path, metadata), "png" => self.write_png(path), "pfm" => self.write_pfm(path), "qoi" => self.write_qoi(path), _ => Err(anyhow::anyhow!("Unsupported write format: {}", ext)), - }; - res.map_err(|e| ImageError::Io(std::io::Error::other(e)))?; - Ok(()) + } } fn write_png(&self, path: &Path) -> Result<()> { - let w = self.resolution().x() as u32; - let h = self.resolution().y() as u32; - - // Convert whatever we have to u8 [0..255] + let w = self.inner.resolution().x() as u32; + let h = self.inner.resolution().y() as u32; let data = self.to_u8_buffer(); - let channels = self.n_channels(); + let channels = self.inner.n_channels(); match channels { 1 => { - // Luma image_rs::save_buffer_with_format( - path, - &data, - w, - h, + path, &data, w, h, image_rs::ColorType::L8, image_rs::ImageFormat::Png, )?; } 3 => { - // RGB image_rs::save_buffer_with_format( - path, - &data, - w, - h, + path, &data, w, h, image_rs::ColorType::Rgb8, image_rs::ImageFormat::Png, )?; } 4 => { - // RGBA image_rs::save_buffer_with_format( - path, - &data, - w, - h, + path, &data, w, h, image_rs::ColorType::Rgba8, image_rs::ImageFormat::Png, )?; @@ -99,37 +82,30 @@ impl ImageIO for HostImage { } fn write_qoi(&self, path: &Path) -> Result<()> { - let w = self.resolution().x() as u32; - let h = self.resolution().y() as u32; + let w = self.inner.resolution().x() as u32; + let h = self.inner.resolution().y() as u32; let data = self.to_u8_buffer(); - let color_type = match self.n_channels() { + let color_type = match self.inner.n_channels() { 3 => image_rs::ColorType::Rgb8, 4 => image_rs::ColorType::Rgba8, _ => bail!("QOI only supports 3 or 4 channels"), }; image_rs::save_buffer_with_format( - path, - &data, - w, - h, - color_type, - image_rs::ImageFormat::Qoi, + path, &data, w, h, color_type, image_rs::ImageFormat::Qoi, )?; Ok(()) } fn write_exr(&self, path: &Path, _metadata: &ImageMetadata) -> Result<()> { - // EXR requires F32 - let w = self.resolution().x() as usize; - let h = self.resolution().y() as usize; - let c = self.n_channels(); + let w = self.inner.resolution().x() as usize; + let h = self.inner.resolution().y() as usize; + let c = self.inner.n_channels(); write_rgba_file(path, w, h, |x, y| { - // Helper to get float value regardless of internal storage let get = |ch| { - self.get_channel_with_wrap( + self.inner.get_channel_with_wrap( Point2i::new(x as i32, y as i32), ch, WrapMode::Clamp.into(), @@ -154,27 +130,22 @@ impl ImageIO for HostImage { let file = File::create(path)?; let mut writer = BufWriter::new(file); - if self.n_channels() != 3 { + if self.inner.n_channels() != 3 { bail!("PFM writing currently only supports 3 channels (RGB)"); } - // Header - let res = self.resolution(); + let res = self.inner.resolution(); writeln!(writer, "PF")?; writeln!(writer, "{} {}", res.x(), res.y())?; - let scale = if cfg!(target_endian = "little") { - -1.0 - } else { - 1.0 - }; + let scale = if cfg!(target_endian = "little") { -1.0 } else { 1.0 }; writeln!(writer, "{}", scale)?; - // PBRT stores top-to-bottom. for y in (0..res.y()).rev() { for x in 0..res.x() { for c in 0..3 { - let val = - self.get_channel_with_wrap(Point2i::new(x, y), c, WrapMode::Clamp.into()); + let val = self.inner.get_channel_with_wrap( + Point2i::new(x, y), c, WrapMode::Clamp.into(), + ); writer.write_all(&val.to_le_bytes())?; } } @@ -183,20 +154,18 @@ impl ImageIO for HostImage { Ok(()) } - // TODO: Change Image to use Vec for data, always. Only convert to Device types on - // constructors/creation fn to_u8_buffer(&self) -> Vec { - match &self.pixels { - PixelStorage::U8(data) => data.to_vec(), - PixelStorage::F16(data) => data - .iter() - .map(|v| (v.to_f32().clamp(0.0, 1.0) * 255.0 + 0.5) as u8) - .collect(), - PixelStorage::F32(data) => data - .iter() - .map(|v| (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8) - .collect(), + let res = self.inner.resolution(); + let n_pixels = (res.x() * res.y()) as usize; + let nc = self.inner.n_channels() as usize; + let total = n_pixels * nc; + + let mut buf = Vec::with_capacity(total); + for i in 0..total { + let val = unsafe { self.inner.pixels.read(i, &self.inner.encoding) }; + buf.push((val.clamp(0.0, 1.0) * 255.0 + 0.5) as u8); } + buf } } @@ -209,21 +178,24 @@ fn read_generic(path: &Path, encoding: Option) -> Result Image::from_f32(buf.into_raw(), res, &rgb_names), - DynamicImage::ImageRgba32F(buf) => Image::from_f32(buf.into_raw(), res, &rgba_names), + DynamicImage::ImageRgb32F(buf) => { + HostImage::from_f32(&buf.into_raw(), res, rgb_names) + } + DynamicImage::ImageRgba32F(buf) => { + HostImage::from_f32(&buf.into_raw(), res, rgba_names) + } _ => { - // Default to RGB8 for everything else let enc = encoding.unwrap_or(SRGB); if dyn_img.color().has_alpha() { let buf = dyn_img.to_rgba8(); - Image::from_u8(buf.into_raw(), res, &rgba_names, enc) + HostImage::from_u8(&buf.into_raw(), res, rgba_names, enc) } else { let buf = dyn_img.to_rgb8(); - Image::from_u8(buf.into_raw(), res, &rgb_names, enc) + HostImage::from_u8(&buf.into_raw(), res, rgb_names, enc) } } }; @@ -242,7 +214,6 @@ fn read_exr(path: &Path) -> Result { |buffer, position, pixel| { let width = position.width(); let idx = (position.y() * width + position.x()) * 4; - // Map exr pixel struct to our buffer buffer[idx] = pixel.0; buffer[idx + 1] = pixel.1; buffer[idx + 2] = pixel.2; @@ -254,10 +225,11 @@ fn read_exr(path: &Path) -> Result { let w = image.layer_data.size.width() as i32; let h = image.layer_data.size.height() as i32; - let image = Image::from_f32( - image.layer_data.channel_data.pixels, + let rgba_names: &[&str] = &["R", "G", "B", "A"]; + let image = HostImage::from_f32( + &image.layer_data.channel_data.pixels, Point2i::new(w, h), - &vec!["R", "G", "B", "A"], + rgba_names, ); let metadata = ImageMetadata::default(); @@ -268,7 +240,6 @@ fn read_pfm(path: &Path) -> Result { let file = File::open(path)?; let mut reader = BufReader::new(file); - // PFM Headers are: "PF\nwidth height\nscale\n" (or Pf for grayscale) let mut header_word = String::new(); reader.read_line(&mut header_word)?; let header_word = header_word.trim(); @@ -309,9 +280,7 @@ fn read_pfm(path: &Path) -> Result { let mut pixels = vec![0.0 as Float; (w * h * channels) as usize]; - // PFM is Bottom-to-Top for y in 0..h { - // Flippety-do let src_y = h - 1 - y; for x in 0..w { for c in 0..channels { @@ -331,13 +300,9 @@ fn read_pfm(path: &Path) -> Result { } } - let names = if channels == 1 { - vec!["Y"] - } else { - vec!["R", "G", "B"] - }; + let names: &[&str] = if channels == 1 { &["Y"] } else { &["R", "G", "B"] }; - let image = Image::new(PixelFormat::F32, Point2i::new(w, h), &names, LINEAR.into()); + let image = HostImage::new(PixelFormat::F32, Point2i::new(w, h), names, LINEAR.into()); let metadata = ImageMetadata::default(); Ok(ImageAndMetadata { image, metadata }) diff --git a/src/core/image/mod.rs b/src/core/image/mod.rs index b256593..457c986 100644 --- a/src/core/image/mod.rs +++ b/src/core/image/mod.rs @@ -1,14 +1,12 @@ -use crate::utils::containers::Array2D; -use anyhow::{Result, anyhow}; +use anyhow::{anyhow, Result}; use half::f16; use rayon::prelude::{IndexedParallelIterator, ParallelIterator, ParallelSliceMut}; -use shared::Float; -use shared::Ptr; use shared::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR}; use shared::core::geometry::{Bounds2f, Point2f, Point2i}; use shared::core::image::{Image, ImageBase, PixelFormat, Pixels, WrapMode, WrapMode2D}; use shared::utils::math::square; -use smallvec::{SmallVec, smallvec}; +use shared::{Array2D, Float, Ptr}; +use smallvec::{smallvec, SmallVec}; use std::ops::{Deref, DerefMut}; use std::sync::Arc; @@ -90,31 +88,32 @@ impl HostImage { let n_channels = channel_names.len() as i32; Self { inner: Image::from_u8(data, resolution, n_channels, encoding), - channel_names: channel_names.iter().map(|s| s.as_ref().to_string()).collect(), + channel_names: channel_names + .iter() + .map(|s| s.as_ref().to_string()) + .collect(), } } - pub fn from_f32( - data: &[f32], - resolution: Point2i, - channel_names: &[impl AsRef], - ) -> Self { + pub fn from_f32(data: &[f32], resolution: Point2i, channel_names: &[impl AsRef]) -> Self { let n_channels = channel_names.len() as i32; Self { inner: Image::from_f32(data, resolution, n_channels), - channel_names: channel_names.iter().map(|s| s.as_ref().to_string()).collect(), + channel_names: channel_names + .iter() + .map(|s| s.as_ref().to_string()) + .collect(), } } - pub fn from_f16( - data: &[u16], - resolution: Point2i, - channel_names: &[impl AsRef], - ) -> Self { + pub fn from_f16(data: &[u16], resolution: Point2i, channel_names: &[impl AsRef]) -> Self { let n_channels = channel_names.len() as i32; Self { inner: Image::from_f16(data, resolution, n_channels), - channel_names: channel_names.iter().map(|s| s.as_ref().to_string()).collect(), + channel_names: channel_names + .iter() + .map(|s| s.as_ref().to_string()) + .collect(), } } @@ -127,7 +126,10 @@ impl HostImage { let n_channels = channel_names.len() as i32; Self { inner: Image::new(format, resolution, n_channels, encoding), - channel_names: channel_names.iter().map(|s| s.as_ref().to_string()).collect(), + channel_names: channel_names + .iter() + .map(|s| s.as_ref().to_string()) + .collect(), } } @@ -137,7 +139,11 @@ impl HostImage { values: &[f32], ) -> Self { let n_channels = channel_names.len(); - assert_eq!(values.len(), n_channels, "values length must match channel count"); + assert_eq!( + values.len(), + n_channels, + "values length must match channel count" + ); let n_pixels = (resolution.x() * resolution.y()) as usize; let mut data = Vec::with_capacity(n_pixels * n_channels); @@ -179,14 +185,37 @@ impl HostImage { } pub fn channel_names_from_desc(&self, desc: &ImageChannelDesc) -> Vec<&str> { - desc.offset.iter().map(|&i| self.channel_names[i].as_str()).collect() + desc.offset + .iter() + .map(|&i| self.channel_names[i].as_str()) + .collect() + } + + pub fn bilerp_channel_with_wrap(&self, p: Point2f, c: i32, wrap: WrapMode2D) -> Float { + self.inner.bilerp_channel_with_wrap(p, c, wrap) + } + + pub fn bilerp_channel(&self, p: Point2f, c: i32) -> Float { + self.bilerp_channel_with_wrap(p, c, WrapMode::Clamp.into()) + } + + pub fn get_channel(&self, p: Point2i, c: i32) -> Float { + self.get_channel_with_wrap(p, c, WrapMode::Clamp.into()) + } + + pub fn get_channel_with_wrap(&self, p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float { + self.inner.get_channel_with_wrap(p, c, wrap_mode) } pub fn get_channels(&self, p: Point2i) -> ImageChannelValues { self.get_channels_with_wrap(p, WrapMode::Clamp.into()) } - pub fn get_channels_with_wrap(&self, mut p: Point2i, wrap_mode: WrapMode2D) -> ImageChannelValues { + pub fn get_channels_with_wrap( + &self, + mut p: Point2i, + wrap_mode: WrapMode2D, + ) -> ImageChannelValues { if !self.inner.remap_pixel_coords(&mut p, wrap_mode) { return ImageChannelValues(SmallVec::from_elem(0.0, self.inner.n_channels() as usize)); } @@ -212,7 +241,11 @@ impl HostImage { let pixel_offset = self.inner.pixel_offset(pp); let mut values = SmallVec::with_capacity(desc.offset.len()); for &c in &desc.offset { - values.push(unsafe { self.inner.pixels.read(pixel_offset + c, &self.inner.encoding) }); + values.push(unsafe { + self.inner + .pixels + .read(pixel_offset + c, &self.inner.encoding) + }); } ImageChannelValues(values) } @@ -223,20 +256,29 @@ impl HostImage { } } + pub fn set_channel(&mut self, p: Point2i, values: &ImageChannelValues) { + self.inner.set_channel(p, values[i]); + } + pub fn select_channels(&self, desc: &ImageChannelDesc) -> Self { - let new_names: Vec = desc.offset.iter().map(|&i| self.channel_names[i].clone()).collect(); + let new_names: Vec = desc + .offset + .iter() + .map(|&i| self.channel_names[i].clone()) + .collect(); let res = self.inner.resolution(); let pixel_count = (res.x() * res.y()) as usize; let src_nc = self.inner.n_channels() as usize; let dst_nc = desc.offset.len(); - // Always produce f32 output for simplicity let mut dst = vec![0.0f32; pixel_count * dst_nc]; for i in 0..pixel_count { let src_offset = i * src_nc; for (out_idx, &in_c) in desc.offset.iter().enumerate() { dst[i * dst_nc + out_idx] = unsafe { - self.inner.pixels.read(src_offset + in_c, &self.inner.encoding) + self.inner + .pixels + .read(src_offset + in_c, &self.inner.encoding) }; } } @@ -256,7 +298,7 @@ impl HostImage { dist.as_mut_slice() .par_chunks_mut(width as usize) .enumerate() - .for_each(|(y, row)| { + .for_each(|(y, row): (usize, &mut [Float])| { let y = y as i32; for (x, out_val) in row.iter_mut().enumerate() { let x = x as i32; @@ -310,15 +352,18 @@ impl HostImage { for y in 0..res.y() { for x in 0..res.x() { - let v = self.get_channels_with_desc( - Point2i::new(x, y), desc, WrapMode::Clamp.into(), - ); + let v = + self.get_channels_with_desc(Point2i::new(x, y), desc, WrapMode::Clamp.into()); let v_ref = ref_img.get_channels_with_desc( - Point2i::new(x, y), &ref_desc, WrapMode::Clamp.into(), + Point2i::new(x, y), + &ref_desc, + WrapMode::Clamp.into(), ); for c in 0..n_channels { let se = square(v[c] as f64 - v_ref[c] as f64); - if se.is_infinite() { continue; } + if se.is_infinite() { + continue; + } sum_se[c] += se; if generate_mse_image { let idx = (y as usize * width + x as usize) * n_channels + c; diff --git a/src/core/image/ops.rs b/src/core/image/ops.rs index c50149b..d24ea3a 100644 --- a/src/core/image/ops.rs +++ b/src/core/image/ops.rs @@ -191,19 +191,19 @@ impl HostImage { PixelFormat::U8 => downsample_kernel( next.inner.pixels.as_u8_mut(), new_res, - &prev.inner, + &prev, internal_wrap, ), PixelFormat::F16 => downsample_kernel( next.inner.pixels.as_f16_mut(), new_res, - &prev.inner, + &prev, internal_wrap, ), PixelFormat::F32 => downsample_kernel( next.inner.pixels.as_f32_slice_mut(), new_res, - &prev.inner, + &prev, internal_wrap, ), } @@ -322,7 +322,7 @@ fn copy_rect_in_kernel( fn downsample_kernel( dst: &mut [T], dst_res: Point2i, - prev: &Image, + prev: &HostImage, wrap: WrapMode2D, ) { let w = dst_res.x() as usize; diff --git a/src/core/light.rs b/src/core/light.rs index a5a213c..4c9aaef 100644 --- a/src/core/light.rs +++ b/src/core/light.rs @@ -9,7 +9,7 @@ use shared::core::shape::Shape; use shared::spectra::DenselySampledSpectrum; use shared::core::spectrum::Spectrum; use shared::spectra::RGBColorSpace; -use shared::Transform; +use shared::{Ptr, Transform}; use std::sync::Arc; pub fn lookup_spectrum(s: &Spectrum) -> Arc { @@ -117,7 +117,7 @@ pub fn create_area_light( shape: &Shape, alpha_tex: &FloatTexture, colorspace: Option<&RGBColorSpace>, - arena: &mut Arena, + arena: &Arena, ) -> Result> { let light = crate::lights::diffuse::create( render_from_light, medium, parameters, loc, diff --git a/src/core/material.rs b/src/core/material.rs index befee49..e3608e5 100644 --- a/src/core/material.rs +++ b/src/core/material.rs @@ -1,5 +1,5 @@ use crate::Arena; -use crate::core::image::Image; +use crate::core::image::HostImage; use crate::utils::TextureParameterDictionary; use crate::utils::error::FileLoc; use anyhow::{Result, anyhow}; @@ -11,7 +11,7 @@ use std::sync::Arc; pub trait CreateMaterial: Sized { fn create( parameters: &TextureParameterDictionary, - normal_map: Option>, + normal_map: Option>, named_materials: &HashMap, loc: &FileLoc, arena: &Arena, @@ -22,7 +22,7 @@ pub trait MaterialFactory { fn create( name: &str, params: &TextureParameterDictionary, - normal_map: Option>, + normal_map: Option>, named_materials: &HashMap, loc: FileLoc, arena: &Arena, @@ -35,7 +35,7 @@ impl MaterialFactory for Material { fn create( name: &str, parameters: &TextureParameterDictionary, - normal_map: Option>, + normal_map: Option>, named_materials: &HashMap, loc: FileLoc, arena: &Arena, diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index 970dfcb..ae7cdad 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -3,7 +3,7 @@ use super::state::*; use crate::core::camera::CameraFactory; use crate::core::film::FilmFactory; use crate::core::filter::FilterFactory; -use crate::core::image::{io::ImageIO, Image}; +use crate::core::image::{HostImage, ImageIO}; use crate::core::material::MaterialFactory; use crate::core::primitive::{CreateGeometricPrimitive, CreateSimplePrimitive}; use crate::core::sampler::SamplerFactory; @@ -12,7 +12,7 @@ use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::utils::parallel::{run_async, AsyncJob}; use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary}; use crate::utils::resolve_filename; -use crate::{Arena, ArenaUpload, FileLoc, Upload}; +use crate::{Arena, ArenaUpload, FileLoc}; use anyhow::{anyhow, Result}; use parking_lot::Mutex; use shared::core::camera::{Camera, CameraTransform}; @@ -25,9 +25,9 @@ use shared::core::medium::{Medium, MediumInterface}; use shared::core::primitive::{AnimatedPrimitive, GeometricPrimitive, Primitive, SimplePrimitive}; use shared::core::sampler::Sampler; use shared::core::shape::Shape; -use shared::core::texture::SpectrumType; +use shared::core::texture::{GPUFloatTexture, SpectrumType}; use shared::spectra::RGBColorSpace; -use shared::utils::Ptr; +use shared::{Ptr, Transform}; use std::collections::HashMap; use std::sync::Arc; @@ -169,7 +169,6 @@ impl BasicScene { }); self.sampler_state.lock().job = Some(sampler_job); - let arena_camera = Arc::clone(&arena); let camera_film = Arc::clone(&film_instance); let scene_ptr = Arc::clone(self); let camera_job = run_async(move || { @@ -181,7 +180,7 @@ impl BasicScene { medium, camera_film, &camera.base.loc, - &arena_camera, + &arena, ) .map_err(|e| anyhow!("Failed to create camera: {}", e)) }); @@ -1027,7 +1026,7 @@ impl BasicScene { let filename_clone = filename.clone(); let job = run_async(move || { let path = std::path::Path::new(&filename_clone); - let immeta = Image::read(path, Some(LINEAR)).expect(&format!( + let immeta = HostImage::read(path, Some(LINEAR)).expect(&format!( "{}: normal map must contain R, G, B channels", filename_clone )); @@ -1051,7 +1050,7 @@ impl BasicScene { &self, state: &MaterialState, params: &ParameterDictionary, - ) -> Result>> { + ) -> Result>> { let filename = resolve_filename(¶ms.get_one_string("normalmap", "")?); if filename.is_empty() { return Ok(None); diff --git a/src/core/scene/state.rs b/src/core/scene/state.rs index 570dbc9..5350615 100644 --- a/src/core/scene/state.rs +++ b/src/core/scene/state.rs @@ -1,5 +1,5 @@ use super::{LightSceneEntity, SceneEntity, TextureSceneEntity}; -use crate::core::image::Image; +use crate::core::image::HostImage; use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::utils::parallel::AsyncJob; use anyhow::Result; @@ -22,8 +22,8 @@ pub struct TextureState { pub struct MaterialState { pub named_materials: Vec<(String, SceneEntity)>, pub materials: Vec, - pub normal_map_jobs: HashMap>>, - pub normal_maps: HashMap>, + pub normal_map_jobs: HashMap>>, + pub normal_maps: HashMap>, } #[derive(Debug, Default)] diff --git a/src/core/shape.rs b/src/core/shape.rs index 2a2c4cc..bebe9ce 100644 --- a/src/core/shape.rs +++ b/src/core/shape.rs @@ -116,8 +116,8 @@ impl ShapeFactory for Shape { global_store.push(host_arc.clone()); drop(global_store); - let n_tris = host_arc..n_triangles; - let mesh_ptr = Ptr::from(&host_arc); + let n_tris = host_arc.n_triangles; + let mesh_ptr = arena.alloc_arc(host_arc); let shapes: Vec> = (0..n_tris) .map(|i| { let tri_shape = Shape::Triangle(TriangleShape { diff --git a/src/core/texture.rs b/src/core/texture.rs index a39065b..7356680 100644 --- a/src/core/texture.rs +++ b/src/core/texture.rs @@ -1,5 +1,5 @@ use crate::textures::*; -use crate::utils::TextureParameterDictionary; +use crate::utils::{MIPMap, MIPMapFilterOptions, TextureParameterDictionary}; use crate::{Arena, FileLoc}; use anyhow::{anyhow, Result}; use enum_dispatch::enum_dispatch; @@ -8,8 +8,8 @@ use shared::core::geometry::Vector3f; use shared::core::image::WrapMode; use shared::core::texture::SpectrumType; use shared::core::texture::{ - CylindricalMapping, GPUFloatTexture, GPUSpectrumTexture, PlanarMapping, SphericalMapping, - TextureEvalContext, TextureMapping2D, UVMapping, + CylindricalMapping, PlanarMapping, SphericalMapping, TextureEvalContext, TextureMapping2D, + UVMapping, }; use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::textures::*; @@ -133,7 +133,10 @@ impl SpectrumTexture { invert: inner.base.invert, is_single_channel: inner.base.mipmap.is_single_channel(), color_space: arena.alloc( - inner.base.mipmap.color_space + inner + .base + .mipmap + .color_space .clone() .unwrap_or_else(crate::spectra::default_colorspace), ), @@ -262,4 +265,3 @@ pub struct TexInfo { pub wrap_mode: WrapMode, pub encoding: ColorEncoding, } - diff --git a/src/films/gbuffer.rs b/src/films/gbuffer.rs index 73ef227..04b1a57 100644 --- a/src/films/gbuffer.rs +++ b/src/films/gbuffer.rs @@ -1,9 +1,8 @@ use super::*; -use crate::core::film::{CreateFilmBase, PixelSensor}; -use crate::utils::containers::Array2D; +use crate::core::film::{CreateFilmBase, CreatePixelSensor}; +use shared::core::film::PixelSensor; use anyhow::{Result, anyhow}; use shared::core::film::{FilmBase, GBufferFilm}; -use shared::core::filter::FilterTrait; use shared::spectra::RGBColorSpace; use shared::utils::AnimatedTransform; use std::path::Path; @@ -16,13 +15,13 @@ impl CreateFilm for GBufferFilm { filter: Filter, camera_transform: Option, loc: &FileLoc, - _arena: &Arena, + arena: &Arena, ) -> Result { let colorspace = params.color_space.as_ref().unwrap(); let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?; let write_fp16 = params.get_one_bool("savefp16", true)?; - let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; - let film_base = FilmBase::create(params, filter, Some(&sensor.device()), loc)?; + let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc, arena)?; + let film_base = FilmBase::create(params, filter, Some(&sensor), loc)?; let filename = params.get_one_string("filename", "pbrt.exr")?; if Path::new(&filename).extension() != Some("exr".as_ref()) { diff --git a/src/films/rgb.rs b/src/films/rgb.rs index 61e6e44..cb41b95 100644 --- a/src/films/rgb.rs +++ b/src/films/rgb.rs @@ -1,11 +1,9 @@ use super::*; use crate::core::film::{CreateFilmBase, PixelSensor}; -use crate::utils::containers::Array2D; use crate::Arena; use anyhow::Result; use shared::core::camera::CameraTransform; use shared::core::film::{Film, FilmBase, RGBFilm, RGBPixel}; -use shared::core::filter::FilterTrait; use shared::spectra::RGBColorSpace; impl CreateFilm for RGBFilm { diff --git a/src/films/spectral.rs b/src/films/spectral.rs index 85fcd71..e24e3b3 100644 --- a/src/films/spectral.rs +++ b/src/films/spectral.rs @@ -1,16 +1,14 @@ use super::*; -use crate::core::film::{CreateFilmBase, PixelSensor}; +use crate::core::film::{CreateFilmBase, CreatePixelSensor}; use crate::{Arena, FileLoc, ParameterDictionary}; -use anyhow::{Result, anyhow}; -use shared::Float; +use anyhow::{anyhow, Result}; use shared::core::camera::CameraTransform; -use shared::core::film::{FilmBase, SpectralFilm}; -use shared::core::filter::FilterTrait; +use shared::core::film::{FilmBase, PixelSensor, SpectralFilm}; use shared::spectra::{LAMBDA_MAX, LAMBDA_MIN}; use shared::utils::math::SquareMatrix; +use shared::Float; use std::path::Path; - impl CreateFilm for SpectralFilm { fn create( params: &ParameterDictionary, @@ -18,14 +16,14 @@ impl CreateFilm for SpectralFilm { filter: Filter, _camera_transform: Option, loc: &FileLoc, - _arena: &Arena, + arena: &Arena, ) -> Result { // Missing default illuminant, use srgb let colorspace = params.color_space.as_ref().unwrap(); let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?; let write_fp16 = params.get_one_bool("savefp16", true)?; - let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; - let film_base = FilmBase::create(params, filter, Some(&sensor.device()), loc)?; + let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc, arena)?; + let film_base = FilmBase::create(params, filter, Some(&sensor), loc)?; let filename = params.get_one_string("filename", "pbrt.exr")?; if Path::new(&filename).extension() != Some("exr".as_ref()) { diff --git a/src/integrators/pipeline.rs b/src/integrators/pipeline.rs index 67f61de..5d5f21e 100644 --- a/src/integrators/pipeline.rs +++ b/src/integrators/pipeline.rs @@ -2,7 +2,7 @@ use super::base::IntegratorBase; use super::RayIntegratorTrait; use crate::core::camera::InitMetadata; use crate::core::film::FilmTrait; -use crate::core::image::{Image, ImageIO, ImageMetadata}; +use crate::core::image::{HostImage, ImageIO, ImageMetadata}; use crate::globals::get_options; use crate::spectra::get_spectra_context; use crate::Arena; @@ -108,12 +108,12 @@ pub fn render( let mut wave_end = 1; let mut next_wave_size = 1; - let mut reference_image: Option = None; + let mut reference_image: Option = None; let mut mse_out_file: Option = None; if let Some(ref_path) = &options.mse_reference_image { let image_and_metadata = - Image::read(Path::new(&ref_path), None).expect("Could not load image"); + HostImage::read(Path::new(&ref_path), None).expect("Could not load image"); let image = image_and_metadata.image; let metadata = image_and_metadata.metadata; let resolution = image.resolution(); diff --git a/src/lib.rs b/src/lib.rs index b9f6278..a2e091b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,6 @@ #[allow(dead_code)] pub mod core; pub mod films; -pub mod filters; pub mod globals; pub mod integrators; pub mod lights; diff --git a/src/lights/diffuse.rs b/src/lights/diffuse.rs index c75673c..ad863c4 100644 --- a/src/lights/diffuse.rs +++ b/src/lights/diffuse.rs @@ -1,6 +1,4 @@ -use std::path::Path; - -use crate::core::image::{Image, ImageIO}; +use crate::core::image::{HostImage, ImageIO}; use crate::core::light::lookup_spectrum; use crate::core::spectrum::spectrum_to_photometric; use crate::core::texture::FloatTexture; @@ -18,6 +16,7 @@ use shared::lights::DiffuseAreaLight; use shared::spectra::RGBColorSpace; use shared::utils::Transform; use shared::{Float, PI}; +use std::path::Path; pub fn create( render_from_light: Transform, @@ -37,13 +36,13 @@ pub fn create( let two_sided = params.get_one_bool("twosided", false)?; let filename = resolve_filename(¶ms.get_one_string("filename", "")?); - let (image, image_color_space): (Option, Option) = + let (image, image_color_space): (Option, Option) = if !filename.is_empty() { if l.is_some() { return Err(anyhow!("{}: both \"L\" and \"filename\" specified", loc)); } - let im = Image::read(Path::new(&filename), None)?; + let im = HostImage::read(Path::new(&filename), None)?; if im.image.has_any_infinite_pixels() { return Err(anyhow!("{}: image has infinite pixel values", loc)); @@ -100,12 +99,13 @@ pub fn create( scale *= phi_v / k_e; } - // Upload alpha texture to GPU and check for constant-zero + // Upload alpha texture to GPU and check for null texture let alpha_ptr = arena.upload(alpha); - let light_type = if alpha_ptr.is_constant_zero() { - LightType::DeltaPosition - } else { - LightType::Area + let light_type = match alpha_ptr.as_ref() { + GPUFloatTexture::Constant(t) if t.evaluate(&TextureEvalContext::default()) == 0.0 => { + LightType::DeltaPosition + } + _ => LightType::Area, }; let mi = match medium { @@ -146,7 +146,7 @@ pub fn create( area: shape.area(), shape: arena.alloc(*shape), alpha: alpha_ptr, - image: arena.alloc(image), + image: arena.upload(image), colorspace: arena.alloc_opt(image_color_space), lemit: arena.alloc((*lookup_spectrum(l_for_scale)).clone()), two_sided, diff --git a/src/lights/goniometric.rs b/src/lights/goniometric.rs index 376c613..18b144b 100644 --- a/src/lights/goniometric.rs +++ b/src/lights/goniometric.rs @@ -1,6 +1,6 @@ use std::path::Path; -use crate::core::image::{Image, ImageIO}; +use crate::core::image::{HostImage, ImageIO}; use crate::core::light::lookup_spectrum; use crate::core::spectrum::spectrum_to_photometric; use crate::core::texture::FloatTexture; @@ -41,10 +41,10 @@ pub fn create( .expect("Could not retrieve spectrum"); let mut scale = params.get_one_float("scale", 1.)?; let filename = resolve_filename(¶ms.get_one_string("filename", "")?); - let image: Ptr = if filename.is_empty() { + let image: Ptr = if filename.is_empty() { Ptr::null() } else { - let im = Image::read(Path::new(&filename), None) + let im = HostImage::read(Path::new(&filename), None) .map_err(|e| anyhow!("could not load image '{}': {}", filename, e))?; let loaded = im.image; @@ -117,7 +117,7 @@ pub fn create( Ok(Light::Goniometric(specific)) } -fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> Result { +fn convert_to_luminance_image(image: &HostImage, filename: &str, loc: &FileLoc) -> Result { let res = image.resolution(); let rgb_desc = image.get_channel_desc(&["R", "G", "B"]); let y_desc = image.get_channel_desc(&["Y"]); @@ -142,7 +142,7 @@ fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> R } } - Ok(Image::from_f32(y_pixels, res, &["Y"].to_vec())) + Ok(HostImage::from_f32(&y_pixels, res, &["Y"].to_vec())) } (Err(_), Ok(_)) => { @@ -158,7 +158,7 @@ fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> R } } -fn compute_emissive_power(image: &Image) -> Float { +fn compute_emissive_power(image: &HostImage) -> Float { let res = image.resolution(); let mut sum_y = 0.0; diff --git a/src/lights/infinite.rs b/src/lights/infinite.rs index b225893..f9dd0a7 100644 --- a/src/lights/infinite.rs +++ b/src/lights/infinite.rs @@ -11,11 +11,11 @@ use shared::core::camera::CameraTransform; use shared::core::geometry::{cos_theta, Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike}; use shared::core::image::WrapMode; use shared::core::light::{Light, LightBase, LightType}; -use shared::core::medium::{Medium, MediumInterface}; +use shared::core::medium::Medium; use shared::core::spectrum::Spectrum; use shared::core::texture::SpectrumType; use shared::lights::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLight}; -use shared::spectra::{DenselySampledSpectrum, RGBColorSpace}; +use shared::spectra::RGBColorSpace; use shared::utils::math::{equal_area_sphere_to_square, equal_area_square_to_sphere}; use shared::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D}; use shared::{Float, Ptr, Transform, PI}; @@ -63,7 +63,7 @@ pub fn create( } let lemit = lookup_spectrum(&spectrum); - let light = UniformInfiniteLight::new(render_from_light, scale, arena.alloc(*lemit)); + let light = UniformInfiniteLight::new(render_from_light, scale, arena.alloc_arc(lemit)); return Ok(Light::InfiniteUniform(light)); } @@ -96,7 +96,7 @@ pub fn create( fn create_image_light( render_from_light: Transform, scale: Float, - image: Image, + image: HostImage, image_cs: RGBColorSpace, arena: &Arena, ) -> Result { @@ -110,7 +110,6 @@ fn create_image_light( let (n_u, n_v) = (res.x() as usize, res.y() as usize); // Extract luminance data - let image_ptr = image.upload(arena); let value = ℑ let mut data: Vec = (0..n_v) .flat_map(|v| { @@ -139,7 +138,7 @@ fn create_image_light( let light = ImageInfiniteLight::new( render_from_light, scale, - image_ptr, + arena.upload(image), arena.alloc(image_cs), arena.alloc(distrib), arena.alloc(compensated_distrib), @@ -151,7 +150,7 @@ fn create_image_light( fn create_portal_light( render_from_light: Transform, scale: Float, - image: Image, + image: HostImage, image_cs: RGBColorSpace, portal_points: &[Point3f], camera_transform: CameraTransform, @@ -198,7 +197,7 @@ fn create_portal_light( let light = PortalInfiniteLight::new( render_from_light, scale, - arena.alloc(remapped), + arena.upload(remapped), arena.alloc(image_cs), portal, portal_frame, @@ -230,10 +229,10 @@ fn validate_and_build_portal_frame(portal: &[Point3f; 4], loc: &FileLoc) -> Resu } fn remap_image_through_portal( - image: &Image, + image: &HostImage, render_from_light: &Transform, portal_frame: &Frame, -) -> Image { +) -> HostImage { let res = image.resolution(); let (width, height) = (res.x() as usize, res.y() as usize); @@ -263,7 +262,7 @@ fn remap_image_through_portal( } }); - Image::from_f32(pixels, res, &["R", "G", "B"]) + HostImage::from_f32(&pixels, res, &["R", "G", "B"]) } fn load_image( @@ -271,16 +270,16 @@ fn load_image( l: &[Spectrum], colorspace: &RGBColorSpace, loc: &FileLoc, -) -> Result<(Image, RGBColorSpace)> { +) -> Result<(HostImage, RGBColorSpace)> { if filename.is_empty() { let stdspec = get_spectra_context(); let rgb = l[0].to_rgb(colorspace, &stdspec); let image = - Image::new_constant(Point2i::new(1, 1), &["R", "G", "B"], &[rgb.r, rgb.g, rgb.b]); + HostImage::new_constant(Point2i::new(1, 1), &["R", "G", "B"], &[rgb.r, rgb.g, rgb.b]); return Ok((image, colorspace.clone())); } - let im = Image::read(Path::new(filename), None) + let im = HostImage::read(Path::new(filename), None) .map_err(|e| anyhow!("failed to load '{}': {}", filename, e))?; if im.image.has_any_infinite_pixels() || im.image.has_any_nan_pixels() { @@ -296,7 +295,7 @@ fn load_image( Ok((im.image.select_channels(&desc), cs)) } -fn compute_hemisphere_illuminance(image: &Image, cs: &RGBColorSpace) -> Float { +fn compute_hemisphere_illuminance(image: &HostImage, cs: &RGBColorSpace) -> Float { let lum = cs.luminance_vector(); let res = image.resolution(); diff --git a/src/lights/projection.rs b/src/lights/projection.rs index 00fe486..8e2f220 100644 --- a/src/lights/projection.rs +++ b/src/lights/projection.rs @@ -1,9 +1,9 @@ -use crate::core::image::{Image, ImageIO}; +use crate::core::image::{HostImage, ImageIO}; use crate::core::spectrum::spectrum_to_photometric; use crate::core::texture::FloatTexture; use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::resolve_filename; -use crate::{Arena, FileLoc, ParameterDictionary}; +use crate::{Arena, FileLoc, ParameterDictionary, ArenaUpload}; use anyhow::{Result, anyhow}; use shared::Float; use shared::core::geometry::{ @@ -41,7 +41,7 @@ pub fn create( )); } - let im = Image::read(Path::new(&filename), None) + let im = HostImage::read(Path::new(&filename), None) .map_err(|e| anyhow!("{}: could not load image '{}': {}", loc, filename, e))?; if im.image.has_any_infinite_pixels() { @@ -125,9 +125,9 @@ pub fn create( let specific = ProjectionLight { base, - image: image.upload(arena), - image_color_space: colorspace.upload(arena), - distrib: distrib.upload(arena), + image: arena.upload(image), + image_color_space: arena.alloc(colorspace), + distrib: arena.alloc(distrib), screen_bounds, screen_from_light, light_from_screen, @@ -150,7 +150,7 @@ fn compute_screen_bounds(aspect: Float) -> Bounds2f { } } -fn compute_emissive_power(image: &Image, colorspace: &RGBColorSpace, fov: Float) -> Float { +fn compute_emissive_power(image: &HostImage, colorspace: &RGBColorSpace, fov: Float) -> Float { let res = image.resolution(); let aspect = res.x() as Float / res.y() as Float; let screen_bounds = compute_screen_bounds(aspect); diff --git a/src/lights/spot.rs b/src/lights/spot.rs index 5914ff0..1d6ed8f 100644 --- a/src/lights/spot.rs +++ b/src/lights/spot.rs @@ -12,7 +12,6 @@ use shared::core::texture::SpectrumType; use shared::lights::SpotLight; use shared::spectra::RGBColorSpace; use shared::utils::math::radians; -use shared::utils::{Ptr, Transform}; use shared::{Float, Ptr, Transform, PI}; trait CreateSpotLight { diff --git a/src/materials/coated.rs b/src/materials/coated.rs index a584ac3..4b388b6 100644 --- a/src/materials/coated.rs +++ b/src/materials/coated.rs @@ -4,7 +4,7 @@ use crate::core::texture::SpectrumTexture; use crate::globals::get_options; use crate::spectra::data::get_named_spectrum; use crate::utils::TextureParameterDictionary; -use crate::{Arena, FileLoc, Upload, ArenaUpload}; +use crate::{Arena, FileLoc, ArenaUpload}; use anyhow::{bail, Result}; use shared::core::material::Material; use shared::core::spectrum::Spectrum; @@ -64,7 +64,7 @@ impl CreateMaterial for CoatedDiffuseMaterial { arena.upload(g), arena.upload(displacement), arena.alloc(eta), - arena.alloc(normal_map), + arena.upload(normal_map), remap_roughness, max_depth as u32, n_samples as u32, @@ -154,7 +154,7 @@ impl CreateMaterial for CoatedConductorMaterial { let remap_roughness = parameters.get_one_bool("remaproughness", true)?; let material = Self::new( - arena.upload(displacement) + arena.upload(displacement), arena.upload(interface_u_roughness), arena.upload(interface_v_roughness), arena.upload(thickness), @@ -165,7 +165,7 @@ impl CreateMaterial for CoatedConductorMaterial { arena.upload(conductor_eta), arena.upload(k), arena.upload(reflectance), - arena.alloc(normal_map), + arena.upload(normal_map), arena.alloc(interface_eta), max_depth as u32, n_samples as u32, diff --git a/src/materials/complex.rs b/src/materials/complex.rs index 16990e9..e69bdea 100644 --- a/src/materials/complex.rs +++ b/src/materials/complex.rs @@ -3,7 +3,7 @@ use crate::core::material::CreateMaterial; use crate::core::texture::SpectrumTexture; use crate::spectra::get_colorspace_device; use crate::utils::TextureParameterDictionary; -use crate::{Arena, FileLoc, Upload, ArenaUpload}; +use crate::{Arena, ArenaUpload, FileLoc}; use anyhow::Result; use shared::bxdfs::HairBxDF; use shared::core::material::Material; @@ -60,7 +60,6 @@ impl CreateMaterial for HairMaterial { } } - impl CreateMaterial for SubsurfaceMaterial { fn create( _parameters: &TextureParameterDictionary, diff --git a/src/shapes/bilinear.rs b/src/shapes/bilinear.rs index e962255..f1a5e12 100644 --- a/src/shapes/bilinear.rs +++ b/src/shapes/bilinear.rs @@ -1,4 +1,4 @@ -use crate::core::image::{Image, ImageIO}; +use crate::core::image::{HostImage, ImageIO}; use crate::core::shape::{CreateShape, ALL_BILINEAR_MESHES}; use crate::core::texture::FloatTexture; use crate::utils::sampling::PiecewiseConstant2D; @@ -6,7 +6,7 @@ use crate::{Arena, FileLoc, ParameterDictionary}; use anyhow::{anyhow, Result}; use log::warn; use shared::core::shape::Shape; -use shared::shapes::BilinearPatchShape; +use shared::shapes::{BilinearPatchMesh, BilinearPatchShape}; use shared::{Ptr, Transform}; use std::collections::HashMap; use std::path::Path; @@ -92,7 +92,7 @@ impl CreateShape for BilinearPatchShape { ); None } else { - let im = Image::read(Path::new(&filename), None)?; + let im = HostImage::read(Path::new(&filename), None)?; let mut img = im.image; img.flip_y(); Some(PiecewiseConstant2D::from_image(&img)) @@ -104,10 +104,10 @@ impl CreateShape for BilinearPatchShape { let host = BilinearPatchMesh::new( &render_from_object, reverse_orientation, - vertex_indices, - p, - n, - uv, + &vertex_indices, + &p, + &n, + &uv, image_dist, ); @@ -116,8 +116,8 @@ impl CreateShape for BilinearPatchShape { // let mesh_index = global_store.len() as u32; global_store.push(host_arc.clone()); drop(global_store); - let n_patches = host_arc.device.n_patches; - let mesh_ptr = Ptr::from(&host_arc.device); + let n_patches = host_arc.n_patches; + let mesh_ptr = arena.alloc_arc(host_arc); let mut shapes = Vec::with_capacity(n_patches as usize); for i in 0..n_patches as i32 { shapes.push(arena.alloc(Shape::BilinearPatch(BilinearPatchShape { diff --git a/src/shapes/mesh.rs b/src/shapes/mesh.rs index b4a9a0c..952f6b5 100644 --- a/src/shapes/mesh.rs +++ b/src/shapes/mesh.rs @@ -1,11 +1,10 @@ use crate::utils::sampling::PiecewiseConstant2D; -use crate::Arena; use anyhow::{bail, Context, Result as AnyResult}; use ply_rs::parser::Parser; use ply_rs::ply::{DefaultElement, Property}; -use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f, VectorLike}; -use shared::shapes::mesh::{BilinearPatchMesh, TriangleMesh}; -use shared::{Ptr, Transform}; +use shared::core::geometry::{Normal3f, Point2f, Point3f, VectorLike}; +use shared::shapes::mesh::TriangleMesh; +use shared::Transform; use std::fs::File; use std::path::Path; @@ -269,11 +268,13 @@ impl TriQuadMesh { } pub trait ReadTriangleMesh { - pub fn from_ply>( + fn from_ply>( filename: P, render_from_object: &Transform, reverse_orientation: bool, - ) -> AnyResult; + ) -> AnyResult + where + Self: Sized; } impl ReadTriangleMesh for TriangleMesh { diff --git a/src/shapes/triangle.rs b/src/shapes/triangle.rs index 593af84..3071ff1 100644 --- a/src/shapes/triangle.rs +++ b/src/shapes/triangle.rs @@ -99,8 +99,8 @@ impl CreateShape for TriangleShape { // let mesh_index = global_store.len() as u32; global_store.push(host_arc.clone()); drop(global_store); - let n_patches = host_arc.device.n_triangles; - let mesh_ptr = Ptr::from(&host_arc.device); + let n_patches = host_arc.n_triangles; + let mesh_ptr = arena.alloc_arc(host_arc); let mut shapes = Vec::with_capacity(n_patches as usize); for i in 0..n_patches { shapes.push(arena.alloc(Shape::Triangle(TriangleShape { diff --git a/src/spectra/mod.rs b/src/spectra/mod.rs index ed353e5..24c030e 100644 --- a/src/spectra/mod.rs +++ b/src/spectra/mod.rs @@ -6,8 +6,7 @@ use shared::core::spectrum::{Spectrum, StandardSpectra}; use shared::spectra::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z}; use shared::spectra::{DenselySampledSpectrum, DeviceStandardColorSpaces, RGBColorSpace}; use shared::Ptr; -use std::sync::Arc; -use std::sync::LazyLock; +use std::sync::{Arc, OnceLock, LazyLock}; pub mod colorspace; pub mod data; @@ -68,7 +67,7 @@ pub static DCI_P3: LazyLock> = LazyLock::new(|| { Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr)) }); -pub static REC2020: LazyLock> = LazyLock::new(|| { +pub static REC2020: LazyLock> = LazyLock::new(|| { let illum = get_d65_illuminant_buffer(); let r = Point2f::new(0.708, 0.292); let g = Point2f::new(0.170, 0.797); @@ -77,7 +76,7 @@ pub static REC2020: LazyLock> = LazyLock::new(|| { Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr)) }); -pub static ACES: LazyLock> = LazyLock::new(|| { +pub static ACES: LazyLock> = LazyLock::new(|| { let illum = get_d65_illuminant_buffer(); let r = Point2f::new(0.7347, 0.2653); let g = Point2f::new(0.0000, 1.0000); @@ -88,10 +87,10 @@ pub static ACES: LazyLock> = LazyLock::new(|| { #[derive(Debug, Clone)] pub struct StandardColorSpaces { - pub srgb: Arc, - pub dci_p3: Arc, - pub rec2020: Arc, - pub aces2065_1: Arc, + pub srgb: Arc, + pub dci_p3: Arc, + pub rec2020: Arc, + pub aces2065_1: Arc, } impl StandardColorSpaces { @@ -141,3 +140,4 @@ pub fn default_colorspace_ref() -> &'static RGBColorSpace { pub fn default_illuminant() -> Spectrum { Spectrum::Dense(default_colorspace().illuminant) } + diff --git a/src/textures/mix.rs b/src/textures/mix.rs index 01a8749..a17c644 100644 --- a/src/textures/mix.rs +++ b/src/textures/mix.rs @@ -7,10 +7,6 @@ use anyhow::Result; use shared::core::geometry::{Vector3f, VectorLike}; use shared::core::texture::{SpectrumType, TextureEvalContext}; use shared::spectra::{SampledSpectrum, SampledWavelengths}; -use shared::textures::{ - GPUFloatDirectionMixTexture, GPUFloatMixTexture, GPUSpectrumDirectionMixTexture, - GPUSpectrumMixTexture, -}; use shared::utils::Transform; use shared::Float; use std::sync::Arc; diff --git a/src/textures/scaled.rs b/src/textures/scaled.rs index ba43763..9adc5a7 100644 --- a/src/textures/scaled.rs +++ b/src/textures/scaled.rs @@ -5,7 +5,6 @@ use crate::Arena; use anyhow::Result; use shared::core::texture::{SpectrumType, TextureEvalContext}; use shared::spectra::{SampledSpectrum, SampledWavelengths}; -use shared::textures::{GPUFloatScaledTexture, GPUSpectrumScaledTexture}; use shared::utils::Transform; use shared::Float; use std::sync::Arc; diff --git a/src/utils/arena.rs b/src/utils/arena.rs index 426c9d0..af1a49a 100644 --- a/src/utils/arena.rs +++ b/src/utils/arena.rs @@ -148,6 +148,13 @@ impl Arena { } } + pub fn alloc_arc(&self, value: Arc) -> Ptr { + match Arc::try_unwrap(value) { + Ok(inner) => self.alloc(inner), + Err(arc) => self.alloc((*arc).clone()), + } + } + pub fn alloc_slice(&self, values: &[T]) -> (Ptr, usize) { let mut bump = self.bump.lock(); let (ptr, len) = bump.alloc_slice(values); diff --git a/src/utils/mipmap.rs b/src/utils/mipmap.rs index db56acb..82efb1d 100644 --- a/src/utils/mipmap.rs +++ b/src/utils/mipmap.rs @@ -1,10 +1,10 @@ -use crate::core::image::{Image, ImageIO}; -use shared::Float; +use crate::core::image::{HostImage, ImageIO}; use shared::core::color::{ColorEncoding, RGB}; use shared::core::geometry::{Point2f, Point2i, Vector2f, VectorLike}; use shared::core::image::{WrapMode, WrapMode2D}; use shared::spectra::RGBColorSpace; use shared::utils::math::{lerp, safe_sqrt, square}; +use shared::Float; use std::hash::{Hash, Hasher}; use std::ops::{Add, Mul, Sub}; use std::path::Path; @@ -69,18 +69,18 @@ pub trait MIPMapSample: Copy + Add + Sub + Mul + std::fmt::Debug { fn zero() -> Self; - fn sample_bilerp(image: &Image, st: Point2f, wrap: WrapMode2D) -> Self; - fn sample_texel(image: &Image, st: Point2i, wrap: WrapMode2D) -> Self; + fn sample_bilerp(image: &HostImage, st: Point2f, wrap: WrapMode2D) -> Self; + fn sample_texel(image: &HostImage, st: Point2i, wrap: WrapMode2D) -> Self; } impl MIPMapSample for Float { fn zero() -> Self { 0. } - fn sample_bilerp(image: &Image, st: Point2f, wrap: WrapMode2D) -> Self { + fn sample_bilerp(image: &HostImage, st: Point2f, wrap: WrapMode2D) -> Self { image.bilerp_channel_with_wrap(st, 0, wrap) } - fn sample_texel(image: &Image, st: Point2i, wrap: WrapMode2D) -> Self { + fn sample_texel(image: &HostImage, st: Point2i, wrap: WrapMode2D) -> Self { image.get_channel_with_wrap(st, 0, wrap) } } @@ -89,7 +89,7 @@ impl MIPMapSample for RGB { fn zero() -> Self { RGB::new(0., 0., 0.) } - fn sample_bilerp(image: &Image, st: Point2f, wrap: WrapMode2D) -> Self { + fn sample_bilerp(image: &HostImage, st: Point2f, wrap: WrapMode2D) -> Self { let nc = image.n_channels(); if nc >= 3 { let r = image.bilerp_channel_with_wrap(st, 0, wrap); @@ -101,7 +101,7 @@ impl MIPMapSample for RGB { RGB::new(v, v, v) } } - fn sample_texel(image: &Image, st: Point2i, wrap: WrapMode2D) -> Self { + fn sample_texel(image: &HostImage, st: Point2i, wrap: WrapMode2D) -> Self { let nc = image.n_channels(); if nc >= 3 { let r = image.get_channel_with_wrap(st, 0, wrap); @@ -117,7 +117,7 @@ impl MIPMapSample for RGB { #[derive(Clone, Debug)] pub struct MIPMap { - pub pyramid: Vec, + pub pyramid: Vec, pub color_space: Option, pub wrap_mode: WrapMode, pub options: MIPMapFilterOptions, @@ -127,12 +127,12 @@ pub struct MIPMap { impl MIPMap { pub fn new( - image: Image, + image: HostImage, color_space: Option, wrap_mode: WrapMode, options: MIPMapFilterOptions, ) -> Self { - let pyramid = Image::generate_pyramid(image, wrap_mode); + let pyramid = HostImage::generate_pyramid(image, wrap_mode); Self { pyramid, color_space, @@ -160,11 +160,11 @@ impl MIPMap { self.color_space.clone() } - pub fn get_level(&self, level: usize) -> &Image { + pub fn get_level(&self, level: usize) -> &HostImage { &self.pyramid[level] } - pub fn base_image(&self) -> &Image { + pub fn base_image(&self) -> &HostImage { &self.pyramid[0] } @@ -321,7 +321,7 @@ impl MIPMap { wrap_mode: WrapMode, encoding: ColorEncoding, ) -> Result { - let image_and_metadata = Image::read(filename, Some(encoding)).unwrap(); + let image_and_metadata = HostImage::read(filename, Some(encoding)).unwrap(); let image = image_and_metadata.image; Ok(MIPMap::new( image, @@ -345,7 +345,7 @@ impl MIPMap { } #[cfg(feature = "cuda")] -fn create_cuda_texture(pyramid: &[Image], wrap_mode: WrapMode) -> u64 { +fn create_cuda_texture(pyramid: &[HostImage], wrap_mode: WrapMode) -> u64 { use cuda_runtime_sys::*; let base = &pyramid[0]; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 8051eb9..08a570d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -8,7 +8,6 @@ pub mod mipmap; pub mod parallel; pub mod parameters; pub mod parser; -pub mod sampling; pub mod strings; pub mod upload; @@ -18,6 +17,7 @@ pub use parameters::{ ParameterDictionary, ParsedParameter, ParsedParameterVector, TextureParameterDictionary, }; pub use strings::*; +pub use mipmap::{MIPMap, MIPMapFilterOptions}; pub use upload::{Upload, ArenaUpload}; #[cfg(feature = "vulkan")] diff --git a/src/utils/parameters.rs b/src/utils/parameters.rs index eb0df57..fdc58c3 100644 --- a/src/utils/parameters.rs +++ b/src/utils/parameters.rs @@ -12,7 +12,7 @@ use shared::spectra::{ PiecewiseLinearSpectrum, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, RGBUnboundedSpectrum, }; -use shared::Float; +use shared::{gvec_from_slice, leak, Float}; use std::collections::HashMap; use std::sync::{ @@ -682,8 +682,8 @@ impl ParameterDictionary { .unzip(); vec![Spectrum::Piecewise(leak(PiecewiseLinearSpectrum { - lambdas: gvec_from_slice(lambdas), - values: gvec_from_slice(values), + lambdas: gvec_from_slice(&lambdas), + values: gvec_from_slice(&values), count: lambdas.len() as u32, }))] } diff --git a/src/utils/upload.rs b/src/utils/upload.rs index ca89112..3cb9490 100644 --- a/src/utils/upload.rs +++ b/src/utils/upload.rs @@ -1,4 +1,6 @@ use crate::core::texture::{FloatTexture, SpectrumTexture}; +use crate::core::image::HostImage; +use shared::core::image::Image; use crate::Arena; use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture}; use shared::textures::*; @@ -149,6 +151,27 @@ impl Upload for Option> { } } +impl Upload for HostImage { + type Target = Ptr; + fn upload(self, arena: &Arena) -> Ptr { + arena.alloc(self.inner) + } +} + +impl Upload for Option { + type Target = Ptr; + fn upload(self, arena: &Arena) -> Ptr { + arena.alloc_opt(self.map(|h| h.inner)) + } +} + +impl Upload for Option> { + type Target = Ptr; + fn upload(self, arena: &Arena) -> Ptr { + arena.alloc_opt(self.map(|h| h.as_ref().inner.clone())) + } +} + pub trait ArenaUpload { fn upload(&self, value: T) -> T::Target; }