Continuing cleanup

This commit is contained in:
Wito Wiala 2026-05-20 20:14:58 +01:00
parent 384a0019d8
commit 72acb8ccdf
40 changed files with 369 additions and 293 deletions

View file

@ -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] { pub fn as_u8_mut(&mut self) -> &mut [u8] {
&mut self.data &mut self.data
} }

View file

@ -12,4 +12,4 @@ pub use cylinder::*;
pub use disk::*; pub use disk::*;
pub use sphere::*; pub use sphere::*;
pub use triangle::*; pub use triangle::*;
pub use mesh::*; pub use mesh::{TriangleMesh, BilinearPatchMesh};

View file

@ -3,7 +3,8 @@ use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{SampledSpectrum, SampledWavelengths, N_SPECTRUM_SAMPLES}; use crate::spectra::{SampledSpectrum, SampledWavelengths, N_SPECTRUM_SAMPLES};
use crate::utils::find_interval; 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 core::hash::{Hash, Hasher};
use num_traits::Float as NumFloat; 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) { pub fn scale(&mut self, s: Float) {
for v in &mut self.values { for v in &mut self.values {
*v *= s; *v *= s;
@ -183,7 +219,6 @@ impl PiecewiseLinearSpectrum {
self.count.try_into().unwrap() self.count.try_into().unwrap()
} }
#[inline(always)] #[inline(always)]
pub fn lambda(&self, idx: u32) -> Float { pub fn lambda(&self, idx: u32) -> Float {
unsafe { *self.lambdas.as_ptr().add(idx as usize) } unsafe { *self.lambdas.as_ptr().add(idx as usize) }

View file

@ -1,5 +1,5 @@
use rayon::prelude::*; 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::geometry::{Bounds3f, Point3f, Ray, Vector3f};
use shared::core::primitive::{Primitive, PrimitiveTrait}; use shared::core::primitive::{Primitive, PrimitiveTrait};
use shared::core::shape::ShapeIntersection; use shared::core::shape::ShapeIntersection;
@ -890,8 +890,6 @@ impl<P: PrimitiveTrait + Clone + Send + Sync> BVHAggregate<P> {
impl BVHAggregate<Primitive> { impl BVHAggregate<Primitive> {
pub fn to_device(&self, arena: &mut Arena) -> DeviceBVHAggregate { pub fn to_device(&self, arena: &mut Arena) -> DeviceBVHAggregate {
let (prims_ptr, _) = arena.alloc_slice(&self.primitives);
let shared_nodes: Vec<shared::core::aggregates::LinearBVHNode> = self.nodes let shared_nodes: Vec<shared::core::aggregates::LinearBVHNode> = self.nodes
.iter() .iter()
.map(|n| shared::core::aggregates::LinearBVHNode { .map(|n| shared::core::aggregates::LinearBVHNode {
@ -902,13 +900,12 @@ impl BVHAggregate<Primitive> {
pad: 0, pad: 0,
}) })
.collect(); .collect();
let (nodes_ptr, _) = arena.alloc_slice(&shared_nodes);
DeviceBVHAggregate { DeviceBVHAggregate {
max_prims_in_node: self.max_prims_in_node as u32, 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, primitive_count: self.primitives.len() as u32,
nodes: nodes_ptr, nodes: gvec_from_slice(&self.shared_nodes),
node_count: self.nodes.len() as u32, node_count: self.nodes.len() as u32,
} }
} }

View file

@ -1,8 +1,7 @@
use crate::core::image::ImageMetadata; use crate::core::image::{HostImage, ImageIO, ImageMetadata};
use crate::core::image::{Image, ImageIO};
use crate::globals::get_options; use crate::globals::get_options;
use crate::utils::read_float_file; use crate::utils::read_float_file;
use crate::{Arena, FileLoc, ParameterDictionary}; use crate::{Arena, FileLoc, ParameterDictionary, ArenaUpload};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use shared::cameras::*; use shared::cameras::*;
use shared::core::camera::{Camera, CameraBase, CameraTrait, CameraTransform}; use shared::core::camera::{Camera, CameraBase, CameraTrait, CameraTransform};
@ -232,8 +231,8 @@ impl CameraFactory for Camera {
} }
let builtin_res = 256; let builtin_res = 256;
let rasterize = |vert: &[Point2f]| -> Image { let rasterize = |vert: &[Point2f]| -> HostImage {
let mut image = Image::new( let mut image = HostImage::new(
PixelFormat::F32, PixelFormat::F32,
Point2i::new(builtin_res, builtin_res), Point2i::new(builtin_res, builtin_res),
&["Y"], &["Y"],
@ -279,12 +278,12 @@ impl CameraFactory for Camera {
}; };
let aperture_name = params.get_one_string("aperture", "")?; let aperture_name = params.get_one_string("aperture", "")?;
let mut aperture_image: Option<Image> = None; let mut aperture_image: Option<HostImage> = None;
if !aperture_name.is_empty() { if !aperture_name.is_empty() {
match aperture_name.as_str() { match aperture_name.as_str() {
"gaussian" => { "gaussian" => {
let mut img = Image::new( let mut img = HostImage::new(
PixelFormat::F32, PixelFormat::F32,
Point2i::new(builtin_res, builtin_res), Point2i::new(builtin_res, builtin_res),
&["Y"], &["Y"],
@ -306,7 +305,7 @@ impl CameraFactory for Camera {
aperture_image = Some(img); aperture_image = Some(img);
} }
"square" => { "square" => {
let mut img = Image::new( let mut img = HostImage::new(
PixelFormat::F32, PixelFormat::F32,
Point2i::new(builtin_res, builtin_res), Point2i::new(builtin_res, builtin_res),
&["Y"], &["Y"],
@ -316,7 +315,7 @@ impl CameraFactory for Camera {
let high = (0.75 * builtin_res as Float) as i32; let high = (0.75 * builtin_res as Float) as i32;
for y in low..high { for y in low..high {
for x 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); aperture_image = Some(img);
@ -353,9 +352,9 @@ impl CameraFactory for Camera {
aperture_image = Some(rasterize(&vert)); 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 { if im.image.n_channels() > 1 {
let mut mono = Image::new( let mut mono = HostImage::new(
PixelFormat::F32, PixelFormat::F32,
im.image.resolution(), im.image.resolution(),
&["Y"], &["Y"],
@ -366,7 +365,7 @@ impl CameraFactory for Camera {
for x in 0..res.x() { for x in 0..res.x() {
let avg = let avg =
im.image.get_channels(Point2i::new(x, y)).average(); 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); aperture_image = Some(mono);
@ -383,7 +382,8 @@ impl CameraFactory for Camera {
&lens_params, &lens_params,
focal_distance, focal_distance,
aperture_diameter, aperture_diameter,
Ptr::from(&*aperture_image.unwrap()), arena.upload(aperture_image)
); );
arena.alloc(camera); arena.alloc(camera);

View file

@ -15,6 +15,7 @@ impl CreateRGBToSpectrumTable for RGBToSpectrumTable {
assert_eq!(z_nodes.len(), RES as usize); assert_eq!(z_nodes.len(), RES as usize);
assert_eq!(coeffs.len(), (RES * RES * RES) as usize * 3 * 3); assert_eq!(coeffs.len(), (RES * RES * RES) as usize * 3 * 3);
Self { Self {
n_nodes: z_nodes.len().try_into().unwrap(),
z_nodes: gvec_from_slice(z_nodes), z_nodes: gvec_from_slice(z_nodes),
coeffs: gvec_from_slice(unsafe { coeffs: gvec_from_slice(unsafe {
core::slice::from_raw_parts( core::slice::from_raw_parts(

View file

@ -87,8 +87,10 @@ impl CreatePixelSensor for PixelSensor {
DenselySampledSpectrum::generate_cie_d(white_balance_temp) DenselySampledSpectrum::generate_cie_d(white_balance_temp)
}; };
let d_ptr = arena.alloc(d_illum);
let sensor_illum: Option<Arc<Spectrum>> = if white_balance_temp != 0. { let sensor_illum: Option<Arc<Spectrum>> = if white_balance_temp != 0. {
Some(Spectrum::Dense(d_illum.device()).into()) Some(Spectrum::Dense(d_ptr).into())
} else { } else {
None None
}; };
@ -150,8 +152,8 @@ impl CreatePixelSensor for PixelSensor {
let g_bar = DenselySampledSpectrum::from_spectrum(g); let g_bar = DenselySampledSpectrum::from_spectrum(g);
let b_bar = DenselySampledSpectrum::from_spectrum(b); let b_bar = DenselySampledSpectrum::from_spectrum(b);
let r_ptr = arena.alloc(r_bar); let r_ptr = arena.alloc(r_bar);
let g_ptr = arena.alloc(r_bar); let g_ptr = arena.alloc(g_bar);
let v_ptr = arena.alloc(r_bar); let b_ptr = arena.alloc(b_bar);
let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES]; let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES];
let swatches = get_swatches(); let swatches = get_swatches();
@ -203,6 +205,7 @@ impl CreatePixelSensor for PixelSensor {
output_colorspace: &RGBColorSpace, output_colorspace: &RGBColorSpace,
sensor_illum: Option<&Spectrum>, sensor_illum: Option<&Spectrum>,
imaging_ratio: Float, imaging_ratio: Float,
arena: &Arena
) -> Self { ) -> Self {
let spectra = get_spectra_context(); let spectra = get_spectra_context();
let r_bar = CIE_X_DATA.clone(); let r_bar = CIE_X_DATA.clone();

View file

@ -1,7 +1,7 @@
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::{FileLoc, ParameterDictionary}; use crate::utils::{FileLoc, ParameterDictionary};
use crate::Arena; use crate::Arena;
use anyhow::{anyhow, Result}; use anyhow::{bail, Result};
use shared::core::filter::Filter; use shared::core::filter::Filter;
use shared::core::geometry::{Bounds2f, Point2f, Vector2f}; use shared::core::geometry::{Bounds2f, Point2f, Vector2f};
use shared::filters::*; use shared::filters::*;
@ -28,13 +28,14 @@ impl FilterFactory for Filter {
let xw = params.get_one_float("xradius", 0.5)?; let xw = params.get_one_float("xradius", 0.5)?;
let yw = params.get_one_float("yradius", 0.5)?; let yw = params.get_one_float("yradius", 0.5)?;
let filter = BoxFilter::new(Vector2f::new(xw, yw)); let filter = BoxFilter::new(Vector2f::new(xw, yw));
Ok(Filter::Box(filter)) Ok(Filter::Box(arena.alloc(filter)))
} }
"gaussian" => { "gaussian" => {
let xw = params.get_one_float("xradius", 1.5)?; let xw = params.get_one_float("xradius", 1.5)?;
let yw = params.get_one_float("yradius", 1.5)?; let yw = params.get_one_float("yradius", 1.5)?;
let sigma = params.get_one_float("sigma", 0.5)?; let sigma = params.get_one_float("sigma", 0.5)?;
let filter = GaussianFilter::new(Vector2f::new(xw, yw), sigma); let filter = GaussianFilter::new(Vector2f::new(xw, yw), sigma);
Ok(Filter::Gaussian(arena.alloc(filter)))
} }
"mitchell" => { "mitchell" => {
let xw = params.get_one_float("xradius", 2.)?; 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 b = params.get_one_float("B", 1. / 3.)?;
let c = params.get_one_float("C", 1. / 3.)?; let c = params.get_one_float("C", 1. / 3.)?;
let filter = MitchellFilter::new(Vector2f::new(xw, yw), b, c); let filter = MitchellFilter::new(Vector2f::new(xw, yw), b, c);
Ok(Filter::Mitchell(filter)) Ok(Filter::Mitchell(arena.alloc(filter)))
} }
"sinc" => { "sinc" => {
let xw = params.get_one_float("xradius", 4.)?; let xw = params.get_one_float("xradius", 4.)?;
let yw = params.get_one_float("yradius", 4.)?; let yw = params.get_one_float("yradius", 4.)?;
let tau = params.get_one_float("tau", 3.)?; let tau = params.get_one_float("tau", 3.)?;
let filter = LanczosSincFilter::new(Vector2f::new(xw, yw), tau); let filter = LanczosSincFilter::new(Vector2f::new(xw, yw), tau);
Ok(Filter::LanczosSinc(filter)) Ok(Filter::LanczosSinc(arena.alloc(filter)))
} }
"triangle" => { "triangle" => {
let xw = params.get_one_float("xradius", 2.)?; let xw = params.get_one_float("xradius", 2.)?;
let yw = params.get_one_float("yradius", 2.)?; let yw = params.get_one_float("yradius", 2.)?;
let filter = TriangleFilter::new(Vector2f::new(xw, yw)); 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),
} }
} }
} }

View file

@ -1,15 +1,14 @@
use super::{HostImage, ImageAndMetadata, ImageMetadata}; use super::{HostImage, ImageAndMetadata, ImageMetadata};
use crate::core::image::{PixelStorage, WrapMode}; use crate::core::image::WrapMode;
use crate::utils::error::ImageError;
use anyhow::{Context, Result, bail}; use anyhow::{Context, Result, bail};
use exr::prelude::{read_first_rgba_layer_from_file, write_rgba_file}; use exr::prelude::{read_first_rgba_layer_from_file, write_rgba_file};
use image_rs::{DynamicImage, ImageReader};
use shared::Float; use shared::Float;
use shared::core::color::{ColorEncoding, LINEAR, SRGB}; use shared::core::color::{ColorEncoding, LINEAR, SRGB};
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
use shared::core::image::PixelFormat; use shared::core::image::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 image_rs::{DynamicImage, ImageReader};
use std::path::Path; use std::path::Path;
pub trait ImageIO { pub trait ImageIO {
@ -40,55 +39,39 @@ impl ImageIO for HostImage {
fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<()> { fn write(&self, filename: &str, metadata: &ImageMetadata) -> Result<()> {
let path = Path::new(filename); let path = Path::new(filename);
let ext = path.extension().and_then(|s| s.to_str()).unwrap_or(""); 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), "exr" => self.write_exr(path, metadata),
"png" => self.write_png(path), "png" => self.write_png(path),
"pfm" => self.write_pfm(path), "pfm" => self.write_pfm(path),
"qoi" => self.write_qoi(path), "qoi" => self.write_qoi(path),
_ => Err(anyhow::anyhow!("Unsupported write format: {}", ext)), _ => 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<()> { fn write_png(&self, path: &Path) -> Result<()> {
let w = self.resolution().x() as u32; let w = self.inner.resolution().x() as u32;
let h = self.resolution().y() as u32; let h = self.inner.resolution().y() as u32;
// Convert whatever we have to u8 [0..255]
let data = self.to_u8_buffer(); let data = self.to_u8_buffer();
let channels = self.n_channels(); let channels = self.inner.n_channels();
match channels { match channels {
1 => { 1 => {
// Luma
image_rs::save_buffer_with_format( image_rs::save_buffer_with_format(
path, path, &data, w, h,
&data,
w,
h,
image_rs::ColorType::L8, image_rs::ColorType::L8,
image_rs::ImageFormat::Png, image_rs::ImageFormat::Png,
)?; )?;
} }
3 => { 3 => {
// RGB
image_rs::save_buffer_with_format( image_rs::save_buffer_with_format(
path, path, &data, w, h,
&data,
w,
h,
image_rs::ColorType::Rgb8, image_rs::ColorType::Rgb8,
image_rs::ImageFormat::Png, image_rs::ImageFormat::Png,
)?; )?;
} }
4 => { 4 => {
// RGBA
image_rs::save_buffer_with_format( image_rs::save_buffer_with_format(
path, path, &data, w, h,
&data,
w,
h,
image_rs::ColorType::Rgba8, image_rs::ColorType::Rgba8,
image_rs::ImageFormat::Png, image_rs::ImageFormat::Png,
)?; )?;
@ -99,37 +82,30 @@ impl ImageIO for HostImage {
} }
fn write_qoi(&self, path: &Path) -> Result<()> { fn write_qoi(&self, path: &Path) -> Result<()> {
let w = self.resolution().x() as u32; let w = self.inner.resolution().x() as u32;
let h = self.resolution().y() as u32; let h = self.inner.resolution().y() as u32;
let data = self.to_u8_buffer(); 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, 3 => image_rs::ColorType::Rgb8,
4 => image_rs::ColorType::Rgba8, 4 => image_rs::ColorType::Rgba8,
_ => bail!("QOI only supports 3 or 4 channels"), _ => bail!("QOI only supports 3 or 4 channels"),
}; };
image_rs::save_buffer_with_format( image_rs::save_buffer_with_format(
path, path, &data, w, h, color_type, image_rs::ImageFormat::Qoi,
&data,
w,
h,
color_type,
image_rs::ImageFormat::Qoi,
)?; )?;
Ok(()) Ok(())
} }
fn write_exr(&self, path: &Path, _metadata: &ImageMetadata) -> Result<()> { fn write_exr(&self, path: &Path, _metadata: &ImageMetadata) -> Result<()> {
// EXR requires F32 let w = self.inner.resolution().x() as usize;
let w = self.resolution().x() as usize; let h = self.inner.resolution().y() as usize;
let h = self.resolution().y() as usize; let c = self.inner.n_channels();
let c = self.n_channels();
write_rgba_file(path, w, h, |x, y| { write_rgba_file(path, w, h, |x, y| {
// Helper to get float value regardless of internal storage
let get = |ch| { let get = |ch| {
self.get_channel_with_wrap( self.inner.get_channel_with_wrap(
Point2i::new(x as i32, y as i32), Point2i::new(x as i32, y as i32),
ch, ch,
WrapMode::Clamp.into(), WrapMode::Clamp.into(),
@ -154,27 +130,22 @@ impl ImageIO for HostImage {
let file = File::create(path)?; let file = File::create(path)?;
let mut writer = BufWriter::new(file); 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)"); bail!("PFM writing currently only supports 3 channels (RGB)");
} }
// Header let res = self.inner.resolution();
let res = self.resolution();
writeln!(writer, "PF")?; writeln!(writer, "PF")?;
writeln!(writer, "{} {}", res.x(), res.y())?; writeln!(writer, "{} {}", res.x(), res.y())?;
let scale = if cfg!(target_endian = "little") { let scale = if cfg!(target_endian = "little") { -1.0 } else { 1.0 };
-1.0
} else {
1.0
};
writeln!(writer, "{}", scale)?; writeln!(writer, "{}", scale)?;
// PBRT stores top-to-bottom.
for y in (0..res.y()).rev() { for y in (0..res.y()).rev() {
for x in 0..res.x() { for x in 0..res.x() {
for c in 0..3 { for c in 0..3 {
let val = let val = self.inner.get_channel_with_wrap(
self.get_channel_with_wrap(Point2i::new(x, y), c, WrapMode::Clamp.into()); Point2i::new(x, y), c, WrapMode::Clamp.into(),
);
writer.write_all(&val.to_le_bytes())?; writer.write_all(&val.to_le_bytes())?;
} }
} }
@ -183,20 +154,18 @@ impl ImageIO for HostImage {
Ok(()) Ok(())
} }
// TODO: Change Image to use Vec for data, always. Only convert to Device types on
// constructors/creation
fn to_u8_buffer(&self) -> Vec<u8> { fn to_u8_buffer(&self) -> Vec<u8> {
match &self.pixels { let res = self.inner.resolution();
PixelStorage::U8(data) => data.to_vec(), let n_pixels = (res.x() * res.y()) as usize;
PixelStorage::F16(data) => data let nc = self.inner.n_channels() as usize;
.iter() let total = n_pixels * nc;
.map(|v| (v.to_f32().clamp(0.0, 1.0) * 255.0 + 0.5) as u8)
.collect(), let mut buf = Vec::with_capacity(total);
PixelStorage::F32(data) => data for i in 0..total {
.iter() let val = unsafe { self.inner.pixels.read(i, &self.inner.encoding) };
.map(|v| (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8) buf.push((val.clamp(0.0, 1.0) * 255.0 + 0.5) as u8);
.collect(),
} }
buf
} }
} }
@ -209,21 +178,24 @@ fn read_generic(path: &Path, encoding: Option<ColorEncoding>) -> Result<ImageAnd
let h = dyn_img.height() as i32; let h = dyn_img.height() as i32;
let res = Point2i::new(w, h); let res = Point2i::new(w, h);
// Check if it was loaded as high precision or standard let rgb_names: &[&str] = &["R", "G", "B"];
let rgb_names = vec!["R", "G", "B"]; let rgba_names: &[&str] = &["R", "G", "B", "A"];
let rgba_names = vec!["R", "G", "B", "A"];
let image = match dyn_img { let image = match dyn_img {
DynamicImage::ImageRgb32F(buf) => Image::from_f32(buf.into_raw(), res, &rgb_names), DynamicImage::ImageRgb32F(buf) => {
DynamicImage::ImageRgba32F(buf) => Image::from_f32(buf.into_raw(), res, &rgba_names), 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); let enc = encoding.unwrap_or(SRGB);
if dyn_img.color().has_alpha() { if dyn_img.color().has_alpha() {
let buf = dyn_img.to_rgba8(); 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 { } else {
let buf = dyn_img.to_rgb8(); 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<ImageAndMetadata> {
|buffer, position, pixel| { |buffer, position, pixel| {
let width = position.width(); let width = position.width();
let idx = (position.y() * width + position.x()) * 4; let idx = (position.y() * width + position.x()) * 4;
// Map exr pixel struct to our buffer
buffer[idx] = pixel.0; buffer[idx] = pixel.0;
buffer[idx + 1] = pixel.1; buffer[idx + 1] = pixel.1;
buffer[idx + 2] = pixel.2; buffer[idx + 2] = pixel.2;
@ -254,10 +225,11 @@ fn read_exr(path: &Path) -> Result<ImageAndMetadata> {
let w = image.layer_data.size.width() as i32; let w = image.layer_data.size.width() as i32;
let h = image.layer_data.size.height() as i32; let h = image.layer_data.size.height() as i32;
let image = Image::from_f32( let rgba_names: &[&str] = &["R", "G", "B", "A"];
image.layer_data.channel_data.pixels, let image = HostImage::from_f32(
&image.layer_data.channel_data.pixels,
Point2i::new(w, h), Point2i::new(w, h),
&vec!["R", "G", "B", "A"], rgba_names,
); );
let metadata = ImageMetadata::default(); let metadata = ImageMetadata::default();
@ -268,7 +240,6 @@ fn read_pfm(path: &Path) -> Result<ImageAndMetadata> {
let file = File::open(path)?; let file = File::open(path)?;
let mut reader = BufReader::new(file); let mut reader = BufReader::new(file);
// PFM Headers are: "PF\nwidth height\nscale\n" (or Pf for grayscale)
let mut header_word = String::new(); let mut header_word = String::new();
reader.read_line(&mut header_word)?; reader.read_line(&mut header_word)?;
let header_word = header_word.trim(); let header_word = header_word.trim();
@ -309,9 +280,7 @@ fn read_pfm(path: &Path) -> Result<ImageAndMetadata> {
let mut pixels = vec![0.0 as Float; (w * h * channels) as usize]; let mut pixels = vec![0.0 as Float; (w * h * channels) as usize];
// PFM is Bottom-to-Top
for y in 0..h { for y in 0..h {
// Flippety-do
let src_y = h - 1 - y; let src_y = h - 1 - y;
for x in 0..w { for x in 0..w {
for c in 0..channels { for c in 0..channels {
@ -331,13 +300,9 @@ fn read_pfm(path: &Path) -> Result<ImageAndMetadata> {
} }
} }
let names = if channels == 1 { let names: &[&str] = if channels == 1 { &["Y"] } else { &["R", "G", "B"] };
vec!["Y"]
} else {
vec!["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(); let metadata = ImageMetadata::default();
Ok(ImageAndMetadata { image, metadata }) Ok(ImageAndMetadata { image, metadata })

View file

@ -1,14 +1,12 @@
use crate::utils::containers::Array2D; use anyhow::{anyhow, Result};
use anyhow::{Result, anyhow};
use half::f16; use half::f16;
use rayon::prelude::{IndexedParallelIterator, ParallelIterator, ParallelSliceMut}; use rayon::prelude::{IndexedParallelIterator, ParallelIterator, ParallelSliceMut};
use shared::Float;
use shared::Ptr;
use shared::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR}; use shared::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
use shared::core::geometry::{Bounds2f, Point2f, Point2i}; use shared::core::geometry::{Bounds2f, Point2f, Point2i};
use shared::core::image::{Image, ImageBase, PixelFormat, Pixels, WrapMode, WrapMode2D}; use shared::core::image::{Image, ImageBase, PixelFormat, Pixels, WrapMode, WrapMode2D};
use shared::utils::math::square; 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::ops::{Deref, DerefMut};
use std::sync::Arc; use std::sync::Arc;
@ -90,31 +88,32 @@ impl HostImage {
let n_channels = channel_names.len() as i32; let n_channels = channel_names.len() as i32;
Self { Self {
inner: Image::from_u8(data, resolution, n_channels, encoding), 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( pub fn from_f32(data: &[f32], resolution: Point2i, channel_names: &[impl AsRef<str>]) -> Self {
data: &[f32],
resolution: Point2i,
channel_names: &[impl AsRef<str>],
) -> Self {
let n_channels = channel_names.len() as i32; let n_channels = channel_names.len() as i32;
Self { Self {
inner: Image::from_f32(data, resolution, n_channels), 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( pub fn from_f16(data: &[u16], resolution: Point2i, channel_names: &[impl AsRef<str>]) -> Self {
data: &[u16],
resolution: Point2i,
channel_names: &[impl AsRef<str>],
) -> Self {
let n_channels = channel_names.len() as i32; let n_channels = channel_names.len() as i32;
Self { Self {
inner: Image::from_f16(data, resolution, n_channels), 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; let n_channels = channel_names.len() as i32;
Self { Self {
inner: Image::new(format, resolution, n_channels, encoding), 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], values: &[f32],
) -> Self { ) -> Self {
let n_channels = channel_names.len(); 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 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);
@ -179,14 +185,37 @@ impl HostImage {
} }
pub fn channel_names_from_desc(&self, desc: &ImageChannelDesc) -> Vec<&str> { 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 { pub fn get_channels(&self, p: Point2i) -> ImageChannelValues {
self.get_channels_with_wrap(p, WrapMode::Clamp.into()) 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) { if !self.inner.remap_pixel_coords(&mut p, wrap_mode) {
return ImageChannelValues(SmallVec::from_elem(0.0, self.inner.n_channels() as usize)); 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 pixel_offset = self.inner.pixel_offset(pp);
let mut values = SmallVec::with_capacity(desc.offset.len()); let mut values = SmallVec::with_capacity(desc.offset.len());
for &c in &desc.offset { 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) 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 { pub fn select_channels(&self, desc: &ImageChannelDesc) -> Self {
let new_names: Vec<String> = desc.offset.iter().map(|&i| self.channel_names[i].clone()).collect(); let new_names: Vec<String> = desc
.offset
.iter()
.map(|&i| self.channel_names[i].clone())
.collect();
let res = self.inner.resolution(); let res = self.inner.resolution();
let pixel_count = (res.x() * res.y()) as usize; let pixel_count = (res.x() * res.y()) as usize;
let src_nc = self.inner.n_channels() as usize; let src_nc = self.inner.n_channels() as usize;
let dst_nc = desc.offset.len(); let dst_nc = desc.offset.len();
// Always produce f32 output for simplicity
let mut dst = vec![0.0f32; pixel_count * dst_nc]; let mut dst = vec![0.0f32; pixel_count * dst_nc];
for i in 0..pixel_count { for i in 0..pixel_count {
let src_offset = i * src_nc; let src_offset = i * src_nc;
for (out_idx, &in_c) in desc.offset.iter().enumerate() { for (out_idx, &in_c) in desc.offset.iter().enumerate() {
dst[i * dst_nc + out_idx] = unsafe { 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() dist.as_mut_slice()
.par_chunks_mut(width as usize) .par_chunks_mut(width as usize)
.enumerate() .enumerate()
.for_each(|(y, row)| { .for_each(|(y, row): (usize, &mut [Float])| {
let y = y as i32; let y = y as i32;
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;
@ -310,15 +352,18 @@ impl HostImage {
for y in 0..res.y() { for y in 0..res.y() {
for x in 0..res.x() { for x in 0..res.x() {
let v = self.get_channels_with_desc( let v =
Point2i::new(x, y), desc, WrapMode::Clamp.into(), self.get_channels_with_desc(Point2i::new(x, y), desc, WrapMode::Clamp.into());
);
let v_ref = ref_img.get_channels_with_desc( 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 { for c in 0..n_channels {
let se = square(v[c] as f64 - v_ref[c] as f64); 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; sum_se[c] += se;
if generate_mse_image { if generate_mse_image {
let idx = (y as usize * width + x as usize) * n_channels + c; let idx = (y as usize * width + x as usize) * n_channels + c;

View file

@ -191,19 +191,19 @@ impl HostImage {
PixelFormat::U8 => downsample_kernel( PixelFormat::U8 => downsample_kernel(
next.inner.pixels.as_u8_mut(), next.inner.pixels.as_u8_mut(),
new_res, new_res,
&prev.inner, &prev,
internal_wrap, internal_wrap,
), ),
PixelFormat::F16 => downsample_kernel( PixelFormat::F16 => downsample_kernel(
next.inner.pixels.as_f16_mut(), next.inner.pixels.as_f16_mut(),
new_res, new_res,
&prev.inner, &prev,
internal_wrap, internal_wrap,
), ),
PixelFormat::F32 => downsample_kernel( PixelFormat::F32 => downsample_kernel(
next.inner.pixels.as_f32_slice_mut(), next.inner.pixels.as_f32_slice_mut(),
new_res, new_res,
&prev.inner, &prev,
internal_wrap, internal_wrap,
), ),
} }
@ -322,7 +322,7 @@ fn copy_rect_in_kernel<T: PixelStorageTrait>(
fn downsample_kernel<T: PixelStorageTrait>( fn downsample_kernel<T: PixelStorageTrait>(
dst: &mut [T], dst: &mut [T],
dst_res: Point2i, dst_res: Point2i,
prev: &Image, prev: &HostImage,
wrap: WrapMode2D, wrap: WrapMode2D,
) { ) {
let w = dst_res.x() as usize; let w = dst_res.x() as usize;

View file

@ -9,7 +9,7 @@ use shared::core::shape::Shape;
use shared::spectra::DenselySampledSpectrum; use shared::spectra::DenselySampledSpectrum;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::Transform; use shared::{Ptr, Transform};
use std::sync::Arc; use std::sync::Arc;
pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> { pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
@ -117,7 +117,7 @@ pub fn create_area_light(
shape: &Shape, shape: &Shape,
alpha_tex: &FloatTexture, alpha_tex: &FloatTexture,
colorspace: Option<&RGBColorSpace>, colorspace: Option<&RGBColorSpace>,
arena: &mut Arena, arena: &Arena,
) -> Result<Ptr<Light>> { ) -> Result<Ptr<Light>> {
let light = crate::lights::diffuse::create( let light = crate::lights::diffuse::create(
render_from_light, medium, parameters, loc, render_from_light, medium, parameters, loc,

View file

@ -1,5 +1,5 @@
use crate::Arena; use crate::Arena;
use crate::core::image::Image; use crate::core::image::HostImage;
use crate::utils::TextureParameterDictionary; use crate::utils::TextureParameterDictionary;
use crate::utils::error::FileLoc; use crate::utils::error::FileLoc;
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
@ -11,7 +11,7 @@ use std::sync::Arc;
pub trait CreateMaterial: Sized { pub trait CreateMaterial: Sized {
fn create( fn create(
parameters: &TextureParameterDictionary, parameters: &TextureParameterDictionary,
normal_map: Option<Arc<Image>>, normal_map: Option<Arc<HostImage>>,
named_materials: &HashMap<String, Material>, named_materials: &HashMap<String, Material>,
loc: &FileLoc, loc: &FileLoc,
arena: &Arena, arena: &Arena,
@ -22,7 +22,7 @@ pub trait MaterialFactory {
fn create( fn create(
name: &str, name: &str,
params: &TextureParameterDictionary, params: &TextureParameterDictionary,
normal_map: Option<Arc<Image>>, normal_map: Option<Arc<HostImage>>,
named_materials: &HashMap<String, Material>, named_materials: &HashMap<String, Material>,
loc: FileLoc, loc: FileLoc,
arena: &Arena, arena: &Arena,
@ -35,7 +35,7 @@ impl MaterialFactory for Material {
fn create( fn create(
name: &str, name: &str,
parameters: &TextureParameterDictionary, parameters: &TextureParameterDictionary,
normal_map: Option<Arc<Image>>, normal_map: Option<Arc<HostImage>>,
named_materials: &HashMap<String, Material>, named_materials: &HashMap<String, Material>,
loc: FileLoc, loc: FileLoc,
arena: &Arena, arena: &Arena,

View file

@ -3,7 +3,7 @@ use super::state::*;
use crate::core::camera::CameraFactory; use crate::core::camera::CameraFactory;
use crate::core::film::FilmFactory; use crate::core::film::FilmFactory;
use crate::core::filter::FilterFactory; 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::material::MaterialFactory;
use crate::core::primitive::{CreateGeometricPrimitive, CreateSimplePrimitive}; use crate::core::primitive::{CreateGeometricPrimitive, CreateSimplePrimitive};
use crate::core::sampler::SamplerFactory; 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::parallel::{run_async, AsyncJob};
use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary}; use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
use crate::utils::resolve_filename; use crate::utils::resolve_filename;
use crate::{Arena, ArenaUpload, FileLoc, Upload}; use crate::{Arena, ArenaUpload, FileLoc};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use parking_lot::Mutex; use parking_lot::Mutex;
use shared::core::camera::{Camera, CameraTransform}; 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::primitive::{AnimatedPrimitive, GeometricPrimitive, Primitive, SimplePrimitive};
use shared::core::sampler::Sampler; use shared::core::sampler::Sampler;
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::core::texture::SpectrumType; use shared::core::texture::{GPUFloatTexture, SpectrumType};
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::Ptr; use shared::{Ptr, Transform};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -169,7 +169,6 @@ impl BasicScene {
}); });
self.sampler_state.lock().job = Some(sampler_job); self.sampler_state.lock().job = Some(sampler_job);
let arena_camera = Arc::clone(&arena);
let camera_film = Arc::clone(&film_instance); let camera_film = Arc::clone(&film_instance);
let scene_ptr = Arc::clone(self); let scene_ptr = Arc::clone(self);
let camera_job = run_async(move || { let camera_job = run_async(move || {
@ -181,7 +180,7 @@ impl BasicScene {
medium, medium,
camera_film, camera_film,
&camera.base.loc, &camera.base.loc,
&arena_camera, &arena,
) )
.map_err(|e| anyhow!("Failed to create camera: {}", e)) .map_err(|e| anyhow!("Failed to create camera: {}", e))
}); });
@ -1027,7 +1026,7 @@ impl BasicScene {
let filename_clone = filename.clone(); let filename_clone = filename.clone();
let job = run_async(move || { let job = run_async(move || {
let path = std::path::Path::new(&filename_clone); 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", "{}: normal map must contain R, G, B channels",
filename_clone filename_clone
)); ));
@ -1051,7 +1050,7 @@ impl BasicScene {
&self, &self,
state: &MaterialState, state: &MaterialState,
params: &ParameterDictionary, params: &ParameterDictionary,
) -> Result<Option<Arc<Image>>> { ) -> Result<Option<Arc<HostImage>>> {
let filename = resolve_filename(&params.get_one_string("normalmap", "")?); let filename = resolve_filename(&params.get_one_string("normalmap", "")?);
if filename.is_empty() { if filename.is_empty() {
return Ok(None); return Ok(None);

View file

@ -1,5 +1,5 @@
use super::{LightSceneEntity, SceneEntity, TextureSceneEntity}; use super::{LightSceneEntity, SceneEntity, TextureSceneEntity};
use crate::core::image::Image; use crate::core::image::HostImage;
use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::utils::parallel::AsyncJob; use crate::utils::parallel::AsyncJob;
use anyhow::Result; use anyhow::Result;
@ -22,8 +22,8 @@ pub struct TextureState {
pub struct MaterialState { pub struct MaterialState {
pub named_materials: Vec<(String, SceneEntity)>, pub named_materials: Vec<(String, SceneEntity)>,
pub materials: Vec<SceneEntity>, pub materials: Vec<SceneEntity>,
pub normal_map_jobs: HashMap<String, AsyncJob<Arc<Image>>>, pub normal_map_jobs: HashMap<String, AsyncJob<Arc<HostImage>>>,
pub normal_maps: HashMap<String, Arc<Image>>, pub normal_maps: HashMap<String, Arc<HostImage>>,
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]

View file

@ -116,8 +116,8 @@ impl ShapeFactory for Shape {
global_store.push(host_arc.clone()); global_store.push(host_arc.clone());
drop(global_store); drop(global_store);
let n_tris = host_arc..n_triangles; let n_tris = host_arc.n_triangles;
let mesh_ptr = Ptr::from(&host_arc); let mesh_ptr = arena.alloc_arc(host_arc);
let shapes: Vec<Ptr<Shape>> = (0..n_tris) let shapes: Vec<Ptr<Shape>> = (0..n_tris)
.map(|i| { .map(|i| {
let tri_shape = Shape::Triangle(TriangleShape { let tri_shape = Shape::Triangle(TriangleShape {

View file

@ -1,5 +1,5 @@
use crate::textures::*; use crate::textures::*;
use crate::utils::TextureParameterDictionary; use crate::utils::{MIPMap, MIPMapFilterOptions, TextureParameterDictionary};
use crate::{Arena, FileLoc}; use crate::{Arena, FileLoc};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
@ -8,8 +8,8 @@ use shared::core::geometry::Vector3f;
use shared::core::image::WrapMode; use shared::core::image::WrapMode;
use shared::core::texture::SpectrumType; use shared::core::texture::SpectrumType;
use shared::core::texture::{ use shared::core::texture::{
CylindricalMapping, GPUFloatTexture, GPUSpectrumTexture, PlanarMapping, SphericalMapping, CylindricalMapping, PlanarMapping, SphericalMapping, TextureEvalContext, TextureMapping2D,
TextureEvalContext, TextureMapping2D, UVMapping, UVMapping,
}; };
use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::spectra::{SampledSpectrum, SampledWavelengths};
use shared::textures::*; use shared::textures::*;
@ -133,7 +133,10 @@ impl SpectrumTexture {
invert: inner.base.invert, invert: inner.base.invert,
is_single_channel: inner.base.mipmap.is_single_channel(), is_single_channel: inner.base.mipmap.is_single_channel(),
color_space: arena.alloc( color_space: arena.alloc(
inner.base.mipmap.color_space inner
.base
.mipmap
.color_space
.clone() .clone()
.unwrap_or_else(crate::spectra::default_colorspace), .unwrap_or_else(crate::spectra::default_colorspace),
), ),
@ -262,4 +265,3 @@ pub struct TexInfo {
pub wrap_mode: WrapMode, pub wrap_mode: WrapMode,
pub encoding: ColorEncoding, pub encoding: ColorEncoding,
} }

View file

@ -1,9 +1,8 @@
use super::*; use super::*;
use crate::core::film::{CreateFilmBase, PixelSensor}; use crate::core::film::{CreateFilmBase, CreatePixelSensor};
use crate::utils::containers::Array2D; use shared::core::film::PixelSensor;
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use shared::core::film::{FilmBase, GBufferFilm}; use shared::core::film::{FilmBase, GBufferFilm};
use shared::core::filter::FilterTrait;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::AnimatedTransform; use shared::utils::AnimatedTransform;
use std::path::Path; use std::path::Path;
@ -16,13 +15,13 @@ impl CreateFilm for GBufferFilm {
filter: Filter, filter: Filter,
camera_transform: Option<CameraTransform>, camera_transform: Option<CameraTransform>,
loc: &FileLoc, loc: &FileLoc,
_arena: &Arena, arena: &Arena,
) -> Result<Film> { ) -> Result<Film> {
let colorspace = params.color_space.as_ref().unwrap(); let colorspace = params.color_space.as_ref().unwrap();
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?; let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?;
let write_fp16 = params.get_one_bool("savefp16", true)?; let write_fp16 = params.get_one_bool("savefp16", true)?;
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc, arena)?;
let film_base = FilmBase::create(params, filter, Some(&sensor.device()), loc)?; let film_base = FilmBase::create(params, filter, Some(&sensor), loc)?;
let filename = params.get_one_string("filename", "pbrt.exr")?; let filename = params.get_one_string("filename", "pbrt.exr")?;
if Path::new(&filename).extension() != Some("exr".as_ref()) { if Path::new(&filename).extension() != Some("exr".as_ref()) {

View file

@ -1,11 +1,9 @@
use super::*; use super::*;
use crate::core::film::{CreateFilmBase, PixelSensor}; use crate::core::film::{CreateFilmBase, PixelSensor};
use crate::utils::containers::Array2D;
use crate::Arena; use crate::Arena;
use anyhow::Result; use anyhow::Result;
use shared::core::camera::CameraTransform; use shared::core::camera::CameraTransform;
use shared::core::film::{Film, FilmBase, RGBFilm, RGBPixel}; use shared::core::film::{Film, FilmBase, RGBFilm, RGBPixel};
use shared::core::filter::FilterTrait;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
impl CreateFilm for RGBFilm { impl CreateFilm for RGBFilm {

View file

@ -1,16 +1,14 @@
use super::*; use super::*;
use crate::core::film::{CreateFilmBase, PixelSensor}; use crate::core::film::{CreateFilmBase, CreatePixelSensor};
use crate::{Arena, FileLoc, ParameterDictionary}; use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow}; use anyhow::{anyhow, Result};
use shared::Float;
use shared::core::camera::CameraTransform; use shared::core::camera::CameraTransform;
use shared::core::film::{FilmBase, SpectralFilm}; use shared::core::film::{FilmBase, PixelSensor, SpectralFilm};
use shared::core::filter::FilterTrait;
use shared::spectra::{LAMBDA_MAX, LAMBDA_MIN}; use shared::spectra::{LAMBDA_MAX, LAMBDA_MIN};
use shared::utils::math::SquareMatrix; use shared::utils::math::SquareMatrix;
use shared::Float;
use std::path::Path; use std::path::Path;
impl CreateFilm for SpectralFilm { impl CreateFilm for SpectralFilm {
fn create( fn create(
params: &ParameterDictionary, params: &ParameterDictionary,
@ -18,14 +16,14 @@ impl CreateFilm for SpectralFilm {
filter: Filter, filter: Filter,
_camera_transform: Option<CameraTransform>, _camera_transform: Option<CameraTransform>,
loc: &FileLoc, loc: &FileLoc,
_arena: &Arena, arena: &Arena,
) -> Result<Film> { ) -> Result<Film> {
// Missing default illuminant, use srgb // Missing default illuminant, use srgb
let colorspace = params.color_space.as_ref().unwrap(); let colorspace = params.color_space.as_ref().unwrap();
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?; let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?;
let write_fp16 = params.get_one_bool("savefp16", true)?; let write_fp16 = params.get_one_bool("savefp16", true)?;
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?; let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc, arena)?;
let film_base = FilmBase::create(params, filter, Some(&sensor.device()), loc)?; let film_base = FilmBase::create(params, filter, Some(&sensor), loc)?;
let filename = params.get_one_string("filename", "pbrt.exr")?; let filename = params.get_one_string("filename", "pbrt.exr")?;
if Path::new(&filename).extension() != Some("exr".as_ref()) { if Path::new(&filename).extension() != Some("exr".as_ref()) {

View file

@ -2,7 +2,7 @@ use super::base::IntegratorBase;
use super::RayIntegratorTrait; use super::RayIntegratorTrait;
use crate::core::camera::InitMetadata; use crate::core::camera::InitMetadata;
use crate::core::film::FilmTrait; 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::globals::get_options;
use crate::spectra::get_spectra_context; use crate::spectra::get_spectra_context;
use crate::Arena; use crate::Arena;
@ -108,12 +108,12 @@ pub fn render<T>(
let mut wave_end = 1; let mut wave_end = 1;
let mut next_wave_size = 1; let mut next_wave_size = 1;
let mut reference_image: Option<Image> = None; let mut reference_image: Option<HostImage> = None;
let mut mse_out_file: Option<std::fs::File> = None; let mut mse_out_file: Option<std::fs::File> = None;
if let Some(ref_path) = &options.mse_reference_image { if let Some(ref_path) = &options.mse_reference_image {
let image_and_metadata = 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 image = image_and_metadata.image;
let metadata = image_and_metadata.metadata; let metadata = image_and_metadata.metadata;
let resolution = image.resolution(); let resolution = image.resolution();

View file

@ -2,7 +2,6 @@
#[allow(dead_code)] #[allow(dead_code)]
pub mod core; pub mod core;
pub mod films; pub mod films;
pub mod filters;
pub mod globals; pub mod globals;
pub mod integrators; pub mod integrators;
pub mod lights; pub mod lights;

View file

@ -1,6 +1,4 @@
use std::path::Path; use crate::core::image::{HostImage, ImageIO};
use crate::core::image::{Image, ImageIO};
use crate::core::light::lookup_spectrum; use crate::core::light::lookup_spectrum;
use crate::core::spectrum::spectrum_to_photometric; use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
@ -18,6 +16,7 @@ use shared::lights::DiffuseAreaLight;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::Transform; use shared::utils::Transform;
use shared::{Float, PI}; use shared::{Float, PI};
use std::path::Path;
pub fn create( pub fn create(
render_from_light: Transform, render_from_light: Transform,
@ -37,13 +36,13 @@ pub fn create(
let two_sided = params.get_one_bool("twosided", false)?; let two_sided = params.get_one_bool("twosided", false)?;
let filename = resolve_filename(&params.get_one_string("filename", "")?); let filename = resolve_filename(&params.get_one_string("filename", "")?);
let (image, image_color_space): (Option<Image>, Option<RGBColorSpace>) = let (image, image_color_space): (Option<HostImage>, Option<RGBColorSpace>) =
if !filename.is_empty() { if !filename.is_empty() {
if l.is_some() { if l.is_some() {
return Err(anyhow!("{}: both \"L\" and \"filename\" specified", loc)); 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() { if im.image.has_any_infinite_pixels() {
return Err(anyhow!("{}: image has infinite pixel values", loc)); return Err(anyhow!("{}: image has infinite pixel values", loc));
@ -100,12 +99,13 @@ pub fn create(
scale *= phi_v / k_e; 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 alpha_ptr = arena.upload(alpha);
let light_type = if alpha_ptr.is_constant_zero() { let light_type = match alpha_ptr.as_ref() {
LightType::DeltaPosition GPUFloatTexture::Constant(t) if t.evaluate(&TextureEvalContext::default()) == 0.0 => {
} else { LightType::DeltaPosition
LightType::Area }
_ => LightType::Area,
}; };
let mi = match medium { let mi = match medium {
@ -146,7 +146,7 @@ pub fn create(
area: shape.area(), area: shape.area(),
shape: arena.alloc(*shape), shape: arena.alloc(*shape),
alpha: alpha_ptr, alpha: alpha_ptr,
image: arena.alloc(image), image: arena.upload(image),
colorspace: arena.alloc_opt(image_color_space), colorspace: arena.alloc_opt(image_color_space),
lemit: arena.alloc((*lookup_spectrum(l_for_scale)).clone()), lemit: arena.alloc((*lookup_spectrum(l_for_scale)).clone()),
two_sided, two_sided,

View file

@ -1,6 +1,6 @@
use std::path::Path; 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::light::lookup_spectrum;
use crate::core::spectrum::spectrum_to_photometric; use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
@ -41,10 +41,10 @@ pub fn create(
.expect("Could not retrieve spectrum"); .expect("Could not retrieve spectrum");
let mut scale = params.get_one_float("scale", 1.)?; let mut scale = params.get_one_float("scale", 1.)?;
let filename = resolve_filename(&params.get_one_string("filename", "")?); let filename = resolve_filename(&params.get_one_string("filename", "")?);
let image: Ptr<Image> = if filename.is_empty() { let image: Ptr<HostImage> = if filename.is_empty() {
Ptr::null() Ptr::null()
} else { } 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))?; .map_err(|e| anyhow!("could not load image '{}': {}", filename, e))?;
let loaded = im.image; let loaded = im.image;
@ -117,7 +117,7 @@ pub fn create(
Ok(Light::Goniometric(specific)) Ok(Light::Goniometric(specific))
} }
fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> Result<Image> { fn convert_to_luminance_image(image: &HostImage, filename: &str, loc: &FileLoc) -> Result<HostImage> {
let res = image.resolution(); let res = image.resolution();
let rgb_desc = image.get_channel_desc(&["R", "G", "B"]); let rgb_desc = image.get_channel_desc(&["R", "G", "B"]);
let y_desc = image.get_channel_desc(&["Y"]); 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(_)) => { (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 res = image.resolution();
let mut sum_y = 0.0; let mut sum_y = 0.0;

View file

@ -11,11 +11,11 @@ use shared::core::camera::CameraTransform;
use shared::core::geometry::{cos_theta, Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike}; use shared::core::geometry::{cos_theta, Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike};
use shared::core::image::WrapMode; use shared::core::image::WrapMode;
use shared::core::light::{Light, LightBase, LightType}; 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::spectrum::Spectrum;
use shared::core::texture::SpectrumType; use shared::core::texture::SpectrumType;
use shared::lights::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLight}; 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::math::{equal_area_sphere_to_square, equal_area_square_to_sphere};
use shared::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D}; use shared::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D};
use shared::{Float, Ptr, Transform, PI}; use shared::{Float, Ptr, Transform, PI};
@ -63,7 +63,7 @@ pub fn create(
} }
let lemit = lookup_spectrum(&spectrum); 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)); return Ok(Light::InfiniteUniform(light));
} }
@ -96,7 +96,7 @@ pub fn create(
fn create_image_light( fn create_image_light(
render_from_light: Transform, render_from_light: Transform,
scale: Float, scale: Float,
image: Image, image: HostImage,
image_cs: RGBColorSpace, image_cs: RGBColorSpace,
arena: &Arena, arena: &Arena,
) -> Result<Light> { ) -> Result<Light> {
@ -110,7 +110,6 @@ fn create_image_light(
let (n_u, n_v) = (res.x() as usize, res.y() as usize); let (n_u, n_v) = (res.x() as usize, res.y() as usize);
// Extract luminance data // Extract luminance data
let image_ptr = image.upload(arena);
let value = &image; 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| {
@ -139,7 +138,7 @@ fn create_image_light(
let light = ImageInfiniteLight::new( let light = ImageInfiniteLight::new(
render_from_light, render_from_light,
scale, scale,
image_ptr, arena.upload(image),
arena.alloc(image_cs), arena.alloc(image_cs),
arena.alloc(distrib), arena.alloc(distrib),
arena.alloc(compensated_distrib), arena.alloc(compensated_distrib),
@ -151,7 +150,7 @@ fn create_image_light(
fn create_portal_light( fn create_portal_light(
render_from_light: Transform, render_from_light: Transform,
scale: Float, scale: Float,
image: Image, image: HostImage,
image_cs: RGBColorSpace, image_cs: RGBColorSpace,
portal_points: &[Point3f], portal_points: &[Point3f],
camera_transform: CameraTransform, camera_transform: CameraTransform,
@ -198,7 +197,7 @@ fn create_portal_light(
let light = PortalInfiniteLight::new( let light = PortalInfiniteLight::new(
render_from_light, render_from_light,
scale, scale,
arena.alloc(remapped), arena.upload(remapped),
arena.alloc(image_cs), arena.alloc(image_cs),
portal, portal,
portal_frame, portal_frame,
@ -230,10 +229,10 @@ fn validate_and_build_portal_frame(portal: &[Point3f; 4], loc: &FileLoc) -> Resu
} }
fn remap_image_through_portal( fn remap_image_through_portal(
image: &Image, image: &HostImage,
render_from_light: &Transform, render_from_light: &Transform,
portal_frame: &Frame, portal_frame: &Frame,
) -> Image { ) -> HostImage {
let res = image.resolution(); let res = image.resolution();
let (width, height) = (res.x() as usize, res.y() as usize); 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( fn load_image(
@ -271,16 +270,16 @@ fn load_image(
l: &[Spectrum], l: &[Spectrum],
colorspace: &RGBColorSpace, colorspace: &RGBColorSpace,
loc: &FileLoc, loc: &FileLoc,
) -> Result<(Image, RGBColorSpace)> { ) -> Result<(HostImage, RGBColorSpace)> {
if filename.is_empty() { if filename.is_empty() {
let stdspec = get_spectra_context(); let stdspec = get_spectra_context();
let rgb = l[0].to_rgb(colorspace, &stdspec); let rgb = l[0].to_rgb(colorspace, &stdspec);
let image = 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())); 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))?; .map_err(|e| anyhow!("failed to load '{}': {}", filename, e))?;
if im.image.has_any_infinite_pixels() || im.image.has_any_nan_pixels() { 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)) 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 lum = cs.luminance_vector();
let res = image.resolution(); let res = image.resolution();

View file

@ -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::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::resolve_filename; use crate::utils::resolve_filename;
use crate::{Arena, FileLoc, ParameterDictionary}; use crate::{Arena, FileLoc, ParameterDictionary, ArenaUpload};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use shared::Float; use shared::Float;
use shared::core::geometry::{ 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))?; .map_err(|e| anyhow!("{}: could not load image '{}': {}", loc, filename, e))?;
if im.image.has_any_infinite_pixels() { if im.image.has_any_infinite_pixels() {
@ -125,9 +125,9 @@ pub fn create(
let specific = ProjectionLight { let specific = ProjectionLight {
base, base,
image: image.upload(arena), image: arena.upload(image),
image_color_space: colorspace.upload(arena), image_color_space: arena.alloc(colorspace),
distrib: distrib.upload(arena), distrib: arena.alloc(distrib),
screen_bounds, screen_bounds,
screen_from_light, screen_from_light,
light_from_screen, 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 res = image.resolution();
let aspect = res.x() as Float / res.y() as Float; let aspect = res.x() as Float / res.y() as Float;
let screen_bounds = compute_screen_bounds(aspect); let screen_bounds = compute_screen_bounds(aspect);

View file

@ -12,7 +12,6 @@ use shared::core::texture::SpectrumType;
use shared::lights::SpotLight; use shared::lights::SpotLight;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::math::radians; use shared::utils::math::radians;
use shared::utils::{Ptr, Transform};
use shared::{Float, Ptr, Transform, PI}; use shared::{Float, Ptr, Transform, PI};
trait CreateSpotLight { trait CreateSpotLight {

View file

@ -4,7 +4,7 @@ use crate::core::texture::SpectrumTexture;
use crate::globals::get_options; use crate::globals::get_options;
use crate::spectra::data::get_named_spectrum; use crate::spectra::data::get_named_spectrum;
use crate::utils::TextureParameterDictionary; use crate::utils::TextureParameterDictionary;
use crate::{Arena, FileLoc, Upload, ArenaUpload}; use crate::{Arena, FileLoc, ArenaUpload};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use shared::core::material::Material; use shared::core::material::Material;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
@ -64,7 +64,7 @@ impl CreateMaterial for CoatedDiffuseMaterial {
arena.upload(g), arena.upload(g),
arena.upload(displacement), arena.upload(displacement),
arena.alloc(eta), arena.alloc(eta),
arena.alloc(normal_map), arena.upload(normal_map),
remap_roughness, remap_roughness,
max_depth as u32, max_depth as u32,
n_samples as u32, n_samples as u32,
@ -154,7 +154,7 @@ impl CreateMaterial for CoatedConductorMaterial {
let remap_roughness = parameters.get_one_bool("remaproughness", true)?; let remap_roughness = parameters.get_one_bool("remaproughness", true)?;
let material = Self::new( let material = Self::new(
arena.upload(displacement) arena.upload(displacement),
arena.upload(interface_u_roughness), arena.upload(interface_u_roughness),
arena.upload(interface_v_roughness), arena.upload(interface_v_roughness),
arena.upload(thickness), arena.upload(thickness),
@ -165,7 +165,7 @@ impl CreateMaterial for CoatedConductorMaterial {
arena.upload(conductor_eta), arena.upload(conductor_eta),
arena.upload(k), arena.upload(k),
arena.upload(reflectance), arena.upload(reflectance),
arena.alloc(normal_map), arena.upload(normal_map),
arena.alloc(interface_eta), arena.alloc(interface_eta),
max_depth as u32, max_depth as u32,
n_samples as u32, n_samples as u32,

View file

@ -3,7 +3,7 @@ use crate::core::material::CreateMaterial;
use crate::core::texture::SpectrumTexture; use crate::core::texture::SpectrumTexture;
use crate::spectra::get_colorspace_device; use crate::spectra::get_colorspace_device;
use crate::utils::TextureParameterDictionary; use crate::utils::TextureParameterDictionary;
use crate::{Arena, FileLoc, Upload, ArenaUpload}; use crate::{Arena, ArenaUpload, FileLoc};
use anyhow::Result; use anyhow::Result;
use shared::bxdfs::HairBxDF; use shared::bxdfs::HairBxDF;
use shared::core::material::Material; use shared::core::material::Material;
@ -60,7 +60,6 @@ impl CreateMaterial for HairMaterial {
} }
} }
impl CreateMaterial for SubsurfaceMaterial { impl CreateMaterial for SubsurfaceMaterial {
fn create( fn create(
_parameters: &TextureParameterDictionary, _parameters: &TextureParameterDictionary,

View file

@ -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::shape::{CreateShape, ALL_BILINEAR_MESHES};
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
@ -6,7 +6,7 @@ use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use log::warn; use log::warn;
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::shapes::BilinearPatchShape; use shared::shapes::{BilinearPatchMesh, BilinearPatchShape};
use shared::{Ptr, Transform}; use shared::{Ptr, Transform};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
@ -92,7 +92,7 @@ impl CreateShape for BilinearPatchShape {
); );
None None
} else { } else {
let im = Image::read(Path::new(&filename), None)?; let im = HostImage::read(Path::new(&filename), None)?;
let mut img = im.image; let mut img = im.image;
img.flip_y(); img.flip_y();
Some(PiecewiseConstant2D::from_image(&img)) Some(PiecewiseConstant2D::from_image(&img))
@ -104,10 +104,10 @@ impl CreateShape for BilinearPatchShape {
let host = BilinearPatchMesh::new( let host = BilinearPatchMesh::new(
&render_from_object, &render_from_object,
reverse_orientation, reverse_orientation,
vertex_indices, &vertex_indices,
p, &p,
n, &n,
uv, &uv,
image_dist, image_dist,
); );
@ -116,8 +116,8 @@ impl CreateShape for BilinearPatchShape {
// let mesh_index = global_store.len() as u32; // let mesh_index = global_store.len() as u32;
global_store.push(host_arc.clone()); global_store.push(host_arc.clone());
drop(global_store); drop(global_store);
let n_patches = host_arc.device.n_patches; let n_patches = host_arc.n_patches;
let mesh_ptr = Ptr::from(&host_arc.device); let mesh_ptr = arena.alloc_arc(host_arc);
let mut shapes = Vec::with_capacity(n_patches as usize); let mut shapes = Vec::with_capacity(n_patches as usize);
for i in 0..n_patches as i32 { for i in 0..n_patches as i32 {
shapes.push(arena.alloc(Shape::BilinearPatch(BilinearPatchShape { shapes.push(arena.alloc(Shape::BilinearPatch(BilinearPatchShape {

View file

@ -1,11 +1,10 @@
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::Arena;
use anyhow::{bail, Context, Result as AnyResult}; use anyhow::{bail, Context, Result as AnyResult};
use ply_rs::parser::Parser; use ply_rs::parser::Parser;
use ply_rs::ply::{DefaultElement, Property}; use ply_rs::ply::{DefaultElement, Property};
use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f, VectorLike}; use shared::core::geometry::{Normal3f, Point2f, Point3f, VectorLike};
use shared::shapes::mesh::{BilinearPatchMesh, TriangleMesh}; use shared::shapes::mesh::TriangleMesh;
use shared::{Ptr, Transform}; use shared::Transform;
use std::fs::File; use std::fs::File;
use std::path::Path; use std::path::Path;
@ -269,11 +268,13 @@ impl TriQuadMesh {
} }
pub trait ReadTriangleMesh { pub trait ReadTriangleMesh {
pub fn from_ply<P: AsRef<Path>>( fn from_ply<P: AsRef<Path>>(
filename: P, filename: P,
render_from_object: &Transform, render_from_object: &Transform,
reverse_orientation: bool, reverse_orientation: bool,
) -> AnyResult<Self>; ) -> AnyResult<Self>
where
Self: Sized;
} }
impl ReadTriangleMesh for TriangleMesh { impl ReadTriangleMesh for TriangleMesh {

View file

@ -99,8 +99,8 @@ impl CreateShape for TriangleShape {
// let mesh_index = global_store.len() as u32; // let mesh_index = global_store.len() as u32;
global_store.push(host_arc.clone()); global_store.push(host_arc.clone());
drop(global_store); drop(global_store);
let n_patches = host_arc.device.n_triangles; let n_patches = host_arc.n_triangles;
let mesh_ptr = Ptr::from(&host_arc.device); let mesh_ptr = arena.alloc_arc(host_arc);
let mut shapes = Vec::with_capacity(n_patches as usize); let mut shapes = Vec::with_capacity(n_patches as usize);
for i in 0..n_patches { for i in 0..n_patches {
shapes.push(arena.alloc(Shape::Triangle(TriangleShape { shapes.push(arena.alloc(Shape::Triangle(TriangleShape {

View file

@ -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::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z};
use shared::spectra::{DenselySampledSpectrum, DeviceStandardColorSpaces, RGBColorSpace}; use shared::spectra::{DenselySampledSpectrum, DeviceStandardColorSpaces, RGBColorSpace};
use shared::Ptr; use shared::Ptr;
use std::sync::Arc; use std::sync::{Arc, OnceLock, LazyLock};
use std::sync::LazyLock;
pub mod colorspace; pub mod colorspace;
pub mod data; pub mod data;
@ -68,7 +67,7 @@ pub static DCI_P3: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| {
Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr)) Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr))
}); });
pub static REC2020: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| { pub static REC2020: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| {
let illum = get_d65_illuminant_buffer(); let illum = get_d65_illuminant_buffer();
let r = Point2f::new(0.708, 0.292); let r = Point2f::new(0.708, 0.292);
let g = Point2f::new(0.170, 0.797); let g = Point2f::new(0.170, 0.797);
@ -77,7 +76,7 @@ pub static REC2020: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr)) Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr))
}); });
pub static ACES: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| { pub static ACES: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| {
let illum = get_d65_illuminant_buffer(); let illum = get_d65_illuminant_buffer();
let r = Point2f::new(0.7347, 0.2653); let r = Point2f::new(0.7347, 0.2653);
let g = Point2f::new(0.0000, 1.0000); let g = Point2f::new(0.0000, 1.0000);
@ -88,10 +87,10 @@ pub static ACES: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct StandardColorSpaces { pub struct StandardColorSpaces {
pub srgb: Arc<RGBColorSpaceData>, pub srgb: Arc<RGBColorSpace>,
pub dci_p3: Arc<RGBColorSpaceData>, pub dci_p3: Arc<RGBColorSpace>,
pub rec2020: Arc<RGBColorSpaceData>, pub rec2020: Arc<RGBColorSpace>,
pub aces2065_1: Arc<RGBColorSpaceData>, pub aces2065_1: Arc<RGBColorSpace>,
} }
impl StandardColorSpaces { impl StandardColorSpaces {
@ -141,3 +140,4 @@ pub fn default_colorspace_ref() -> &'static RGBColorSpace {
pub fn default_illuminant() -> Spectrum { pub fn default_illuminant() -> Spectrum {
Spectrum::Dense(default_colorspace().illuminant) Spectrum::Dense(default_colorspace().illuminant)
} }

View file

@ -7,10 +7,6 @@ use anyhow::Result;
use shared::core::geometry::{Vector3f, VectorLike}; use shared::core::geometry::{Vector3f, VectorLike};
use shared::core::texture::{SpectrumType, TextureEvalContext}; use shared::core::texture::{SpectrumType, TextureEvalContext};
use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::spectra::{SampledSpectrum, SampledWavelengths};
use shared::textures::{
GPUFloatDirectionMixTexture, GPUFloatMixTexture, GPUSpectrumDirectionMixTexture,
GPUSpectrumMixTexture,
};
use shared::utils::Transform; use shared::utils::Transform;
use shared::Float; use shared::Float;
use std::sync::Arc; use std::sync::Arc;

View file

@ -5,7 +5,6 @@ use crate::Arena;
use anyhow::Result; use anyhow::Result;
use shared::core::texture::{SpectrumType, TextureEvalContext}; use shared::core::texture::{SpectrumType, TextureEvalContext};
use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::spectra::{SampledSpectrum, SampledWavelengths};
use shared::textures::{GPUFloatScaledTexture, GPUSpectrumScaledTexture};
use shared::utils::Transform; use shared::utils::Transform;
use shared::Float; use shared::Float;
use std::sync::Arc; use std::sync::Arc;

View file

@ -148,6 +148,13 @@ impl<A: GpuAllocator> Arena<A> {
} }
} }
pub fn alloc_arc<T: Clone>(&self, value: Arc<T>) -> Ptr<T> {
match Arc::try_unwrap(value) {
Ok(inner) => self.alloc(inner),
Err(arc) => self.alloc((*arc).clone()),
}
}
pub fn alloc_slice<T: Copy>(&self, values: &[T]) -> (Ptr<T>, usize) { pub fn alloc_slice<T: Copy>(&self, values: &[T]) -> (Ptr<T>, usize) {
let mut bump = self.bump.lock(); let mut bump = self.bump.lock();
let (ptr, len) = bump.alloc_slice(values); let (ptr, len) = bump.alloc_slice(values);

View file

@ -1,10 +1,10 @@
use crate::core::image::{Image, ImageIO}; use crate::core::image::{HostImage, ImageIO};
use shared::Float;
use shared::core::color::{ColorEncoding, RGB}; use shared::core::color::{ColorEncoding, RGB};
use shared::core::geometry::{Point2f, Point2i, Vector2f, VectorLike}; use shared::core::geometry::{Point2f, Point2i, Vector2f, VectorLike};
use shared::core::image::{WrapMode, WrapMode2D}; use shared::core::image::{WrapMode, WrapMode2D};
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::math::{lerp, safe_sqrt, square}; use shared::utils::math::{lerp, safe_sqrt, square};
use shared::Float;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::ops::{Add, Mul, Sub}; use std::ops::{Add, Mul, Sub};
use std::path::Path; use std::path::Path;
@ -69,18 +69,18 @@ pub trait MIPMapSample:
Copy + Add<Output = Self> + Sub<Output = Self> + Mul<Float, Output = Self> + std::fmt::Debug Copy + Add<Output = Self> + Sub<Output = Self> + Mul<Float, Output = Self> + std::fmt::Debug
{ {
fn zero() -> Self; fn zero() -> Self;
fn sample_bilerp(image: &Image, st: Point2f, wrap: WrapMode2D) -> Self; fn sample_bilerp(image: &HostImage, st: Point2f, wrap: WrapMode2D) -> Self;
fn sample_texel(image: &Image, st: Point2i, wrap: WrapMode2D) -> Self; fn sample_texel(image: &HostImage, st: Point2i, wrap: WrapMode2D) -> Self;
} }
impl MIPMapSample for Float { impl MIPMapSample for Float {
fn zero() -> Self { fn zero() -> Self {
0. 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) 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) image.get_channel_with_wrap(st, 0, wrap)
} }
} }
@ -89,7 +89,7 @@ impl MIPMapSample for RGB {
fn zero() -> Self { fn zero() -> Self {
RGB::new(0., 0., 0.) 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(); let nc = image.n_channels();
if nc >= 3 { if nc >= 3 {
let r = image.bilerp_channel_with_wrap(st, 0, wrap); let r = image.bilerp_channel_with_wrap(st, 0, wrap);
@ -101,7 +101,7 @@ impl MIPMapSample for RGB {
RGB::new(v, v, v) 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(); let nc = image.n_channels();
if nc >= 3 { if nc >= 3 {
let r = image.get_channel_with_wrap(st, 0, wrap); let r = image.get_channel_with_wrap(st, 0, wrap);
@ -117,7 +117,7 @@ impl MIPMapSample for RGB {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MIPMap { pub struct MIPMap {
pub pyramid: Vec<Image>, pub pyramid: Vec<HostImage>,
pub color_space: Option<RGBColorSpace>, pub color_space: Option<RGBColorSpace>,
pub wrap_mode: WrapMode, pub wrap_mode: WrapMode,
pub options: MIPMapFilterOptions, pub options: MIPMapFilterOptions,
@ -127,12 +127,12 @@ pub struct MIPMap {
impl MIPMap { impl MIPMap {
pub fn new( pub fn new(
image: Image, image: HostImage,
color_space: Option<RGBColorSpace>, color_space: Option<RGBColorSpace>,
wrap_mode: WrapMode, wrap_mode: WrapMode,
options: MIPMapFilterOptions, options: MIPMapFilterOptions,
) -> Self { ) -> Self {
let pyramid = Image::generate_pyramid(image, wrap_mode); let pyramid = HostImage::generate_pyramid(image, wrap_mode);
Self { Self {
pyramid, pyramid,
color_space, color_space,
@ -160,11 +160,11 @@ impl MIPMap {
self.color_space.clone() self.color_space.clone()
} }
pub fn get_level(&self, level: usize) -> &Image { pub fn get_level(&self, level: usize) -> &HostImage {
&self.pyramid[level] &self.pyramid[level]
} }
pub fn base_image(&self) -> &Image { pub fn base_image(&self) -> &HostImage {
&self.pyramid[0] &self.pyramid[0]
} }
@ -321,7 +321,7 @@ impl MIPMap {
wrap_mode: WrapMode, wrap_mode: WrapMode,
encoding: ColorEncoding, encoding: ColorEncoding,
) -> Result<MIPMap, ()> { ) -> Result<MIPMap, ()> {
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; let image = image_and_metadata.image;
Ok(MIPMap::new( Ok(MIPMap::new(
image, image,
@ -345,7 +345,7 @@ impl MIPMap {
} }
#[cfg(feature = "cuda")] #[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::*; use cuda_runtime_sys::*;
let base = &pyramid[0]; let base = &pyramid[0];

View file

@ -8,7 +8,6 @@ pub mod mipmap;
pub mod parallel; pub mod parallel;
pub mod parameters; pub mod parameters;
pub mod parser; pub mod parser;
pub mod sampling;
pub mod strings; pub mod strings;
pub mod upload; pub mod upload;
@ -18,6 +17,7 @@ pub use parameters::{
ParameterDictionary, ParsedParameter, ParsedParameterVector, TextureParameterDictionary, ParameterDictionary, ParsedParameter, ParsedParameterVector, TextureParameterDictionary,
}; };
pub use strings::*; pub use strings::*;
pub use mipmap::{MIPMap, MIPMapFilterOptions};
pub use upload::{Upload, ArenaUpload}; pub use upload::{Upload, ArenaUpload};
#[cfg(feature = "vulkan")] #[cfg(feature = "vulkan")]

View file

@ -12,7 +12,7 @@ use shared::spectra::{
PiecewiseLinearSpectrum, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum, PiecewiseLinearSpectrum, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum,
RGBUnboundedSpectrum, RGBUnboundedSpectrum,
}; };
use shared::Float; use shared::{gvec_from_slice, leak, Float};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{ use std::sync::{
@ -682,8 +682,8 @@ impl ParameterDictionary {
.unzip(); .unzip();
vec![Spectrum::Piecewise(leak(PiecewiseLinearSpectrum { vec![Spectrum::Piecewise(leak(PiecewiseLinearSpectrum {
lambdas: gvec_from_slice(lambdas), lambdas: gvec_from_slice(&lambdas),
values: gvec_from_slice(values), values: gvec_from_slice(&values),
count: lambdas.len() as u32, count: lambdas.len() as u32,
}))] }))]
} }

View file

@ -1,4 +1,6 @@
use crate::core::texture::{FloatTexture, SpectrumTexture}; use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::core::image::HostImage;
use shared::core::image::Image;
use crate::Arena; use crate::Arena;
use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture}; use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture};
use shared::textures::*; use shared::textures::*;
@ -149,6 +151,27 @@ impl Upload for Option<Arc<SpectrumTexture>> {
} }
} }
impl Upload for HostImage {
type Target = Ptr<Image>;
fn upload(self, arena: &Arena) -> Ptr<Image> {
arena.alloc(self.inner)
}
}
impl Upload for Option<HostImage> {
type Target = Ptr<Image>;
fn upload(self, arena: &Arena) -> Ptr<Image> {
arena.alloc_opt(self.map(|h| h.inner))
}
}
impl Upload for Option<Arc<HostImage>> {
type Target = Ptr<Image>;
fn upload(self, arena: &Arena) -> Ptr<Image> {
arena.alloc_opt(self.map(|h| h.as_ref().inner.clone()))
}
}
pub trait ArenaUpload { pub trait ArenaUpload {
fn upload<T: Upload>(&self, value: T) -> T::Target; fn upload<T: Upload>(&self, value: T) -> T::Target;
} }