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] {
&mut self.data
}

View file

@ -12,4 +12,4 @@ pub use cylinder::*;
pub use disk::*;
pub use sphere::*;
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::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) }

View file

@ -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<P: PrimitiveTrait + Clone + Send + Sync> BVHAggregate<P> {
impl BVHAggregate<Primitive> {
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
.iter()
.map(|n| shared::core::aggregates::LinearBVHNode {
@ -902,13 +900,12 @@ impl BVHAggregate<Primitive> {
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,
}
}

View file

@ -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<Image> = None;
let mut aperture_image: Option<HostImage> = 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);

View file

@ -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(

View file

@ -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<Arc<Spectrum>> = 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();

View file

@ -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),
}
}
}

View file

@ -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<u8> {
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<ColorEncoding>) -> Result<ImageAnd
let h = dyn_img.height() as i32;
let res = Point2i::new(w, h);
// Check if it was loaded as high precision or standard
let rgb_names = vec!["R", "G", "B"];
let rgba_names = vec!["R", "G", "B", "A"];
let rgb_names: &[&str] = &["R", "G", "B"];
let rgba_names: &[&str] = &["R", "G", "B", "A"];
let image = match dyn_img {
DynamicImage::ImageRgb32F(buf) => 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<ImageAndMetadata> {
|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<ImageAndMetadata> {
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<ImageAndMetadata> {
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<ImageAndMetadata> {
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<ImageAndMetadata> {
}
}
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 })

View file

@ -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<str>],
) -> Self {
pub fn from_f32(data: &[f32], resolution: Point2i, channel_names: &[impl AsRef<str>]) -> 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<str>],
) -> Self {
pub fn from_f16(data: &[u16], resolution: Point2i, channel_names: &[impl AsRef<str>]) -> 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<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 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;

View file

@ -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<T: PixelStorageTrait>(
fn downsample_kernel<T: PixelStorageTrait>(
dst: &mut [T],
dst_res: Point2i,
prev: &Image,
prev: &HostImage,
wrap: WrapMode2D,
) {
let w = dst_res.x() as usize;

View file

@ -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<DenselySampledSpectrum> {
@ -117,7 +117,7 @@ pub fn create_area_light(
shape: &Shape,
alpha_tex: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
arena: &mut Arena,
arena: &Arena,
) -> Result<Ptr<Light>> {
let light = crate::lights::diffuse::create(
render_from_light, medium, parameters, loc,

View file

@ -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<Arc<Image>>,
normal_map: Option<Arc<HostImage>>,
named_materials: &HashMap<String, Material>,
loc: &FileLoc,
arena: &Arena,
@ -22,7 +22,7 @@ pub trait MaterialFactory {
fn create(
name: &str,
params: &TextureParameterDictionary,
normal_map: Option<Arc<Image>>,
normal_map: Option<Arc<HostImage>>,
named_materials: &HashMap<String, Material>,
loc: FileLoc,
arena: &Arena,
@ -35,7 +35,7 @@ impl MaterialFactory for Material {
fn create(
name: &str,
parameters: &TextureParameterDictionary,
normal_map: Option<Arc<Image>>,
normal_map: Option<Arc<HostImage>>,
named_materials: &HashMap<String, Material>,
loc: FileLoc,
arena: &Arena,

View file

@ -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<Option<Arc<Image>>> {
) -> Result<Option<Arc<HostImage>>> {
let filename = resolve_filename(&params.get_one_string("normalmap", "")?);
if filename.is_empty() {
return Ok(None);

View file

@ -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<SceneEntity>,
pub normal_map_jobs: HashMap<String, AsyncJob<Arc<Image>>>,
pub normal_maps: HashMap<String, Arc<Image>>,
pub normal_map_jobs: HashMap<String, AsyncJob<Arc<HostImage>>>,
pub normal_maps: HashMap<String, Arc<HostImage>>,
}
#[derive(Debug, Default)]

View file

@ -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<Ptr<Shape>> = (0..n_tris)
.map(|i| {
let tri_shape = Shape::Triangle(TriangleShape {

View file

@ -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,
}

View file

@ -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<CameraTransform>,
loc: &FileLoc,
_arena: &Arena,
arena: &Arena,
) -> Result<Film> {
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()) {

View file

@ -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 {

View file

@ -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<CameraTransform>,
loc: &FileLoc,
_arena: &Arena,
arena: &Arena,
) -> Result<Film> {
// 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()) {

View file

@ -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<T>(
let mut wave_end = 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;
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();

View file

@ -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;

View file

@ -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(&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 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,

View file

@ -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(&params.get_one_string("filename", "")?);
let image: Ptr<Image> = if filename.is_empty() {
let image: Ptr<HostImage> = 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<Image> {
fn convert_to_luminance_image(image: &HostImage, filename: &str, loc: &FileLoc) -> Result<HostImage> {
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;

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::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<Light> {
@ -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 = &image;
let mut data: Vec<Float> = (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();

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::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);

View file

@ -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 {

View file

@ -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,

View file

@ -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,

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::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 {

View file

@ -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<P: AsRef<Path>>(
fn from_ply<P: AsRef<Path>>(
filename: P,
render_from_object: &Transform,
reverse_orientation: bool,
) -> AnyResult<Self>;
) -> AnyResult<Self>
where
Self: Sized;
}
impl ReadTriangleMesh for TriangleMesh {

View file

@ -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 {

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::{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<Arc<RGBColorSpace>> = LazyLock::new(|| {
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 r = Point2f::new(0.708, 0.292);
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))
});
pub static ACES: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
pub static ACES: LazyLock<Arc<RGBColorSpace>> = 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<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
#[derive(Debug, Clone)]
pub struct StandardColorSpaces {
pub srgb: Arc<RGBColorSpaceData>,
pub dci_p3: Arc<RGBColorSpaceData>,
pub rec2020: Arc<RGBColorSpaceData>,
pub aces2065_1: Arc<RGBColorSpaceData>,
pub srgb: Arc<RGBColorSpace>,
pub dci_p3: Arc<RGBColorSpace>,
pub rec2020: Arc<RGBColorSpace>,
pub aces2065_1: Arc<RGBColorSpace>,
}
impl StandardColorSpaces {
@ -141,3 +140,4 @@ pub fn default_colorspace_ref() -> &'static RGBColorSpace {
pub fn default_illuminant() -> Spectrum {
Spectrum::Dense(default_colorspace().illuminant)
}

View file

@ -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;

View file

@ -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;

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) {
let mut bump = self.bump.lock();
let (ptr, len) = bump.alloc_slice(values);

View file

@ -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<Output = Self> + Sub<Output = Self> + Mul<Float, Output = Self> + 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<Image>,
pub pyramid: Vec<HostImage>,
pub color_space: Option<RGBColorSpace>,
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<RGBColorSpace>,
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<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;
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];

View file

@ -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")]

View file

@ -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,
}))]
}

View file

@ -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<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 {
fn upload<T: Upload>(&self, value: T) -> T::Target;
}