Compare commits

...

5 commits

112 changed files with 2720 additions and 4811 deletions

View file

@ -9,7 +9,7 @@ use_f64 = []
use_gpu = ["dep:wgpu"] use_gpu = ["dep:wgpu"]
use_nvtx = ["dep:nvtx"] use_nvtx = ["dep:nvtx"]
cuda = ["dep:cudarc", "dep:cust", "dep:cust_raw", "dep:cuda-runtime-sys"] cuda = ["dep:cudarc", "dep:cust", "dep:cust_raw", "dep:cuda-runtime-sys"]
vulkan = ["ash", "gpu-allocator"] vulkan = ["dep:ash", "dep:gpu-allocator", "shared/vulkan"]
ash = ["dep:ash"] ash = ["dep:ash"]
gpu-allocator = ["dep:gpu-allocator"] gpu-allocator = ["dep:gpu-allocator"]
jemalloc = ["jemallocator"] jemalloc = ["jemallocator"]
@ -54,6 +54,7 @@ cust_raw = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", def
cuda-runtime-sys = { version = "0.3.0-alpha.1", optional = true} cuda-runtime-sys = { version = "0.3.0-alpha.1", optional = true}
cudarc = { version = "0.18.2", features = ["cuda-13000"], optional = true } cudarc = { version = "0.18.2", features = ["cuda-13000"], optional = true }
jemallocator = { version = "0.5", optional = true } jemallocator = { version = "0.5", optional = true }
syn = "2.0.117"
[build-dependencies] [build-dependencies]
spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true } spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true }

View file

@ -5,8 +5,12 @@ edition = "2024"
[dependencies] [dependencies]
bitflags = "2.10.0" bitflags = "2.10.0"
half = "2.7.1"
bytemuck = { version = "1.24.0", features = ["derive"] } bytemuck = { version = "1.24.0", features = ["derive"] }
enum_dispatch = "0.3.13" enum_dispatch = "0.3.13"
ash = { version = "0.38", optional = true }
parking_lot = { version = "0.12.5", optional = true }
gpu-allocator = { version = "0.28", features = ["vulkan"], optional = true }
num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } num-traits = { version = "0.2.19", default-features = false, features = ["libm"] }
cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true } cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
@ -14,3 +18,4 @@ cuda_std = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", def
use_f64 = [] use_f64 = []
cuda = ["cuda_std"] cuda = ["cuda_std"]
cpu_debug = [] cpu_debug = []
vulkan = ["dep:ash", "dep:gpu-allocator", "dep:parking_lot"]

View file

@ -16,13 +16,13 @@ use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct MeasuredBxDFData { pub struct MeasuredBxDFData {
pub wavelengths: Ptr<Float>,
pub spectra: PiecewiseLinear2D<3>,
pub ndf: PiecewiseLinear2D<0>,
pub vndf: PiecewiseLinear2D<2>,
pub sigma: PiecewiseLinear2D<0>,
pub isotropic: bool, pub isotropic: bool,
pub luminance: PiecewiseLinear2D<2>, pub wavelengths: Ptr<Float>,
pub spectra: Ptr<PiecewiseLinear2D<3>>,
pub ndf: Ptr<PiecewiseLinear2D<0>>,
pub vndf: Ptr<PiecewiseLinear2D<2>>,
pub sigma: Ptr<PiecewiseLinear2D<0>>,
pub luminance: Ptr<PiecewiseLinear2D<2>>,
} }
#[repr(C)] #[repr(C)]

View file

@ -1,18 +1,16 @@
use crate::PI;
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform}; use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
use crate::core::color::SRGB; use crate::core::color::SRGB;
use crate::core::film::Film; use crate::core::film::Film;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike, Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike,
}; };
use crate::core::image::{DeviceImage, PixelFormat}; use crate::core::image::{Image, PixelFormat};
use crate::core::medium::Medium; use crate::core::medium::Medium;
use crate::core::pbrt::Float;
use crate::core::sampler::CameraSample; use crate::core::sampler::CameraSample;
use crate::core::scattering::refract; use crate::core::scattering::refract;
use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr;
use crate::utils::math::{lerp, quadratic, square}; use crate::utils::math::{lerp, quadratic, square};
use crate::{Float, GVec, Ptr, PI, gvec};
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
@ -34,19 +32,92 @@ pub struct ExitPupilSample {
pub const EXIT_PUPIL_SAMPLES: usize = 64; pub const EXIT_PUPIL_SAMPLES: usize = 64;
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Clone)]
pub struct RealisticCamera { pub struct RealisticCamera {
pub base: CameraBase, pub base: CameraBase,
pub focus_distance: Float, pub focus_distance: Float,
pub set_aperture_diameter: Float, pub set_aperture_diameter: Float,
pub aperture_image: Ptr<DeviceImage>, pub aperture_image: Ptr<Image>,
pub element_interfaces: Ptr<LensElementInterface>, pub element_interfaces: GVec<LensElementInterface>,
pub n_elements: usize, pub n_elements: usize,
pub physical_extent: Bounds2f, pub physical_extent: Bounds2f,
pub exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES], pub exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES],
} }
impl RealisticCamera { impl RealisticCamera {
pub fn new(
base: CameraBase,
lens_params: &[Float],
focus_distance: Float,
set_aperture_diameter: Float,
aperture_image: Ptr<Image>,
) -> Self {
let film_ptr = base.film;
if film_ptr.is_null() {
panic!("Camera must have a film");
}
let film = &*film_ptr;
let aspect = film.full_resolution().x() as Float / film.full_resolution().y() as Float;
let diagonal = film.diagonal();
let x = (square(diagonal) / (1.0 + square(diagonal))).sqrt();
let y = x * aspect;
let physical_extent =
Bounds2f::from_points(Point2f::new(-x / 2., -y / 2.), Point2f::new(x / 2., y / 2.));
let mut element_interfaces: GVec<LensElementInterface> = gvec();
for i in (0..lens_params.len()).step_by(4) {
let curvature_radius = lens_params[i] / 1000.0;
let thickness = lens_params[i + 1] / 1000.0;
let eta = lens_params[i + 2];
let mut aperture_diameter = lens_params[i + 3] / 1000.0;
if curvature_radius == 0.0 {
aperture_diameter /= 1000.0;
if set_aperture_diameter > aperture_diameter {
// println!("Aperture is larger than possible")
aperture_diameter = -1.;
} else {
aperture_diameter = set_aperture_diameter;
}
}
let el_int = LensElementInterface {
curvature_radius,
thickness,
eta,
aperture_radius: aperture_diameter / 2.0,
};
element_interfaces.push(el_int);
}
let half_diag = film.diagonal() / 2.0;
let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES];
for i in 0..EXIT_PUPIL_SAMPLES {
let r0 = (i as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
let r1 = ((i + 1) as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
exit_pupil_bounds[i] =
RealisticCamera::compute_exit_pupil_bounds(&element_interfaces, r0, r1);
}
let n_elements = element_interfaces.len();
RealisticCamera {
base,
focus_distance,
element_interfaces,
n_elements,
physical_extent,
set_aperture_diameter,
aperture_image,
exit_pupil_bounds,
}
}
unsafe fn lens(&self, idx: usize) -> &LensElementInterface {
unsafe { &*self.element_interfaces.as_ptr().add(idx) }
}
pub fn compute_cardinal_points(r_in: Ray, r_out: Ray) -> (Float, Float) { pub fn compute_cardinal_points(r_in: Ray, r_out: Ray) -> (Float, Float) {
let tf = -r_out.o.x() / r_out.d.x(); let tf = -r_out.o.x() / r_out.d.x();
let tp = (r_in.o.x() - r_out.o.x()) / r_out.d.x(); let tp = (r_in.o.x() - r_out.o.x()) / r_out.d.x();
@ -96,14 +167,12 @@ impl RealisticCamera {
); );
} }
let delta = (pz[1] - z + pz[0] - c.sqrt()) / 2.; let delta = (pz[1] - z + pz[0] - c.sqrt()) / 2.;
let last_interface = unsafe { self.element_interfaces.add(self.n_elements) }; let last_interface = unsafe { self.lens(self.n_elements - 1) };
last_interface.thickness + delta last_interface.thickness + delta
} }
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f { pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
let interface_array = unsafe { let interface_array = self.element_interfaces.as_slice();
core::slice::from_raw_parts(self.element_interfaces.as_raw(), self.n_elements as usize)
};
Self::compute_exit_pupil_bounds(interface_array, film_x_0, film_x_1) Self::compute_exit_pupil_bounds(interface_array, film_x_0, film_x_1)
} }
@ -125,7 +194,6 @@ impl RealisticCamera {
let trace_lenses_from_film = |_ray: Ray, _place: Option<Ray>| true; let trace_lenses_from_film = |_ray: Ray, _place: Option<Ray>| true;
for i in 0..n_samples { for i in 0..n_samples {
// Find location of sample points on $x$ segment and rear lens element // Find location of sample points on $x$ segment and rear lens element
//
let p_film = Point3f::new( let p_film = Point3f::new(
lerp((i as Float + 0.5) / n_samples as Float, film_x_0, film_x_1), lerp((i as Float + 0.5) / n_samples as Float, film_x_0, film_x_1),
0., 0.,
@ -206,7 +274,7 @@ impl RealisticCamera {
); );
for i in (0..self.n_elements - 1).rev() { for i in (0..self.n_elements - 1).rev() {
let element: &LensElementInterface = unsafe { &self.element_interfaces.add(i) }; let element: &LensElementInterface = unsafe { self.lens(i) };
// Update ray from film accounting for interaction with _element_ // Update ray from film accounting for interaction with _element_
element_z -= element.thickness; element_z -= element.thickness;
@ -245,7 +313,7 @@ impl RealisticCamera {
// Update ray path for element interface interaction // Update ray path for element interface interaction
if !is_stop { if !is_stop {
let eta_i = element.eta; let eta_i = element.eta;
let interface_i = unsafe { self.element_interfaces.add(i - 1) }; let interface_i = unsafe { self.lens(i) };
let eta_t = if i > 0 && interface_i.eta != 0. { let eta_t = if i > 0 && interface_i.eta != 0. {
interface_i.eta interface_i.eta
} else { } else {
@ -306,21 +374,21 @@ impl RealisticCamera {
} }
pub fn lens_rear_z(&self) -> Float { pub fn lens_rear_z(&self) -> Float {
let last_interface = unsafe { self.element_interfaces.add(self.n_elements - 1) }; let last_interface = unsafe { self.lens(self.n_elements - 1) };
last_interface.thickness last_interface.thickness
} }
pub fn lens_front_z(&self) -> Float { pub fn lens_front_z(&self) -> Float {
let mut z_sum = 0.; let mut z_sum = 0.;
for i in 0..self.n_elements { for i in 0..self.n_elements {
let element = unsafe { self.element_interfaces.add(i) }; let element = unsafe { self.lens(i) };
z_sum += element.thickness; z_sum += element.thickness;
} }
z_sum z_sum
} }
pub fn rear_element_radius(&self) -> Float { pub fn rear_element_radius(&self) -> Float {
let last_interface = unsafe { self.element_interfaces.add(self.n_elements - 1) }; let last_interface = unsafe { self.lens(self.n_elements - 1) };
last_interface.aperture_radius last_interface.aperture_radius
} }
} }

View file

@ -1,8 +1,7 @@
use crate::core::geometry::{Bounds3f, Ray, Vector3f}; use crate::core::geometry::{Bounds3f, Ray, Vector3f};
use crate::core::pbrt::Float;
use crate::core::primitive::{Primitive, PrimitiveTrait}; use crate::core::primitive::{Primitive, PrimitiveTrait};
use crate::core::shape::ShapeIntersection; use crate::core::shape::ShapeIntersection;
use crate::utils::Ptr; use crate::{Float, Ptr, GVec, gvec};
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -15,40 +14,40 @@ pub struct LinearBVHNode {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub struct DeviceBVHAggregate { pub struct BVHAggregate {
pub max_prims_in_node: u32,
pub primitives: Ptr<Primitive>,
pub primitive_count: u32,
pub nodes: Ptr<LinearBVHNode>,
pub node_count: u32, pub node_count: u32,
pub max_prims_in_node: u32,
pub primitive_count: u32,
pub primitives: GVec<Primitive>,
pub nodes: GVec<LinearBVHNode>,
} }
impl DeviceBVHAggregate { impl BVHAggregate {
pub const fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
max_prims_in_node: 0, max_prims_in_node: 0,
primitives: Ptr::null(), primitives: gvec(),
primitive_count: 0, primitive_count: 0,
nodes: Ptr::null(), nodes: gvec(),
node_count: 0, node_count: 0,
} }
} }
#[inline(always)] #[inline(always)]
fn node(&self, i: usize) -> &LinearBVHNode { fn node(&self, i: usize) -> &LinearBVHNode {
unsafe { self.nodes.at(i) } unsafe { self.nodes.get_unchecked(i) }
} }
#[inline(always)] #[inline(always)]
fn primitive(&self, i: usize) -> &Primitive { fn primitive(&self, i: usize) -> &Primitive {
unsafe { self.primitives.at(i) } unsafe { self.primitives.get_unchecked(i) }
} }
} }
impl PrimitiveTrait for DeviceBVHAggregate { impl PrimitiveTrait for BVHAggregate {
fn bounds(&self) -> Bounds3f { fn bounds(&self) -> Bounds3f {
if self.nodes.is_null() || self.node_count == 0 { if self.nodes.is_empty() || self.node_count == 0 {
Bounds3f::default() Bounds3f::default()
} else { } else {
self.node(0).bounds self.node(0).bounds
@ -56,7 +55,7 @@ impl PrimitiveTrait for DeviceBVHAggregate {
} }
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> { fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
if self.nodes.is_null() { if self.nodes.is_empty() {
return None; return None;
} }
@ -124,7 +123,7 @@ impl PrimitiveTrait for DeviceBVHAggregate {
} }
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool { fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
if self.nodes.is_null() || self.node_count == 0 { if self.nodes.is_empty() || self.node_count == 0 {
return false; return false;
} }

View file

@ -118,7 +118,7 @@ pub struct CameraBase {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Clone)]
#[enum_dispatch(CameraTrait)] #[enum_dispatch(CameraTrait)]
pub enum Camera { pub enum Camera {
Perspective(PerspectiveCamera), Perspective(PerspectiveCamera),

View file

@ -1,18 +1,15 @@
use crate::core::geometry::Point2f;
use crate::core::spectrum::Spectrum;
use crate::utils::find_interval;
use crate::utils::math::{clamp, evaluate_polynomial, lerp, SquareMatrix, SquareMatrix3f};
use crate::{Float, GVec, Ptr};
use core::any::TypeId; use core::any::TypeId;
use core::fmt; use core::fmt;
use core::ops::{ use core::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
}; };
use crate::Float;
use crate::core::geometry::Point2f;
use crate::core::spectrum::Spectrum;
use crate::utils::Ptr;
use crate::utils::find_interval;
use crate::utils::math::{SquareMatrix, SquareMatrix3f, clamp, evaluate_polynomial, lerp};
use num_traits::Float as NumFloat;
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
@ -311,17 +308,33 @@ impl RGB {
pub fn min_component_index(&self) -> u32 { pub fn min_component_index(&self) -> u32 {
if self.r < self.g { if self.r < self.g {
if self.r < self.b { 0 } else { 2 } if self.r < self.b {
0
} else {
2
}
} else { } else {
if self.g < self.b { 1 } else { 2 } if self.g < self.b {
1
} else {
2
}
} }
} }
pub fn max_component_index(&self) -> u32 { pub fn max_component_index(&self) -> u32 {
if self.r > self.g { if self.r > self.g {
if self.r > self.b { 0 } else { 2 } if self.r > self.b {
0
} else {
2
}
} else { } else {
if self.g > self.b { 1 } else { 2 } if self.g > self.b {
1
} else {
2
}
} }
} }
@ -606,7 +619,6 @@ pub struct RGBSigmoidPolynomial {
} }
impl RGBSigmoidPolynomial { impl RGBSigmoidPolynomial {
#[cfg(not(target_os = "cuda"))]
pub fn new(c0: Float, c1: Float, c2: Float) -> Self { pub fn new(c0: Float, c1: Float, c2: Float) -> Self {
Self { c0, c1, c2 } Self { c0, c1, c2 }
} }
@ -1087,7 +1099,7 @@ impl Mul<Float> for Coeffs {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct RGBToSpectrumTable { pub struct RGBToSpectrumTable {
pub z_nodes: Ptr<Float>, pub z_nodes: Ptr<Float>,
pub coeffs: Ptr<Coeffs>, pub coeffs: Ptr<Coeffs>,
@ -1115,7 +1127,11 @@ impl RGBToSpectrumTable {
// Identify the primary bucket (c) based on the dominant axis // Identify the primary bucket (c) based on the dominant axis
let c_idx = if rgb[0] > rgb[1] { let c_idx = if rgb[0] > rgb[1] {
if rgb[0] > rgb[2] { 0 } else { 2 } if rgb[0] > rgb[2] {
0
} else {
2
}
} else if rgb[1] > rgb[2] { } else if rgb[1] > rgb[2] {
1 1
} else { } else {
@ -1137,10 +1153,9 @@ impl RGBToSpectrumTable {
let x = coord_a / z; let x = coord_a / z;
let y = coord_b / z; let y = coord_b / z;
let z_nodes_slice = let z_nodes = unsafe { core::slice::from_raw_parts(self.z_nodes.as_raw(), RES as usize) };
unsafe { core::slice::from_raw_parts(self.z_nodes.as_raw(), RES as usize) }; let zi = find_interval(RES, |i| z_nodes[i as usize] < z) as usize;
let zi = find_interval(RES, |i| z_nodes_slice[i as usize] < z) as usize; let dz = (z - z_nodes[zi]) / (z_nodes[zi + 1] - z_nodes[zi]);
let dz = (z - z_nodes_slice[zi]) / (z_nodes_slice[zi + 1] - z_nodes_slice[zi]);
let x_float = x * (RES - 1) as Float; let x_float = x * (RES - 1) as Float;
let xi = (x_float as u32).min(RES - 2); let xi = (x_float as u32).min(RES - 2);
let dx = x_float - xi as Float; let dx = x_float - xi as Float;

View file

@ -1,24 +1,23 @@
use crate::core::camera::CameraTransform; use crate::core::camera::CameraTransform;
use crate::core::color::{MatrixMulColor, RGB, SRGB, XYZ, white_balance}; use crate::core::color::{white_balance, MatrixMulColor, RGB, SRGB, XYZ};
use crate::core::filter::{Filter, FilterTrait}; use crate::core::filter::{Filter, FilterTrait};
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi, Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi,
Vector2i, Vector3f, Vector2i, Vector3f,
}; };
use crate::core::image::{DeviceImage, PixelFormat}; use crate::core::image::{Image, PixelFormat};
use crate::core::interaction::SurfaceInteraction; use crate::core::interaction::SurfaceInteraction;
use crate::core::pbrt::Float;
use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra}; use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra};
use crate::spectra::{ use crate::spectra::{
ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, colorspace, ConstantSpectrum, DenselySampledSpectrum, PiecewiseLinearSpectrum, RGBColorSpace,
PiecewiseLinearSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, colorspace, SampledSpectrum, SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
}; };
use crate::utils::containers::DeviceArray2D;
use crate::utils::math::linear_least_squares; use crate::utils::math::linear_least_squares;
use crate::utils::math::{SquareMatrix, wrap_equal_area_square}; use crate::utils::math::{wrap_equal_area_square, SquareMatrix};
use crate::utils::sampling::VarianceEstimator; use crate::utils::sampling::VarianceEstimator;
use crate::utils::transform::AnimatedTransform; use crate::utils::transform::AnimatedTransform;
use crate::utils::{AtomicFloat, Ptr, gpu_array_from_fn}; use crate::utils::{gpu_array_from_fn, AtomicFloat};
use crate::{gvec_from_slice, gvec_with_capacity, Array2D, Float, GVec, Ptr};
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
@ -29,7 +28,7 @@ pub struct RGBFilm {
pub write_fp16: bool, pub write_fp16: bool,
pub filter_integral: Float, pub filter_integral: Float,
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>, pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
pub pixels: DeviceArray2D<RGBPixel>, pub pixels: Array2D<RGBPixel>,
} }
#[repr(C)] #[repr(C)]
@ -51,6 +50,43 @@ impl Default for RGBPixel {
} }
impl RGBFilm { impl RGBFilm {
pub fn new(
base: FilmBase,
colorspace: &RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
) -> Self {
let sensor_ptr = base.sensor;
// TODO: This wont work on gpu, need to add check on host side
if sensor_ptr.is_null() {
panic!("Film must have a sensor");
}
let sensor = &*sensor_ptr;
let filter_integral = base.filter.integral();
let sensor_matrix = sensor.xyz_from_sensor_rgb;
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix;
let width = base.pixel_bounds.p_max.x() - base.pixel_bounds.p_min.x();
let height = base.pixel_bounds.p_max.y() - base.pixel_bounds.p_min.y();
let count = (width * height) as usize;
let mut pixel_vec = gvec_with_capacity(count);
for _ in 0..count {
pixel_vec.push(RGBPixel::default());
}
let pixels: Array2D<RGBPixel> = Array2D::new(base.pixel_bounds);
RGBFilm {
base,
max_component_value,
write_fp16,
filter_integral,
output_rgbf_from_sensor_rgb,
pixels,
}
}
pub fn base(&self) -> &FilmBase { pub fn base(&self) -> &FilmBase {
&self.base &self.base
} }
@ -59,7 +95,7 @@ impl RGBFilm {
&mut self.base &mut self.base
} }
pub fn get_sensor(&self) -> &DevicePixelSensor { pub fn get_sensor(&self) -> &PixelSensor {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
{ {
if self.base.sensor.is_null() { if self.base.sensor.is_null() {
@ -206,7 +242,7 @@ pub struct GBufferFilm {
pub base: FilmBase, pub base: FilmBase,
pub output_from_render: AnimatedTransform, pub output_from_render: AnimatedTransform,
pub apply_inverse: bool, pub apply_inverse: bool,
pub pixels: DeviceArray2D<GBufferPixel>, pub pixels: Array2D<GBufferPixel>,
pub colorspace: RGBColorSpace, pub colorspace: RGBColorSpace,
pub max_component_value: Float, pub max_component_value: Float,
pub write_fp16: bool, pub write_fp16: bool,
@ -215,6 +251,37 @@ pub struct GBufferFilm {
} }
impl GBufferFilm { impl GBufferFilm {
pub fn new(
base: &FilmBase,
output_from_render: &AnimatedTransform,
apply_inverse: bool,
colorspace: &RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
) -> Self {
assert!(!base.pixel_bounds.is_empty());
let sensor_ptr = base.sensor;
if sensor_ptr.is_null() {
panic!("Film must have a sensor");
}
let sensor = &*sensor_ptr;
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor.xyz_from_sensor_rgb;
let filter_integral = base.filter.integral();
let pixels = Array2D::new(base.pixel_bounds);
GBufferFilm {
base: base.clone(),
output_from_render: *output_from_render,
apply_inverse,
pixels,
colorspace: colorspace.clone(),
max_component_value,
write_fp16,
filter_integral,
output_rgbf_from_sensor_rgb,
}
}
pub fn base(&self) -> &FilmBase { pub fn base(&self) -> &FilmBase {
&self.base &self.base
} }
@ -223,7 +290,7 @@ impl GBufferFilm {
&mut self.base &mut self.base
} }
pub fn get_sensor(&self) -> &DevicePixelSensor { pub fn get_sensor(&self) -> &PixelSensor {
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
{ {
if self.base.sensor.is_null() { if self.base.sensor.is_null() {
@ -352,24 +419,68 @@ impl Default for SpectralPixel {
#[cfg_attr(target_os = "cuda", derive(Copy, Clone))] #[cfg_attr(target_os = "cuda", derive(Copy, Clone))]
pub struct SpectralFilm { pub struct SpectralFilm {
pub base: FilmBase, pub base: FilmBase,
pub colorspace: RGBColorSpace,
pub lambda_min: Float, pub lambda_min: Float,
pub lambda_max: Float, pub lambda_max: Float,
pub n_buckets: usize, pub n_buckets: usize,
pub max_component_value: Float, pub max_component_value: Float,
pub write_fp16: bool, pub write_fp16: bool,
pub filter_integral: Float, pub filter_integral: Float,
pub pixels: DeviceArray2D<SpectralPixel>, pub colorspace: RGBColorSpace,
pub pixels: Array2D<SpectralPixel>,
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>, pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
pub bucket_sums: *mut f64, pub bucket_sums: GVec<f64>,
pub weight_sums: *mut f64, pub weight_sums: GVec<f64>,
pub bucket_splats: *mut AtomicFloat, pub bucket_splats: GVec<AtomicFloat>,
} }
unsafe impl Send for SpectralFilm {} unsafe impl Send for SpectralFilm {}
unsafe impl Sync for SpectralFilm {} unsafe impl Sync for SpectralFilm {}
impl SpectralFilm { impl SpectralFilm {
pub fn new(
base: &FilmBase,
lambda_min: Float,
lambda_max: Float,
n_buckets: usize,
colorspace: &RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
) -> Self {
let n_pixels = base.pixel_bounds.area() as usize;
let total_buckets = n_pixels * n_buckets;
let bucket_sums = gvec_with_capacity(total_buckets);
let weight_sums = gvec_with_capacity(total_buckets);
let mut bucket_splats = gvec_with_capacity(total_buckets);
for _ in 0..total_buckets {
bucket_splats.push(AtomicFloat::new(0.0));
}
let mut pixels = Array2D::<SpectralPixel>::new(base.pixel_bounds);
for i in 0..n_pixels {
let pixel = pixels.get_linear_mut(i);
pixel.bucket_offset = i * n_buckets;
}
SpectralFilm {
base: *base,
colorspace: colorspace.clone(),
lambda_min,
lambda_max,
n_buckets,
max_component_value,
write_fp16,
filter_integral: base.filter.integral(),
output_rgbf_from_sensor_rgb: SquareMatrix::identity(),
pixels: Array2D::from_slice(base.pixel_bounds, pixels.as_slice()),
bucket_sums,
weight_sums,
bucket_splats,
}
}
pub fn base(&self) -> &FilmBase { pub fn base(&self) -> &FilmBase {
&self.base &self.base
} }
@ -404,15 +515,15 @@ impl SpectralFilm {
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct DevicePixelSensor { pub struct PixelSensor {
pub xyz_from_sensor_rgb: SquareMatrix<Float, 3>, pub xyz_from_sensor_rgb: SquareMatrix<Float, 3>,
pub r_bar: DenselySampledSpectrum, pub r_bar: Ptr<DenselySampledSpectrum>,
pub g_bar: DenselySampledSpectrum, pub g_bar: Ptr<DenselySampledSpectrum>,
pub b_bar: DenselySampledSpectrum, pub b_bar: Ptr<DenselySampledSpectrum>,
pub imaging_ratio: Float, pub imaging_ratio: Float,
} }
impl DevicePixelSensor { impl PixelSensor {
pub fn project_reflectance<T>( pub fn project_reflectance<T>(
refl: &Spectrum, refl: &Spectrum,
illum: &Spectrum, illum: &Spectrum,
@ -492,7 +603,7 @@ pub struct FilmBase {
pub pixel_bounds: Bounds2i, pub pixel_bounds: Bounds2i,
pub filter: Filter, pub filter: Filter,
pub diagonal: Float, pub diagonal: Float,
pub sensor: Ptr<DevicePixelSensor>, pub sensor: Ptr<PixelSensor>,
} }
#[repr(C)] #[repr(C)]

View file

@ -1,9 +1,8 @@
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f}; use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
use crate::core::pbrt::Float;
use crate::filters::*; use crate::filters::*;
use crate::utils::containers::DeviceArray2D;
use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc}; use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc};
use crate::utils::sampling::DevicePiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::{Array2D, Float, Ptr};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
pub struct FilterSample { pub struct FilterSample {
@ -12,23 +11,45 @@ pub struct FilterSample {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug)]
pub struct DeviceFilterSampler { pub struct FilterSampler {
pub domain: Bounds2f, pub domain: Bounds2f,
pub distrib: DevicePiecewiseConstant2D, pub distrib: PiecewiseConstant2D,
pub f: DeviceArray2D<Float>, pub f: Array2D<Float>,
} }
impl DeviceFilterSampler { impl FilterSampler {
pub fn new<F>(radius: Vector2f, func: F) -> Self
where
F: Fn(Point2f) -> Float,
{
let domain = Bounds2f::from_points(
Point2f::new(-radius.x(), -radius.y()),
Point2f::new(radius.x(), radius.y()),
);
let nx = (32.0 * radius.x()) as i32;
let ny = (32.0 * radius.y()) as i32;
let mut f = Array2D::new_dims(nx, ny);
for y in 0..f.y_size() {
for x in 0..f.x_size() {
let p = domain.lerp(Point2f::new(
(x as Float + 0.5) / f.x_size() as Float,
(y as Float + 0.5) / f.y_size() as Float,
));
f[(x as i32, y as i32)] = func(p);
}
}
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
Self { domain, distrib, f }
}
pub fn sample(&self, u: Point2f) -> FilterSample { pub fn sample(&self, u: Point2f) -> FilterSample {
let (p, pdf, pi) = self.distrib.sample(u); let (p, pdf, pi) = self.distrib.sample(u);
if pdf == 0.0 { if pdf == 0.0 {
return FilterSample { p, weight: 0.0 }; return FilterSample { p, weight: 0.0 };
} }
let idx = pi.x() as usize + pi.y() as usize * self.f.x_size();
let idx = pi.x() as u32 + self.f.x_size(); let weight = self.f.as_slice()[idx] / pdf;
let weight = *self.f.get_linear(idx as usize) / pdf;
FilterSample { p, weight } FilterSample { p, weight }
} }
} }
@ -38,16 +59,33 @@ pub trait FilterTrait {
fn radius(&self) -> Vector2f; fn radius(&self) -> Vector2f;
fn evaluate(&self, p: Point2f) -> Float; fn evaluate(&self, p: Point2f) -> Float;
fn integral(&self) -> Float; fn integral(&self) -> Float;
fn sample(&self, u: Point2f) -> DeviceFilterSample; fn sample(&self, u: Point2f) -> FilterSample;
} }
#[repr(C)] #[repr(C)]
#[enum_dispatch(FilterTrait)] #[enum_dispatch(FilterTrait)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Filter { pub enum Filter {
Box(BoxFilter), Box(Ptr<BoxFilter>),
Gaussian(GaussianFilter), Gaussian(Ptr<GaussianFilter>),
Mitchell(MitchellFilter), Mitchell(Ptr<MitchellFilter>),
LanczosSinc(LanczosSincFilter), LanczosSinc(Ptr<LanczosSincFilter>),
Triangle(TriangleFilter), Triangle(Ptr<TriangleFilter>),
}
impl<T: FilterTrait> FilterTrait for Ptr<T> {
fn radius(&self) -> Vector2f {
unsafe { self.as_ref().radius() }
}
fn integral(&self) -> Float {
unsafe { self.as_ref().integral() }
}
fn evaluate(&self, p: Point2f) -> Float {
unsafe { self.as_ref().evaluate(p) }
}
fn sample(&self, p: Point2f) -> FilterSample {
unsafe { self.as_ref().sample(p) }
}
} }

View file

@ -1,9 +1,7 @@
use crate::Float;
use crate::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR}; use crate::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i}; use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
use crate::utils::Ptr;
use crate::utils::containers::DeviceArray2D;
use crate::utils::math::{f16_to_f32_software, lerp, square}; use crate::utils::math::{f16_to_f32_software, lerp, square};
use crate::{gvec_with_capacity, Float, GVec};
use core::hash; use core::hash;
use core::ops::{Deref, DerefMut}; use core::ops::{Deref, DerefMut};
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
@ -68,60 +66,134 @@ impl PixelFormat {
} }
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Debug)]
pub struct Pixels { pub struct Pixels {
ptr: Ptr<u8>, data: GVec<u8>,
format: PixelFormat, format: PixelFormat,
} }
impl Pixels { impl Pixels {
pub fn new_u8(ptr: Ptr<u8>) -> Self { pub fn new(data: GVec<u8>, format: PixelFormat) -> Self {
Self { data, format }
}
pub fn format(&self) -> PixelFormat {
self.format
}
pub fn as_ptr(&self) -> *const u8 {
self.data.as_ptr()
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn texel_count(&self) -> usize {
self.data.len() / self.format.texel_bytes()
}
pub unsafe fn read_u8(&self, texel_offset: usize) -> u8 {
unsafe { *self.data.as_ptr().add(texel_offset) }
}
pub unsafe fn read_f16(&self, texel_offset: usize) -> u16 {
let byte_offset = texel_offset * 2;
unsafe { *(self.data.as_ptr().add(byte_offset) as *const u16) }
}
pub unsafe fn read_f32(&self, texel_offset: usize) -> f32 {
let byte_offset = texel_offset * 4;
unsafe { *(self.data.as_ptr().add(byte_offset) as *const f32) }
}
pub unsafe fn read(&self, texel_offset: usize, encoding: &ColorEncoding) -> Float {
match self.format {
PixelFormat::U8 => encoding.to_linear_scalar(self.read_u8(texel_offset)),
PixelFormat::F16 => f16_to_f32_software(self.read_f16(texel_offset)),
PixelFormat::F32 => self.read_f32(texel_offset),
}
}
pub unsafe fn write_u8(&mut self, texel_offset: usize, val: u8) {
unsafe { *self.data.as_mut_ptr().add(texel_offset) = val };
}
pub unsafe fn write_f16(&mut self, texel_offset: usize, val: u16) {
let byte_offset = texel_offset * 2;
unsafe { *(self.data.as_mut_ptr().add(byte_offset) as *mut u16) = val };
}
pub unsafe fn write_f32(&mut self, texel_offset: usize, val: f32) {
let byte_offset = texel_offset * 4;
unsafe { *(self.data.as_mut_ptr().add(byte_offset) as *mut f32) = val };
}
pub fn empty(texel_count: usize, format: PixelFormat) -> Self {
let byte_count = texel_count * format.texel_bytes();
let mut data = gvec_with_capacity(byte_count);
data.resize(byte_count, 0u8);
Self { data, format }
}
pub fn from_u8_slice(slice: &[u8]) -> Self {
let mut data = gvec_with_capacity(slice.len());
data.extend_from_slice(slice);
Self { Self {
ptr, data,
format: PixelFormat::U8, format: PixelFormat::U8,
} }
} }
pub fn new_f16(ptr: Ptr<u16>) -> Self { pub fn from_f32_slice(slice: &[f32]) -> Self {
let byte_len = slice.len() * 4;
let mut data = gvec_with_capacity(byte_len);
let bytes = unsafe { core::slice::from_raw_parts(slice.as_ptr() as *const u8, byte_len) };
data.extend_from_slice(bytes);
Self { Self {
ptr: Ptr::from_raw(ptr.as_raw() as *const u8), data,
format: PixelFormat::F16,
}
}
pub fn new_f32(ptr: Ptr<f32>) -> Self {
Self {
ptr: Ptr::from_raw(ptr.as_raw() as *const u8),
format: PixelFormat::F32, format: PixelFormat::F32,
} }
} }
unsafe fn read_u8(&self, byte_offset: usize) -> u8 { pub fn from_f16_slice(slice: &[u16]) -> Self {
unsafe { *self.ptr.as_raw().add(byte_offset) } let byte_len = slice.len() * 2;
let mut data = gvec_with_capacity(byte_len);
let bytes = unsafe { core::slice::from_raw_parts(slice.as_ptr() as *const u8, byte_len) };
data.extend_from_slice(bytes);
Self {
data,
format: PixelFormat::F16,
}
} }
unsafe fn read_f16(&self, elem_offset: usize) -> u16 { pub fn as_u8_mut(&mut self) -> &mut [u8] {
let byte_offset = elem_offset * 2; &mut self.data
unsafe { *(self.ptr.as_raw().add(byte_offset) as *const u16) }
} }
unsafe fn read_f32(&self, elem_offset: usize) -> f32 { pub fn as_f16_mut(&mut self) -> &mut [u16] {
let byte_offset = elem_offset * 4; assert_eq!(self.format, PixelFormat::F16);
unsafe { *(self.ptr.as_raw().add(byte_offset) as *const f32) } unsafe {
core::slice::from_raw_parts_mut(self.data.as_mut_ptr() as *mut u16, self.data.len() / 2)
}
} }
// Unsure if ill need this pub fn as_f32_slice(&self) -> &[f32] {
// pub unsafe fn read(&self, offset: usize) -> Float { assert_eq!(self.format, PixelFormat::F32);
// match self.format { unsafe {
// PixelFormat::U8 => unsafe { self.read_u8(offset) as Float / 255.0 }, core::slice::from_raw_parts(self.data.as_ptr() as *const f32, self.data.len() / 4)
// PixelFormat::F16 => unsafe { f16_to_f32_software(self.read_f16(offset)) }, }
// PixelFormat::F32 => unsafe { self.read_f32(offset) }, }
// }
// } pub fn as_f32_slice_mut(&mut self) -> &mut [f32] {
assert_eq!(self.format, PixelFormat::F32);
unsafe {
core::slice::from_raw_parts_mut(self.data.as_mut_ptr() as *mut f32, self.data.len() / 4)
}
}
} }
#[repr(C)] #[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
pub struct ImageBase { pub struct ImageBase {
pub format: PixelFormat, pub format: PixelFormat,
pub encoding: ColorEncoding, pub encoding: ColorEncoding,
@ -129,58 +201,205 @@ pub struct ImageBase {
pub n_channels: i32, pub n_channels: i32,
} }
impl ImageBase { #[derive(Clone, Debug)]
pub struct Image {
pub format: PixelFormat,
pub encoding: ColorEncoding,
pub resolution: Point2i,
pub n_channels: i32,
pub pixels: Pixels,
}
impl Image {
pub fn new(
format: PixelFormat,
resolution: Point2i,
n_channels: i32,
encoding: ColorEncoding,
) -> Self {
let texel_count = (resolution.x() * resolution.y()) as usize * n_channels as usize;
Self {
format,
encoding,
resolution,
n_channels,
pixels: Pixels::empty(texel_count, format),
}
}
pub fn from_u8(
data: &[u8],
resolution: Point2i,
n_channels: i32,
encoding: ColorEncoding,
) -> Self {
let expected = (resolution.x() * resolution.y()) as usize * n_channels as usize;
assert_eq!(data.len(), expected, "Pixel data size mismatch");
Self {
format: PixelFormat::U8,
encoding,
resolution,
n_channels,
pixels: Pixels::from_u8_slice(data),
}
}
pub fn from_f32(data: &[f32], resolution: Point2i, n_channels: i32) -> Self {
let expected = (resolution.x() * resolution.y()) as usize * n_channels as usize;
assert_eq!(data.len(), expected, "Pixel data size mismatch");
Self {
format: PixelFormat::F32,
encoding: LINEAR,
resolution,
n_channels,
pixels: Pixels::from_f32_slice(data),
}
}
pub fn from_f16(data: &[u16], resolution: Point2i, n_channels: i32) -> Self {
let expected = (resolution.x() * resolution.y()) as usize * n_channels as usize;
assert_eq!(data.len(), expected, "Pixel data size mismatch");
Self {
format: PixelFormat::F16,
encoding: LINEAR,
resolution,
n_channels,
pixels: Pixels::from_f16_slice(data),
}
}
pub fn resolution(&self) -> Point2i {
self.resolution
}
pub fn n_channels(&self) -> i32 {
self.n_channels
}
pub fn format(&self) -> PixelFormat {
self.format
}
pub fn is_valid(&self) -> bool {
self.resolution.x() > 0 && self.resolution.y() > 0
}
pub fn pixel_offset(&self, p: Point2i) -> usize {
let width = self.resolution.x() as usize;
(p.y() as usize * width + p.x() as usize) * self.n_channels as usize
}
pub fn remap_pixel_coords(&self, p: &mut Point2i, wrap_mode: WrapMode2D) -> bool { pub fn remap_pixel_coords(&self, p: &mut Point2i, wrap_mode: WrapMode2D) -> bool {
let resolution = self.resolution;
for i in 0..2 { for i in 0..2 {
if p[i] >= 0 && p[i] < resolution[i] { if p[i] >= 0 && p[i] < self.resolution[i] {
continue; continue;
} }
match wrap_mode.uv[i] { match wrap_mode.uv[i] {
WrapMode::Black => return false, WrapMode::Black => return false,
WrapMode::Clamp => p[i] = p[i].clamp(0, resolution[i] - 1), WrapMode::Clamp => p[i] = p[i].clamp(0, self.resolution[i] - 1),
WrapMode::Repeat => p[i] = p[i].rem_euclid(resolution[i]), WrapMode::Repeat => p[i] = p[i].rem_euclid(self.resolution[i]),
WrapMode::OctahedralSphere => { WrapMode::OctahedralSphere => {
p[i] = p[i].clamp(0, resolution[i] - 1); p[i] = p[i].clamp(0, self.resolution[i] - 1);
} }
} }
} }
true true
} }
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DeviceImage {
pub base: ImageBase,
pub pixels: Pixels,
}
impl DeviceImage {
pub fn base(&self) -> ImageBase { pub fn base(&self) -> ImageBase {
self.base ImageBase {
format: self.format,
encoding: self.encoding,
resolution: self.resolution,
n_channels: self.n_channels,
}
} }
pub fn resolution(&self) -> Point2i { pub fn get_channel(&self, p: Point2i, c: i32) -> Float {
self.base.resolution self.get_channel_with_wrap(p, c, WrapMode::Clamp.into())
} }
pub fn is_valid(&self) -> bool { pub fn get_channel_with_wrap(&self, mut p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float {
self.resolution().x() > 0 && self.resolution().y() > 0 if !self.remap_pixel_coords(&mut p, wrap_mode) {
return 0.0;
}
let offset = self.pixel_offset(p) + c as usize;
unsafe { self.pixels.read(offset, &self.encoding) }
} }
pub fn format(&self) -> PixelFormat { pub fn lookup_nearest_channel(&self, p: Point2f, c: i32) -> Float {
self.base().format self.lookup_nearest_channel_with_wrap(p, c, WrapMode::Clamp.into())
} }
pub fn n_channels(&self) -> i32 { pub fn lookup_nearest_channel_with_wrap(
self.base().n_channels &self,
p: Point2f,
c: i32,
wrap_mode: WrapMode2D,
) -> Float {
let pi = Point2i::new(
p.x() as i32 * self.resolution().x(),
p.y() as i32 * self.resolution().y(),
);
self.get_channel_with_wrap(pi, c, wrap_mode)
} }
pub fn pixel_offset(&self, p: Point2i) -> u32 { pub fn get_channels<const N: usize>(&self, p: Point2i) -> [Float; N] {
let width = self.resolution().x() as u32; self.get_channels_with_wrap(p, WrapMode::Clamp.into())
let idx = p.y() as u32 * width + p.x() as u32; }
idx * (self.n_channels() as u32)
pub fn get_channels_with_wrap<const N: usize>(
&self,
mut p: Point2i,
wrap_mode: WrapMode2D,
) -> [Float; N] {
debug_assert!(N <= self.n_channels as usize);
let mut result = [0.0; N];
if !self.remap_pixel_coords(&mut p, wrap_mode) {
return result;
}
let offset = self.pixel_offset(p);
for i in 0..N {
result[i] = unsafe { self.pixels.read(offset + i, &self.encoding) };
}
result
}
pub fn get_channels_average(&self, p: Point2i) -> Float {
let offset = self.pixel_offset(p);
let nc = self.n_channels as usize;
let mut sum = 0.0;
for i in 0..nc {
sum += unsafe { self.pixels.read(offset + i, &self.encoding) };
}
sum / nc as Float
}
pub fn set_channel(&mut self, p: Point2i, c: i32, mut value: Float) {
if value.is_nan() {
value = 0.0;
}
let res = self.resolution;
if p.x() < 0 || p.x() >= res.x() || p.y() < 0 || p.y() >= res.y() {
return;
}
let offset = self.pixel_offset(p) + c as usize;
unsafe {
match self.format {
PixelFormat::U8 => {
self.pixels
.write_u8(offset, self.encoding.from_linear_scalar(value));
}
PixelFormat::F16 => {
self.pixels
.write_f16(offset, half::f16::from_f32(value).to_bits());
}
PixelFormat::F32 => {
self.pixels.write_f32(offset, value);
}
}
}
} }
pub fn bilerp_channel(&self, p: Point2f, c: i32) -> Float { pub fn bilerp_channel(&self, p: Point2f, c: i32) -> Float {
@ -188,8 +407,8 @@ impl DeviceImage {
} }
pub fn bilerp_channel_with_wrap(&self, p: Point2f, c: i32, wrap_mode: WrapMode2D) -> Float { pub fn bilerp_channel_with_wrap(&self, p: Point2f, c: i32, wrap_mode: WrapMode2D) -> Float {
let x = p.x() * self.resolution().x() as Float - 0.5; let x = p.x() * self.resolution.x() as Float - 0.5;
let y = p.y() * self.resolution().y() as Float - 0.5; let y = p.y() * self.resolution.y() as Float - 0.5;
let xi = x.floor() as i32; let xi = x.floor() as i32;
let yi = y.floor() as i32; let yi = y.floor() as i32;
let dx = x - xi as Float; let dx = x - xi as Float;
@ -200,51 +419,30 @@ impl DeviceImage {
let v11 = self.get_channel_with_wrap(Point2i::new(xi + 1, yi + 1), c, wrap_mode); let v11 = self.get_channel_with_wrap(Point2i::new(xi + 1, yi + 1), c, wrap_mode);
lerp(dy, lerp(dx, v00, v10), lerp(dx, v01, v11)) lerp(dy, lerp(dx, v00, v10), lerp(dx, v01, v11))
} }
}
pub trait ImageAccess { pub fn has_any_infinite_pixels(&self) -> bool {
fn get_channel_with_wrap(&self, p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float; for y in 0..self.resolution.y() {
fn get_channel(&self, p: Point2i, c: i32) -> Float; for x in 0..self.resolution.x() {
fn lookup_nearest_channel_with_wrap(&self, p: Point2f, c: i32, wrap_mode: WrapMode2D) -> Float; for c in 0..self.n_channels {
fn lookup_nearest_channel(&self, p: Point2f, c: i32) -> Float; if self.get_channel(Point2i::new(x, y), c).is_infinite() {
} return true;
}
impl ImageAccess for DeviceImage {
fn get_channel_with_wrap(&self, mut p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float {
if !self.base.remap_pixel_coords(&mut p, wrap_mode) {
return 0.;
}
let offset = (self.pixel_offset(p) + c as u32) as usize;
unsafe {
match self.pixels.format {
PixelFormat::U8 => {
let raw_val = self.pixels.read_u8(offset);
self.base.encoding.to_linear_scalar(raw_val)
} }
PixelFormat::F16 => {
let raw_val = self.pixels.read_f16(offset);
f16_to_f32_software(raw_val)
}
PixelFormat::F32 => self.pixels.read_f32(offset),
} }
} }
false
} }
fn get_channel(&self, p: Point2i, c: i32) -> Float { pub fn has_any_nan_pixels(&self) -> bool {
self.get_channel_with_wrap(p, c, WrapMode::Clamp.into()) for y in 0..self.resolution.y() {
} for x in 0..self.resolution.x() {
for c in 0..self.n_channels {
fn lookup_nearest_channel_with_wrap(&self, p: Point2f, c: i32, wrap_mode: WrapMode2D) -> Float { if self.get_channel(Point2i::new(x, y), c).is_nan() {
let pi = Point2i::new( return true;
p.x() as i32 * self.resolution().x(), }
p.y() as i32 * self.resolution().y(), }
); }
}
self.get_channel_with_wrap(pi, c, wrap_mode) false
}
fn lookup_nearest_channel(&self, p: Point2f, c: i32) -> Float {
self.lookup_nearest_channel_with_wrap(p, c, WrapMode::Clamp.into())
} }
} }

View file

@ -7,7 +7,7 @@ use crate::core::camera::{Camera, CameraTrait};
use crate::core::geometry::{ use crate::core::geometry::{
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike, Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
}; };
use crate::core::image::DeviceImage; use crate::core::image::Image;
use crate::core::light::{Light, LightTrait}; use crate::core::light::{Light, LightTrait};
use crate::core::material::{ use crate::core::material::{
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map, Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
@ -348,7 +348,7 @@ impl SurfaceInteraction {
&mut self, &mut self,
tex_eval: &UniversalTextureEvaluator, tex_eval: &UniversalTextureEvaluator,
displacement: Ptr<GPUFloatTexture>, displacement: Ptr<GPUFloatTexture>,
normal_image: Ptr<DeviceImage>, normal_image: Ptr<Image>,
) { ) {
let ctx = NormalBumpEvalContext::from(&*self); let ctx = NormalBumpEvalContext::from(&*self);
let (dpdu, dpdv) = if !displacement.is_null() { let (dpdu, dpdv) = if !displacement.is_null() {

View file

@ -3,7 +3,6 @@ use crate::core::geometry::{
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
Vector3f, VectorLike, cos_theta, Vector3f, VectorLike, cos_theta,
}; };
use crate::core::image::DeviceImage;
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionBase, InteractionTrait, MediumInteraction, SimpleInteraction, Interaction, InteractionBase, InteractionTrait, MediumInteraction, SimpleInteraction,
SurfaceInteraction, SurfaceInteraction,
@ -17,7 +16,6 @@ use crate::spectra::{
}; };
use crate::utils::Transform; use crate::utils::Transform;
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square}; use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
use crate::utils::sampling::DevicePiecewiseConstant2D;
use crate::{Float, PI}; use crate::{Float, PI};
use bitflags::bitflags; use bitflags::bitflags;

View file

@ -10,7 +10,7 @@ use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF; use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF; use crate::core::bxdf::BxDF;
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike}; use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
use crate::core::image::{DeviceImage, WrapMode, WrapMode2D}; use crate::core::image::{Image, WrapMode, WrapMode2D};
use crate::core::interaction::{Interaction, InteractionTrait, ShadingGeom, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, ShadingGeom, SurfaceInteraction};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
@ -103,7 +103,7 @@ impl From<&NormalBumpEvalContext> for TextureEvalContext {
} }
} }
pub fn normal_map(normal_map: &DeviceImage, ctx: &NormalBumpEvalContext) -> (Vector3f, Vector3f) { pub fn normal_map(normal_map: &Image, ctx: &NormalBumpEvalContext) -> (Vector3f, Vector3f) {
let wrap = WrapMode2D::from(WrapMode::Repeat); let wrap = WrapMode2D::from(WrapMode::Repeat);
let uv = Point2f::new(ctx.uv[0], 1. - ctx.uv[1]); let uv = Point2f::new(ctx.uv[0], 1. - ctx.uv[1]);
let r = normal_map.bilerp_channel_with_wrap(uv, 0, wrap); let r = normal_map.bilerp_channel_with_wrap(uv, 0, wrap);
@ -173,7 +173,7 @@ pub trait MaterialTrait {
) -> Option<BSSRDF>; ) -> Option<BSSRDF>;
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool; fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
fn get_normal_map(&self) -> Option<&DeviceImage>; fn get_normal_map(&self) -> Option<&Image>;
fn get_displacement(&self) -> Ptr<GPUFloatTexture>; fn get_displacement(&self) -> Ptr<GPUFloatTexture>;
fn has_subsurface_scattering(&self) -> bool; fn has_subsurface_scattering(&self) -> bool;
} }

View file

@ -1,19 +1,18 @@
use enum_dispatch::enum_dispatch;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike, spherical_direction, spherical_direction, Bounds3f, Frame, Point2f, Point3f, Point3i, Ray, Vector3f, VectorLike,
}; };
use crate::core::pbrt::{Float, INV_4_PI, PI}; use crate::core::pbrt::{Float, INV_4_PI, PI};
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{ use crate::spectra::{
BlackbodySpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, RGBIlluminantSpectrum, BlackbodySpectrum, DenselySampledSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum,
RGBUnboundedSpectrum, SampledSpectrum, SampledWavelengths, SampledSpectrum, SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN,
}; };
use crate::utils::containers::SampledGrid; use crate::utils::containers::SampledGrid;
use crate::utils::math::{clamp, square}; use crate::utils::math::{clamp, square};
use crate::utils::ptr::Ptr;
use crate::utils::rng::Rng; use crate::utils::rng::Rng;
use crate::utils::transform::Transform; use crate::utils::transform::Transform;
use crate::{gvec_with_capacity, GVec, Ptr};
use enum_dispatch::enum_dispatch;
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
@ -90,11 +89,11 @@ impl PhaseFunctionTrait for HGPhaseFunction {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub struct MajorantGrid { pub struct MajorantGrid {
pub bounds: Bounds3f, pub bounds: Bounds3f,
pub res: Point3i, pub res: Point3i,
pub voxels: *mut Float, pub voxels: GVec<Float>,
pub n_voxels: u32, pub n_voxels: u32,
} }
@ -102,21 +101,21 @@ unsafe impl Send for MajorantGrid {}
unsafe impl Sync for MajorantGrid {} unsafe impl Sync for MajorantGrid {}
impl MajorantGrid { impl MajorantGrid {
// #[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
// pub fn new(bounds: Bounds3f, res: Point3i) -> Self { pub fn new(bounds: Bounds3f, res: Point3i) -> Self {
// let n_voxels = (res.x() * res.y() * res.z()) as usize; let n_voxels = (res.x() * res.y() * res.z()) as usize;
// let voxels = Vec::with_capacity(n_voxels); let voxels = gvec_with_capacity(n_voxels);
// Self { Self {
// bounds, bounds,
// res, res,
// voxels: voxels.as_ptr(), voxels,
// n_voxels: n_voxels as u32, n_voxels: n_voxels as u32,
// } }
// } }
//
#[inline(always)] #[inline(always)]
fn is_valid(&self) -> bool { fn is_valid(&self) -> bool {
!self.voxels.is_null() !self.voxels.is_empty()
} }
#[inline(always)] #[inline(always)]
@ -128,7 +127,7 @@ impl MajorantGrid {
let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x; let idx = z * self.res.x() * self.res.y() + y * self.res.x() + x;
if idx >= 0 && (idx as u32) < self.n_voxels { if idx >= 0 && (idx as u32) < self.n_voxels {
unsafe { *self.voxels.add(idx as usize) } unsafe { *self.voxels.as_ptr().add(idx as usize) }
} else { } else {
0.0 0.0
} }
@ -143,7 +142,7 @@ impl MajorantGrid {
let idx = x + self.res.x() * (y + self.res.y() * z); let idx = x + self.res.x() * (y + self.res.y() * z);
unsafe { unsafe {
*self.voxels.add(idx as usize) = v; *self.voxels.as_mut_ptr().add(idx as usize) = v;
} }
} }
@ -230,7 +229,7 @@ pub struct DDAMajorantIterator {
sigma_t: SampledSpectrum, sigma_t: SampledSpectrum,
t_min: Float, t_min: Float,
t_max: Float, t_max: Float,
grid: MajorantGrid, grid: Ptr<MajorantGrid>,
next_crossing_t: [Float; 3], next_crossing_t: [Float; 3],
delta_t: [Float; 3], delta_t: [Float; 3],
step: [i32; 3], step: [i32; 3],
@ -250,7 +249,7 @@ impl DDAMajorantIterator {
t_min, t_min,
t_max, t_max,
sigma_t: *sigma_t, sigma_t: *sigma_t,
grid: *grid, grid: Ptr::from(&*grid),
next_crossing_t: [0.0; 3], next_crossing_t: [0.0; 3],
delta_t: [0.0; 3], delta_t: [0.0; 3],
step: [0; 3], step: [0; 3],
@ -453,9 +452,9 @@ pub enum Medium {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct HomogeneousMedium { pub struct HomogeneousMedium {
pub sigma_a_spec: DenselySampledSpectrum, pub sigma_a_spec: Ptr<DenselySampledSpectrum>,
pub sigma_s_spec: DenselySampledSpectrum, pub sigma_s_spec: Ptr<DenselySampledSpectrum>,
pub le_spec: DenselySampledSpectrum, pub le_spec: Ptr<DenselySampledSpectrum>,
pub phase: HGPhaseFunction, pub phase: HGPhaseFunction,
} }
@ -497,15 +496,15 @@ impl MediumTrait for HomogeneousMedium {
pub struct GridMedium { pub struct GridMedium {
pub bounds: Bounds3f, pub bounds: Bounds3f,
pub render_from_medium: Transform, pub render_from_medium: Transform,
pub sigma_a_spec: DenselySampledSpectrum, pub sigma_a_spec: Ptr<DenselySampledSpectrum>,
pub sigma_s_spec: DenselySampledSpectrum, pub sigma_s_spec: Ptr<DenselySampledSpectrum>,
pub density_grid: SampledGrid<Float>, pub density_grid: Ptr<SampledGrid<Float>>,
pub phase: HGPhaseFunction, pub phase: HGPhaseFunction,
pub temperature_grid: Option<SampledGrid<Float>>, pub temperature_grid: Ptr<SampledGrid<Float>>,
pub le_spec: DenselySampledSpectrum, pub le_spec: Ptr<DenselySampledSpectrum>,
pub le_scale: SampledGrid<Float>, pub le_scale: Ptr<SampledGrid<Float>>,
pub is_emissive: bool, pub is_emissive: bool,
pub majorant_grid: MajorantGrid, pub majorant_grid: Ptr<MajorantGrid>,
} }
impl MediumTrait for GridMedium { impl MediumTrait for GridMedium {
@ -528,8 +527,8 @@ impl MediumTrait for GridMedium {
}; };
let le = if scale > 0.0 { let le = if scale > 0.0 {
let raw_emission = if let Some(temp_grid) = &self.temperature_grid { let raw_emission = if !self.temperature_grid.is_null() {
let temp = temp_grid.lookup(p); let temp = self.temperature_grid.lookup(p);
BlackbodySpectrum::new(temp).sample(lambda) BlackbodySpectrum::new(temp).sample(lambda)
} else { } else {
self.le_spec.sample(lambda) self.le_spec.sample(lambda)
@ -593,10 +592,10 @@ pub struct RGBGridMedium {
pub phase: HGPhaseFunction, pub phase: HGPhaseFunction,
pub le_scale: Float, pub le_scale: Float,
pub sigma_scale: Float, pub sigma_scale: Float,
pub sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>, pub sigma_a_grid: Ptr<SampledGrid<RGBUnboundedSpectrum>>,
pub sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>, pub sigma_s_grid: Ptr<SampledGrid<RGBUnboundedSpectrum>>,
pub le_grid: SampledGrid<RGBIlluminantSpectrum>, pub le_grid: Ptr<SampledGrid<RGBIlluminantSpectrum>>,
pub majorant_grid: MajorantGrid, pub majorant_grid: Ptr<MajorantGrid>,
} }
impl MediumTrait for RGBGridMedium { impl MediumTrait for RGBGridMedium {

View file

@ -2,7 +2,6 @@ use crate::core::geometry::Lerp;
use core::ops::{Add, Mul}; use core::ops::{Add, Mul};
use num_traits::{Num, PrimInt}; use num_traits::{Num, PrimInt};
use crate::core::image::DeviceImage;
use crate::core::light::LightTrait; use crate::core::light::LightTrait;
use crate::core::shape::Shape; use crate::core::shape::Shape;
use crate::core::texture::GPUFloatTexture; use crate::core::texture::GPUFloatTexture;

View file

@ -1,5 +1,5 @@
use crate::core::geometry::{Bounds3f, Ray}; use crate::core::geometry::{Bounds3f, Ray};
use crate::core::aggregates::DeviceBVHAggregate; use crate::core::aggregates::BVHAggregate;
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::light::Light; use crate::core::light::Light;
use crate::core::material::Material; use crate::core::material::Material;
@ -213,6 +213,23 @@ pub enum Primitive {
Geometric(GeometricPrimitive), Geometric(GeometricPrimitive),
Transformed(TransformedPrimitive), Transformed(TransformedPrimitive),
Animated(AnimatedPrimitive), Animated(AnimatedPrimitive),
BVH(DeviceBVHAggregate), BVH(Ptr<BVHAggregate>),
KdTree(KdTreeAggregate), KdTree(KdTreeAggregate),
} }
impl<T: PrimitiveTrait> PrimitiveTrait for Ptr<T> {
fn bounds(&self) -> Bounds3f {
unsafe { self.as_ref().bounds() }
}
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
unsafe { self.as_ref().intersect(r, t_max) }
}
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
unsafe { self.as_ref().intersect_p(r, t_max) }
}
}

View file

@ -1,17 +1,17 @@
use crate::core::filter::FilterTrait; use crate::core::filter::FilterTrait;
use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f}; use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4}; use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4};
use crate::utils::Ptr;
use crate::utils::containers::DeviceArray2D;
use crate::utils::math::{ use crate::utils::math::{
BinaryPermuteScrambler, DeviceDigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler, clamp, encode_morton_2, inverse_radical_inverse, lerp, log2_int,
PRIME_TABLE_SIZE, Scrambler, clamp, encode_morton_2, inverse_radical_inverse, lerp, log2_int,
owen_scrambled_radical_inverse, permutation_element, radical_inverse, round_up_pow2, owen_scrambled_radical_inverse, permutation_element, radical_inverse, round_up_pow2,
scrambled_radical_inverse, sobol_interval_to_index, sobol_sample, scrambled_radical_inverse, sobol_interval_to_index, sobol_sample, BinaryPermuteScrambler,
DigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler, Scrambler,
PRIME_TABLE_SIZE,
}; };
use crate::utils::rng::Rng; use crate::utils::rng::Rng;
use crate::utils::sobol::N_SOBOL_DIMENSIONS; use crate::utils::sobol::N_SOBOL_DIMENSIONS;
use crate::utils::{hash::*, sobol}; use crate::utils::{hash::*, sobol};
use crate::{gvec, GVec, Ptr};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
#[repr(C)] #[repr(C)]
@ -100,7 +100,7 @@ pub struct HaltonSampler {
pub mult_inverse: [u64; 2], pub mult_inverse: [u64; 2],
pub halton_index: u64, pub halton_index: u64,
pub dim: u32, pub dim: u32,
pub digit_permutations: Ptr<DeviceDigitPermutation>, pub digit_permutations: Ptr<DigitPermutation>,
} }
#[allow(clippy::derivable_impls)] #[allow(clippy::derivable_impls)]

View file

@ -1,11 +1,11 @@
use crate::Float; use crate::{Float, Ptr};
use crate::core::color::{RGB, XYZ}; use crate::core::color::{RGB, XYZ};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
pub use crate::spectra::*; pub use crate::spectra::*;
#[enum_dispatch] #[enum_dispatch]
pub trait SpectrumTrait: Copy { pub trait SpectrumTrait {
fn evaluate(&self, lambda: Float) -> Float; fn evaluate(&self, lambda: Float) -> Float;
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
SampledSpectrum::from_fn(|i| self.evaluate(lambda[i])) SampledSpectrum::from_fn(|i| self.evaluate(lambda[i]))
@ -16,10 +16,10 @@ pub trait SpectrumTrait: Copy {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct StandardSpectra { pub struct StandardSpectra {
pub x: DenselySampledSpectrum, pub x: Ptr<DenselySampledSpectrum>,
pub y: DenselySampledSpectrum, pub y: Ptr<DenselySampledSpectrum>,
pub z: DenselySampledSpectrum, pub z: Ptr<DenselySampledSpectrum>,
pub d65: DenselySampledSpectrum, pub d65: Ptr<DenselySampledSpectrum>,
} }
unsafe impl Send for StandardSpectra {} unsafe impl Send for StandardSpectra {}
@ -30,14 +30,23 @@ unsafe impl Sync for StandardSpectra {}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Spectrum { pub enum Spectrum {
Constant(ConstantSpectrum), Constant(ConstantSpectrum),
Dense(DenselySampledSpectrum), Dense(Ptr<DenselySampledSpectrum>),
Piecewise(PiecewiseLinearSpectrum), Piecewise(Ptr<PiecewiseLinearSpectrum>),
Blackbody(BlackbodySpectrum), Blackbody(BlackbodySpectrum),
RGBAlbedo(RGBAlbedoSpectrum), RGBAlbedo(RGBAlbedoSpectrum),
RGBIlluminant(RGBIlluminantSpectrum), RGBIlluminant(RGBIlluminantSpectrum),
RGBUnbounded(RGBUnboundedSpectrum), RGBUnbounded(RGBUnboundedSpectrum),
} }
impl<T: SpectrumTrait> SpectrumTrait for Ptr<T> {
fn evaluate(&self, lambda: Float) -> Float {
unsafe { self.as_ref().evaluate(lambda) }
}
fn max_value(&self) -> Float {
unsafe { self.as_ref().max_value() }
}
}
impl Spectrum { impl Spectrum {
pub fn std_illuminant_d65() -> Self { pub fn std_illuminant_d65() -> Self {
unimplemented!("Use crate::spectra::default_illuminant() on host") unimplemented!("Use crate::spectra::default_illuminant() on host")

View file

@ -1,5 +1,5 @@
use crate::Float; use crate::Float;
use crate::core::filter::{DeviceFilterSample, FilterTrait}; use crate::core::filter::{FilterSample, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::lerp; use crate::utils::math::lerp;
@ -31,7 +31,7 @@ impl FilterTrait for BoxFilter {
(2.0 * self.radius.x()) * (2.0 * self.radius.y()) (2.0 * self.radius.x()) * (2.0 * self.radius.y())
} }
fn sample(&self, u: Point2f) -> DeviceFilterSample { fn sample(&self, u: Point2f) -> FilterSample {
let p = Point2f::new( let p = Point2f::new(
lerp(u[0], -self.radius.x(), self.radius.x()), lerp(u[0], -self.radius.x(), self.radius.x()),
lerp(u[1], -self.radius.y(), self.radius.y()), lerp(u[1], -self.radius.y(), self.radius.y()),

View file

@ -1,10 +1,10 @@
use crate::Float; use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::filter::{DeviceFilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::{gaussian, gaussian_integral}; use crate::utils::math::{gaussian, gaussian_integral};
use crate::{Ptr, Float};
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug)]
pub struct GaussianFilter { pub struct GaussianFilter {
pub radius: Vector2f, pub radius: Vector2f,
pub sigma: Float, pub sigma: Float,
@ -13,6 +13,25 @@ pub struct GaussianFilter {
pub sampler: FilterSampler, pub sampler: FilterSampler,
} }
impl GaussianFilter {
pub fn new(radius: Vector2f, sigma: Float) -> Self {
let exp_x = gaussian(radius.x(), 0.0, sigma);
let exp_y = gaussian(radius.y(), 0.0, sigma);
let sampler = FilterSampler::new(radius, move |p: Point2f| {
let gx = (gaussian(p.x(), 0.0, sigma) - exp_x).max(0.0);
let gy = (gaussian(p.y(), 0.0, sigma) - exp_y).max(0.0);
gx * gy
});
Self {
radius,
sigma,
exp_x,
exp_y,
sampler,
}
}
}
impl FilterTrait for GaussianFilter { impl FilterTrait for GaussianFilter {
fn radius(&self) -> Vector2f { fn radius(&self) -> Vector2f {
self.radius self.radius
@ -30,7 +49,7 @@ impl FilterTrait for GaussianFilter {
- 2.0 * self.radius.y() * self.exp_y) - 2.0 * self.radius.y() * self.exp_y)
} }
fn sample(&self, u: Point2f) -> DeviceFilterSample { fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u) self.sampler.sample(u)
} }
} }

View file

@ -1,10 +1,11 @@
use crate::Float; use crate::core::filter::{FilterSampler, FilterSample, FilterTrait};
use crate::core::filter::{DeviceFilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::{lerp, windowed_sinc}; use crate::utils::math::{lerp, windowed_sinc};
use crate::utils::rng::Rng;
use crate::Float;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, Copy)] #[derive(Clone, Debug)]
pub struct LanczosSincFilter { pub struct LanczosSincFilter {
pub radius: Vector2f, pub radius: Vector2f,
pub tau: Float, pub tau: Float,
@ -12,6 +13,38 @@ pub struct LanczosSincFilter {
pub integral: Float, pub integral: Float,
} }
impl LanczosSincFilter {
pub fn new(radius: Vector2f, tau: Float) -> Self {
let evaluate = move |p: Point2f| -> Float {
windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau)
};
let sampler = FilterSampler::new(radius, evaluate);
let sqrt_samples = 64u32;
let n_samples = sqrt_samples * sqrt_samples;
let area = (2.0 * radius.x()) * (2.0 * radius.y());
let mut sum = 0.0;
let mut rng = Rng::new(0);
for y in 0..sqrt_samples {
for x in 0..sqrt_samples {
let u = Point2f::new(
(x as Float + rng.uniform::<Float>()) / sqrt_samples as Float,
(y as Float + rng.uniform::<Float>()) / sqrt_samples as Float,
);
let p = Point2f::new(
lerp(u.x(), -radius.x(), radius.x()),
lerp(u.y(), -radius.y(), radius.y()),
);
sum += evaluate(p);
}
}
let integral = sum / n_samples as Float * area;
Self { radius, tau, sampler, integral }
}
}
impl FilterTrait for LanczosSincFilter { impl FilterTrait for LanczosSincFilter {
fn radius(&self) -> Vector2f { fn radius(&self) -> Vector2f {
self.radius self.radius
@ -26,7 +59,7 @@ impl FilterTrait for LanczosSincFilter {
self.integral self.integral
} }
fn sample(&self, u: Point2f) -> DeviceFilterSample { fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u) self.sampler.sample(u)
} }
} }

View file

@ -1,38 +1,50 @@
use crate::Float; use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
use crate::core::filter::{DeviceFilterSample, FilterSampler, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use crate::Float;
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Debug)]
pub struct MitchellFilter { pub struct MitchellFilter {
pub radius: Vector2f, pub radius: Vector2f,
pub b: Float, pub b: Float,
pub c: Float, pub c: Float,
pub sampler: DeviceFilterSampler, pub sampler: FilterSampler,
}
pub fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float {
let x = x.abs();
if x <= 1.0 {
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3)
+ (-18.0 + 12.0 * b + 6.0 * c) * x.powi(2)
+ (6.0 - 2.0 * b))
* (1.0 / 6.0)
} else if x <= 2.0 {
((-b - 6.0 * c) * x.powi(3)
+ (6.0 * b + 30.0 * c) * x.powi(2)
+ (-12.0 * b - 48.0 * c) * x
+ (8.0 * b + 24.0 * c))
* (1.0 / 6.0)
} else {
0.0
}
} }
impl MitchellFilter { impl MitchellFilter {
pub fn mitchell_1d_eval(b: Float, c: Float, x: Float) -> Float { pub fn new(radius: Vector2f, b: Float, c: Float) -> Self {
let x = x.abs(); let sampler = FilterSampler::new(radius, move |p: Point2f| {
if x <= 1.0 { mitchell_1d_eval(p.x() / radius.x(), b, c) * mitchell_1d_eval(p.y() / radius.y(), b, c)
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3) });
+ (-18.0 + 12.0 * b + 6.0 * c) * x.powi(2) Self {
+ (6.0 - 2.0 * b)) radius,
* (1.0 / 6.0) b,
} else if x <= 2.0 { c,
((-b - 6.0 * c) * x.powi(3) sampler,
+ (6.0 * b + 30.0 * c) * x.powi(2)
+ (-12.0 * b - 48.0 * c) * x
+ (8.0 * b + 24.0 * c))
* (1.0 / 6.0)
} else {
0.0
} }
} }
fn mitchell_1d(&self, x: Float) -> Float { fn mitchell_1d(&self, x: Float) -> Float {
Self::mitchell_1d_eval(self.b, self.c, x) mitchell_1d_eval(self.b, self.c, x)
} }
} }
@ -50,7 +62,7 @@ impl FilterTrait for MitchellFilter {
self.radius.x() * self.radius.y() / 4.0 self.radius.x() * self.radius.y() / 4.0
} }
fn sample(&self, u: Point2f) -> DeviceFilterSample { fn sample(&self, u: Point2f) -> FilterSample {
self.sampler.sample(u) self.sampler.sample(u)
} }
} }

View file

@ -1,5 +1,5 @@
use crate::Float; use crate::Float;
use crate::core::filter::{DeviceFilterSample, FilterTrait}; use crate::core::filter::{FilterSample, FilterTrait};
use crate::core::geometry::{Point2f, Vector2f}; use crate::core::geometry::{Point2f, Vector2f};
use crate::utils::math::sample_tent; use crate::utils::math::sample_tent;
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
@ -29,11 +29,11 @@ impl FilterTrait for TriangleFilter {
self.radius.x().powi(2) * self.radius.y().powi(2) self.radius.x().powi(2) * self.radius.y().powi(2)
} }
fn sample(&self, u: Point2f) -> DeviceFilterSample { fn sample(&self, u: Point2f) -> FilterSample {
let p = Point2f::new( let p = Point2f::new(
sample_tent(u[0], self.radius.x()), sample_tent(u[0], self.radius.x()),
sample_tent(u[1], self.radius.y()), sample_tent(u[1], self.radius.y()),
); );
DeviceFilterSample { p, weight: 1.0 } FilterSample { p, weight: 1.0 }
} }
} }

View file

@ -1,4 +1,5 @@
#![allow(unused_imports, dead_code)] #![allow(unused_imports, dead_code)]
#![feature(allocator_api)]
#![feature(associated_type_defaults)] #![feature(associated_type_defaults)]
#![no_std] #![no_std]
extern crate alloc; extern crate alloc;
@ -17,5 +18,5 @@ pub mod textures;
pub mod utils; pub mod utils;
pub use core::pbrt::*; pub use core::pbrt::*;
pub use utils::PBRTOptions; pub use utils::alloc::{gbox, gvec, gvec_from_slice, gvec_with_capacity, GVec, GBox};
pub use utils::ptr::Ptr; pub use utils::{Array2D, PBRTOptions, Ptr, Transform};

View file

@ -1,6 +1,6 @@
use crate::core::color::{RGB, XYZ}; use crate::core::color::{RGB, XYZ};
use crate::core::geometry::*; use crate::core::geometry::*;
use crate::core::image::{DeviceImage, ImageAccess}; use crate::core::image::Image;
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction, Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
}; };
@ -27,7 +27,7 @@ pub struct DiffuseAreaLight {
pub alpha: Ptr<GPUFloatTexture>, pub alpha: Ptr<GPUFloatTexture>,
pub colorspace: Ptr<RGBColorSpace>, pub colorspace: Ptr<RGBColorSpace>,
pub lemit: Ptr<DenselySampledSpectrum>, pub lemit: Ptr<DenselySampledSpectrum>,
pub image: Ptr<DeviceImage>, pub image: Ptr<Image>,
pub area: Float, pub area: Float,
pub two_sided: bool, pub two_sided: bool,
pub scale: Float, pub scale: Float,
@ -171,8 +171,8 @@ impl LightTrait for DiffuseAreaLight {
fn bounds(&self) -> Option<LightBounds> { fn bounds(&self) -> Option<LightBounds> {
let mut phi = 0.; let mut phi = 0.;
if !self.image.is_null() { if !self.image.is_null() {
for y in 0..self.image.base.resolution.y() { for y in 0..self.image.resolution().y() {
for x in 0..self.image.base.resolution.x() { for x in 0..self.image.resolution().x() {
for c in 0..3 { for c in 0..3 {
phi += self.image.get_channel(Point2i::new(x, y), c); phi += self.image.get_channel(Point2i::new(x, y), c);
} }

View file

@ -1,5 +1,5 @@
use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f}; use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f};
use crate::core::image::{DeviceImage, ImageAccess}; use crate::core::image::Image;
use crate::core::light::{ use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
}; };
@ -7,7 +7,7 @@ use crate::core::medium::MediumInterface;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::math::equal_area_sphere_to_square; use crate::utils::math::equal_area_sphere_to_square;
use crate::utils::sampling::DevicePiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::{Ptr, Transform}; use crate::utils::{Ptr, Transform};
use crate::{Float, PI}; use crate::{Float, PI};
@ -16,8 +16,8 @@ pub struct GoniometricLight {
pub base: LightBase, pub base: LightBase,
pub iemit: Ptr<DenselySampledSpectrum>, pub iemit: Ptr<DenselySampledSpectrum>,
pub scale: Float, pub scale: Float,
pub image: Ptr<DeviceImage>, pub image: Ptr<Image>,
pub distrib: Ptr<DevicePiecewiseConstant2D>, pub distrib: Ptr<PiecewiseConstant2D>,
} }
impl GoniometricLight { impl GoniometricLight {

View file

@ -3,7 +3,7 @@ use crate::core::geometry::{
Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector3f, Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector3f,
}; };
use crate::core::geometry::{Frame, VectorLike}; use crate::core::geometry::{Frame, VectorLike};
use crate::core::image::{DeviceImage, ImageAccess, PixelFormat, WrapMode}; use crate::core::image::{Image, PixelFormat, WrapMode};
use crate::core::interaction::InteractionBase; use crate::core::interaction::InteractionBase;
use crate::core::interaction::{Interaction, SimpleInteraction}; use crate::core::interaction::{Interaction, SimpleInteraction};
use crate::core::light::{ use crate::core::light::{
@ -15,8 +15,8 @@ use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths
use crate::spectra::{RGBColorSpace, RGBIlluminantSpectrum}; use crate::spectra::{RGBColorSpace, RGBIlluminantSpectrum};
use crate::utils::math::{clamp, equal_area_sphere_to_square, equal_area_square_to_sphere, square}; use crate::utils::math::{clamp, equal_area_sphere_to_square, equal_area_square_to_sphere, square};
use crate::utils::sampling::{ use crate::utils::sampling::{
AliasTable, DevicePiecewiseConstant2D, DeviceWindowedPiecewiseConstant2D, sample_uniform_sphere, uniform_sphere_pdf, AliasTable, PiecewiseConstant2D,
sample_uniform_sphere, uniform_sphere_pdf, WindowedPiecewiseConstant2D,
}; };
use crate::utils::{Ptr, Transform}; use crate::utils::{Ptr, Transform};
use crate::{Float, PI}; use crate::{Float, PI};
@ -35,6 +35,27 @@ pub struct UniformInfiniteLight {
unsafe impl Send for UniformInfiniteLight {} unsafe impl Send for UniformInfiniteLight {}
unsafe impl Sync for UniformInfiniteLight {} unsafe impl Sync for UniformInfiniteLight {}
impl UniformInfiniteLight {
pub fn new(
render_from_light: Transform,
scale: Float,
lemit: Ptr<DenselySampledSpectrum>,
) -> Self {
let base = LightBase::new(
LightType::Infinite,
render_from_light,
MediumInterface::default(),
);
Self {
base,
lemit,
scale,
scene_center: Point3f::default(),
scene_radius: 0.0,
}
}
}
impl LightTrait for UniformInfiniteLight { impl LightTrait for UniformInfiniteLight {
fn base(&self) -> &LightBase { fn base(&self) -> &LightBase {
&self.base &self.base
@ -114,10 +135,10 @@ impl LightTrait for UniformInfiniteLight {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct ImageInfiniteLight { pub struct ImageInfiniteLight {
pub base: LightBase, pub base: LightBase,
pub image: Ptr<DeviceImage>, pub image: Ptr<Image>,
pub image_color_space: Ptr<RGBColorSpace>, pub image_color_space: Ptr<RGBColorSpace>,
pub distrib: Ptr<DevicePiecewiseConstant2D>, pub distrib: Ptr<PiecewiseConstant2D>,
pub compensated_distrib: Ptr<DevicePiecewiseConstant2D>, pub compensated_distrib: Ptr<PiecewiseConstant2D>,
pub scale: Float, pub scale: Float,
pub scene_radius: Float, pub scene_radius: Float,
pub scene_center: Point3f, pub scene_center: Point3f,
@ -127,6 +148,31 @@ unsafe impl Send for ImageInfiniteLight {}
unsafe impl Sync for ImageInfiniteLight {} unsafe impl Sync for ImageInfiniteLight {}
impl ImageInfiniteLight { impl ImageInfiniteLight {
pub fn new(
render_from_light: Transform,
scale: Float,
image: Ptr<Image>,
image_color_space: Ptr<RGBColorSpace>,
distrib: Ptr<PiecewiseConstant2D>,
compensated_distrib: Ptr<PiecewiseConstant2D>,
) -> Self {
let base = LightBase::new(
LightType::Infinite,
render_from_light,
MediumInterface::default(),
);
Self {
base,
image,
image_color_space,
scale,
distrib,
compensated_distrib,
scene_center: Point3f::default(),
scene_radius: 0.0,
}
}
fn image_le(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum { fn image_le(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {
@ -250,17 +296,44 @@ impl LightTrait for ImageInfiniteLight {
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct PortalInfiniteLight { pub struct PortalInfiniteLight {
pub base: LightBase, pub base: LightBase,
pub image: Ptr<DeviceImage>, pub image: Ptr<Image>,
pub image_color_space: Ptr<RGBColorSpace>, pub image_color_space: Ptr<RGBColorSpace>,
pub scale: Float, pub scale: Float,
pub portal: [Point3f; 4], pub portal: [Point3f; 4],
pub portal_frame: Frame, pub portal_frame: Frame,
pub distribution: DeviceWindowedPiecewiseConstant2D, pub distribution: Ptr<WindowedPiecewiseConstant2D>,
pub scene_center: Point3f, pub scene_center: Point3f,
pub scene_radius: Float, pub scene_radius: Float,
} }
impl PortalInfiniteLight { impl PortalInfiniteLight {
pub fn new(
render_from_light: Transform,
scale: Float,
image: Ptr<Image>,
image_color_space: Ptr<RGBColorSpace>,
portal: [Point3f; 4],
portal_frame: Frame,
distribution: Ptr<WindowedPiecewiseConstant2D>,
) -> Self {
let base = LightBase::new(
LightType::Infinite,
render_from_light,
MediumInterface::default(),
);
Self {
base,
image,
image_color_space,
scale,
portal,
portal_frame,
distribution,
scene_center: Point3f::default(),
scene_radius: 0.0,
}
}
pub fn image_lookup(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum { pub fn image_lookup(&self, uv: Point2f, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut rgb = RGB::default(); let mut rgb = RGB::default();
for c in 0..3 { for c in 0..3 {

View file

@ -7,8 +7,7 @@ use crate::core::light::{
}; };
use crate::core::spectrum::SpectrumTrait; use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::{Float, PI, Ptr, Transform};
use crate::{Float, PI};
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]

View file

@ -3,7 +3,7 @@ use crate::core::color::RGB;
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta, Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta,
}; };
use crate::core::image::{DeviceImage, ImageAccess}; use crate::core::image::Image;
use crate::core::light::{ use crate::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
}; };
@ -13,7 +13,7 @@ use crate::spectra::{SampledSpectrum, SampledWavelengths};
use crate::utils::math::{radians, square}; use crate::utils::math::{radians, square};
use crate::{ use crate::{
spectra::{RGBColorSpace, RGBIlluminantSpectrum}, spectra::{RGBColorSpace, RGBIlluminantSpectrum},
utils::{Ptr, Transform, sampling::DevicePiecewiseConstant2D}, utils::{Ptr, Transform, sampling::PiecewiseConstant2D},
}; };
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
@ -27,8 +27,8 @@ pub struct ProjectionLight {
pub screen_from_light: Transform, pub screen_from_light: Transform,
pub light_from_screen: Transform, pub light_from_screen: Transform,
pub a: Float, pub a: Float,
pub image: Ptr<DeviceImage>, pub image: Ptr<Image>,
pub distrib: Ptr<DevicePiecewiseConstant2D>, pub distrib: Ptr<PiecewiseConstant2D>,
pub image_color_space: Ptr<RGBColorSpace>, pub image_color_space: Ptr<RGBColorSpace>,
} }

View file

@ -198,12 +198,12 @@ pub enum LightSampler {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct UniformLightSampler { pub struct UniformLightSampler {
lights: *const Light, lights: Ptr<Light>,
lights_len: u32, lights_len: u32,
} }
impl UniformLightSampler { impl UniformLightSampler {
pub fn new(lights: *const Light, lights_len: u32) -> Self { pub fn new(lights: Ptr<Light>, lights_len: u32) -> Self {
Self { lights, lights_len } Self { lights, lights_len }
} }
@ -251,7 +251,7 @@ pub struct Alias {
pub struct PowerLightSampler { pub struct PowerLightSampler {
pub lights: Ptr<Light>, pub lights: Ptr<Light>,
pub lights_len: u32, pub lights_len: u32,
pub alias_table: AliasTable, pub alias_table: Ptr<AliasTable>,
} }
unsafe impl Send for PowerLightSampler {} unsafe impl Send for PowerLightSampler {}

View file

@ -5,8 +5,7 @@ use crate::core::interaction::{Interaction, InteractionBase, InteractionTrait, S
use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait}; use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait};
use crate::core::spectrum::SpectrumTrait; use crate::core::spectrum::SpectrumTrait;
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
use crate::utils::Ptr; use crate::{Float, PI, Ptr, Transform};
use crate::{Float, PI};
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]

View file

@ -4,7 +4,7 @@ use crate::bxdfs::{
use crate::core::bsdf::BSDF; use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF; use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF; use crate::core::bxdf::BxDF;
use crate::core::image::DeviceImage; use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
@ -16,7 +16,7 @@ use crate::utils::math::clamp;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct CoatedDiffuseMaterial { pub struct CoatedDiffuseMaterial {
pub normal_map: Ptr<DeviceImage>, pub normal_map: Ptr<Image>,
pub displacement: Ptr<GPUFloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<GPUSpectrumTexture>, pub reflectance: Ptr<GPUSpectrumTexture>,
pub albedo: Ptr<GPUSpectrumTexture>, pub albedo: Ptr<GPUSpectrumTexture>,
@ -42,7 +42,7 @@ impl CoatedDiffuseMaterial {
g: Ptr<GPUFloatTexture>, g: Ptr<GPUFloatTexture>,
eta: Ptr<Spectrum>, eta: Ptr<Spectrum>,
displacement: Ptr<GPUFloatTexture>, displacement: Ptr<GPUFloatTexture>,
normal_map: Ptr<DeviceImage>, normal_map: Ptr<Image>,
remap_roughness: bool, remap_roughness: bool,
max_depth: u32, max_depth: u32,
n_samples: u32, n_samples: u32,
@ -137,7 +137,7 @@ impl MaterialTrait for CoatedDiffuseMaterial {
) )
} }
fn get_normal_map(&self) -> Option<&DeviceImage> { fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map) Some(&*self.normal_map)
} }
@ -153,7 +153,7 @@ impl MaterialTrait for CoatedDiffuseMaterial {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct CoatedConductorMaterial { pub struct CoatedConductorMaterial {
normal_map: Ptr<DeviceImage>, normal_map: Ptr<Image>,
displacement: Ptr<GPUFloatTexture>, displacement: Ptr<GPUFloatTexture>,
interface_uroughness: Ptr<GPUFloatTexture>, interface_uroughness: Ptr<GPUFloatTexture>,
interface_vroughness: Ptr<GPUFloatTexture>, interface_vroughness: Ptr<GPUFloatTexture>,
@ -175,7 +175,7 @@ pub struct CoatedConductorMaterial {
impl CoatedConductorMaterial { impl CoatedConductorMaterial {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
normal_map: Ptr<DeviceImage>, normal_map: Ptr<Image>,
displacement: Ptr<GPUFloatTexture>, displacement: Ptr<GPUFloatTexture>,
interface_uroughness: Ptr<GPUFloatTexture>, interface_uroughness: Ptr<GPUFloatTexture>,
interface_vroughness: Ptr<GPUFloatTexture>, interface_vroughness: Ptr<GPUFloatTexture>,
@ -333,7 +333,7 @@ impl MaterialTrait for CoatedConductorMaterial {
tex_eval.can_evaluate(&float_textures, &spectrum_textures) tex_eval.can_evaluate(&float_textures, &spectrum_textures)
} }
fn get_normal_map(&self) -> Option<&DeviceImage> { fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map) Some(&*self.normal_map)
} }

View file

@ -6,7 +6,7 @@ use crate::bxdfs::{
use crate::core::bsdf::BSDF; use crate::core::bsdf::BSDF;
use crate::core::bssrdf::{BSSRDF, BSSRDFTable}; use crate::core::bssrdf::{BSSRDF, BSSRDFTable};
use crate::core::bxdf::BxDF; use crate::core::bxdf::BxDF;
use crate::core::image::DeviceImage; use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
@ -77,7 +77,7 @@ impl MaterialTrait for HairMaterial {
todo!() todo!()
} }
fn get_normal_map(&self) -> Option<&DeviceImage> { fn get_normal_map(&self) -> Option<&Image> {
todo!() todo!()
} }
@ -94,7 +94,7 @@ impl MaterialTrait for HairMaterial {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct MeasuredMaterial { pub struct MeasuredMaterial {
pub displacement: Ptr<GPUFloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub normal_map: Ptr<DeviceImage>, pub normal_map: Ptr<Image>,
pub brdf: Ptr<MeasuredBxDFData>, pub brdf: Ptr<MeasuredBxDFData>,
} }
@ -122,7 +122,7 @@ impl MaterialTrait for MeasuredMaterial {
true true
} }
fn get_normal_map(&self) -> Option<&DeviceImage> { fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map) Some(&*self.normal_map)
} }
@ -138,7 +138,7 @@ impl MaterialTrait for MeasuredMaterial {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct SubsurfaceMaterial { pub struct SubsurfaceMaterial {
pub normal_map: Ptr<DeviceImage>, pub normal_map: Ptr<Image>,
pub displacement: Ptr<GPUFloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub sigma_a: Ptr<GPUSpectrumTexture>, pub sigma_a: Ptr<GPUSpectrumTexture>,
pub sigma_s: Ptr<GPUSpectrumMixTexture>, pub sigma_s: Ptr<GPUSpectrumMixTexture>,
@ -174,7 +174,7 @@ impl MaterialTrait for SubsurfaceMaterial {
todo!() todo!()
} }
fn get_normal_map(&self) -> Option<&DeviceImage> { fn get_normal_map(&self) -> Option<&Image> {
todo!() todo!()
} }

View file

@ -4,7 +4,7 @@ use crate::bxdfs::{
use crate::core::bsdf::BSDF; use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF; use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF; use crate::core::bxdf::BxDF;
use crate::core::image::DeviceImage; use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
@ -23,7 +23,7 @@ pub struct ConductorMaterial {
pub u_roughness: Ptr<GPUFloatTexture>, pub u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<GPUFloatTexture>, pub v_roughness: Ptr<GPUFloatTexture>,
pub remap_roughness: bool, pub remap_roughness: bool,
pub normal_map: Ptr<DeviceImage>, pub normal_map: Ptr<Image>,
} }
impl MaterialTrait for ConductorMaterial { impl MaterialTrait for ConductorMaterial {
@ -50,7 +50,7 @@ impl MaterialTrait for ConductorMaterial {
) )
} }
fn get_normal_map(&self) -> Option<&DeviceImage> { fn get_normal_map(&self) -> Option<&Image> {
todo!() todo!()
} }

View file

@ -4,7 +4,7 @@ use crate::bxdfs::{
use crate::core::bsdf::BSDF; use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF; use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF; use crate::core::bxdf::BxDF;
use crate::core::image::DeviceImage; use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
@ -16,7 +16,7 @@ use crate::utils::math::clamp;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct DielectricMaterial { pub struct DielectricMaterial {
pub normal_map: Ptr<DeviceImage>, pub normal_map: Ptr<Image>,
pub displacement: Ptr<GPUFloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub u_roughness: Ptr<GPUFloatTexture>, pub u_roughness: Ptr<GPUFloatTexture>,
pub v_roughness: Ptr<GPUFloatTexture>, pub v_roughness: Ptr<GPUFloatTexture>,
@ -67,7 +67,7 @@ impl MaterialTrait for DielectricMaterial {
tex_eval.can_evaluate(&[self.u_roughness, self.v_roughness], &[]) tex_eval.can_evaluate(&[self.u_roughness, self.v_roughness], &[])
} }
fn get_normal_map(&self) -> Option<&DeviceImage> { fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map) Some(&*self.normal_map)
} }
@ -84,7 +84,7 @@ impl MaterialTrait for DielectricMaterial {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct ThinDielectricMaterial { pub struct ThinDielectricMaterial {
pub displacement: Ptr<GPUFloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub normal_map: Ptr<DeviceImage>, pub normal_map: Ptr<Image>,
pub eta: Ptr<Spectrum>, pub eta: Ptr<Spectrum>,
} }
@ -110,7 +110,7 @@ impl MaterialTrait for ThinDielectricMaterial {
true true
} }
fn get_normal_map(&self) -> Option<&DeviceImage> { fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map) Some(&*self.normal_map)
} }

View file

@ -5,7 +5,7 @@ use crate::bxdfs::{
use crate::core::bsdf::BSDF; use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF; use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF; use crate::core::bxdf::BxDF;
use crate::core::image::DeviceImage; use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
@ -17,7 +17,7 @@ use crate::utils::math::clamp;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct DiffuseMaterial { pub struct DiffuseMaterial {
pub normal_map: Ptr<DeviceImage>, pub normal_map: Ptr<Image>,
pub displacement: Ptr<GPUFloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<GPUSpectrumTexture>, pub reflectance: Ptr<GPUSpectrumTexture>,
} }
@ -47,7 +47,7 @@ impl MaterialTrait for DiffuseMaterial {
tex_eval.can_evaluate(&[], &[self.reflectance]) tex_eval.can_evaluate(&[], &[self.reflectance])
} }
fn get_normal_map(&self) -> Option<&DeviceImage> { fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.normal_map) Some(&*self.normal_map)
} }
@ -63,7 +63,7 @@ impl MaterialTrait for DiffuseMaterial {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct DiffuseTransmissionMaterial { pub struct DiffuseTransmissionMaterial {
pub image: Ptr<DeviceImage>, pub image: Ptr<Image>,
pub displacement: Ptr<GPUFloatTexture>, pub displacement: Ptr<GPUFloatTexture>,
pub reflectance: Ptr<GPUFloatTexture>, pub reflectance: Ptr<GPUFloatTexture>,
pub transmittance: Ptr<GPUFloatTexture>, pub transmittance: Ptr<GPUFloatTexture>,
@ -92,7 +92,7 @@ impl MaterialTrait for DiffuseTransmissionMaterial {
tex_eval.can_evaluate(&[self.reflectance, self.transmittance], &[]) tex_eval.can_evaluate(&[self.reflectance, self.transmittance], &[])
} }
fn get_normal_map(&self) -> Option<&DeviceImage> { fn get_normal_map(&self) -> Option<&Image> {
Some(&*self.image) Some(&*self.image)
} }

View file

@ -4,7 +4,7 @@ use crate::bxdfs::{
use crate::core::bsdf::BSDF; use crate::core::bsdf::BSDF;
use crate::core::bssrdf::BSSRDF; use crate::core::bssrdf::BSSRDF;
use crate::core::bxdf::BxDF; use crate::core::bxdf::BxDF;
use crate::core::image::DeviceImage; use crate::core::image::Image;
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait}; use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::scattering::TrowbridgeReitzDistribution;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
@ -69,7 +69,7 @@ impl MaterialTrait for MixMaterial {
tex_eval.can_evaluate(&[self.amount], &[]) tex_eval.can_evaluate(&[self.amount], &[])
} }
fn get_normal_map(&self) -> Option<&DeviceImage> { fn get_normal_map(&self) -> Option<&Image> {
None None
} }

View file

@ -1,17 +1,16 @@
use crate::core::geometry::{ use crate::core::geometry::{
Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Tuple, Vector3f, spherical_quad_area, Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi,
VectorLike, spherical_quad_area, Ray, Tuple, Vector3f, VectorLike,
}; };
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction}; use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
use crate::core::pbrt::{Float, gamma}; use crate::core::pbrt::{gamma, Float};
use crate::core::shape::{Shape, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait}; use crate::core::shape::{Shape, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
use crate::utils::Ptr; use crate::shapes::mesh::BilinearPatchMesh;
use crate::utils::Transform; use crate::utils::math::{clamp, difference_of_products, lerp, quadratic, SquareMatrix};
use crate::utils::math::{SquareMatrix, clamp, difference_of_products, lerp, quadratic};
use crate::utils::mesh::DeviceBilinearPatchMesh;
use crate::utils::sampling::{ use crate::utils::sampling::{
bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle, bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle,
}; };
use crate::{GVec, Ptr, Transform};
use core::ops::Add; use core::ops::Add;
#[repr(C)] #[repr(C)]
@ -47,7 +46,7 @@ impl BilinearIntersection {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct BilinearPatchShape { pub struct BilinearPatchShape {
pub mesh: Ptr<DeviceBilinearPatchMesh>, pub mesh: Ptr<BilinearPatchMesh>,
pub blp_index: i32, pub blp_index: i32,
pub area: Float, pub area: Float,
pub rectangle: bool, pub rectangle: bool,
@ -55,14 +54,14 @@ pub struct BilinearPatchShape {
impl BilinearPatchShape { impl BilinearPatchShape {
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 1e-4; pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 1e-4;
fn mesh(&self) -> Ptr<DeviceBilinearPatchMesh> { fn mesh(&self) -> Ptr<BilinearPatchMesh> {
self.mesh self.mesh
} }
#[inline(always)] #[inline(always)]
fn get_vertex_indices(&self) -> [usize; 4] { fn get_vertex_indices(&self) -> [usize; 4] {
unsafe { unsafe {
let base_ptr = self.mesh.vertex_indices.add((self.blp_index as usize) * 4); let base_ptr = self.mesh.vertex_indices.as_ptr().add((self.blp_index as usize) * 4);
[ [
*base_ptr.add(0) as usize, *base_ptr.add(0) as usize,
*base_ptr.add(1) as usize, *base_ptr.add(1) as usize,
@ -74,51 +73,33 @@ impl BilinearPatchShape {
#[inline(always)] #[inline(always)]
fn get_points(&self) -> [Point3f; 4] { fn get_points(&self) -> [Point3f; 4] {
let mesh = self.mesh();
let [v0, v1, v2, v3] = self.get_vertex_indices(); let [v0, v1, v2, v3] = self.get_vertex_indices();
unsafe { [mesh.p[v0], mesh.p[v1], mesh.p[v2], mesh.p[v3]]
[
*self.mesh.p.add(v0),
*self.mesh.p.add(v1),
*self.mesh.p.add(v2),
*self.mesh.p.add(v3),
]
}
} }
#[inline(always)] #[inline(always)]
fn get_uvs(&self) -> Option<[Point2f; 4]> { fn get_uvs(&self) -> Option<[Point2f; 4]> {
if self.mesh.uv.is_null() { let mesh = self.mesh();
if mesh.uv.is_empty() {
return None; return None;
} }
let [v0, v1, v2, v3] = self.get_vertex_indices(); let [v0, v1, v2, v3] = self.get_vertex_indices();
unsafe { Some([mesh.uv[v0], mesh.uv[v1], mesh.uv[v2], mesh.uv[v3]])
Some([
*self.mesh.uv.add(v0),
*self.mesh.uv.add(v1),
*self.mesh.uv.add(v2),
*self.mesh.uv.add(v3),
])
}
} }
#[inline(always)] #[inline(always)]
fn get_shading_normals(&self) -> Option<[Normal3f; 4]> { fn get_shading_normals(&self) -> Option<[Normal3f; 4]> {
if self.mesh.n.is_null() { let mesh = self.mesh();
if mesh.n.is_empty() {
return None; return None;
} }
let [v0, v1, v2, v3] = self.get_vertex_indices(); let [v0, v1, v2, v3] = self.get_vertex_indices();
unsafe { Some([mesh.n[v0], mesh.n[v1], mesh.n[v2], mesh.n[v3]])
Some([
*self.mesh.n.add(v0),
*self.mesh.n.add(v1),
*self.mesh.n.add(v2),
*self.mesh.n.add(v3),
])
}
} }
#[cfg(not(target_os = "cuda"))] #[cfg(not(target_os = "cuda"))]
pub fn new(mesh: Ptr<DeviceBilinearPatchMesh>, blp_index: i32) -> Self { pub fn new(mesh: Ptr<BilinearPatchMesh>, blp_index: i32) -> Self {
let mut bp = BilinearPatchShape { let mut bp = BilinearPatchShape {
mesh, mesh,
blp_index, blp_index,
@ -456,7 +437,11 @@ impl BilinearPatchShape {
ss.pdf *= dist_sq / abs_dot; ss.pdf *= dist_sq / abs_dot;
if ss.pdf.is_infinite() { None } else { Some(ss) } if ss.pdf.is_infinite() {
None
} else {
Some(ss)
}
} }
fn sample_parametric_coords(&self, corners: &[Point3f; 4], u: Point2f) -> (Point2f, Float) { fn sample_parametric_coords(&self, corners: &[Point3f; 4], u: Point2f) -> (Point2f, Float) {
@ -723,7 +708,11 @@ impl ShapeTrait for BilinearPatchShape {
let (_, dpdu, dpdv) = self.calculate_base_derivatives(&corners, uv); let (_, dpdu, dpdv) = self.calculate_base_derivatives(&corners, uv);
let cross = dpdu.cross(dpdv).norm(); let cross = dpdu.cross(dpdv).norm();
if cross == 0. { 0. } else { param_pdf / cross } if cross == 0. {
0.
} else {
param_pdf / cross
}
} }
#[inline] #[inline]
@ -754,7 +743,11 @@ impl ShapeTrait for BilinearPatchShape {
return 0.; return 0.;
} }
let pdf = isect_pdf * distsq / absdot; let pdf = isect_pdf * distsq / absdot;
if pdf.is_infinite() { 0. } else { pdf } if pdf.is_infinite() {
0.
} else {
pdf
}
} else { } else {
let mut pdf = 1. / spherical_quad_area(v00, v10, v01, v11); let mut pdf = 1. / spherical_quad_area(v00, v10, v01, v11);
if ctx.ns != Normal3f::zero() { if ctx.ns != Normal3f::zero() {

159
shared/src/shapes/mesh.rs Normal file
View file

@ -0,0 +1,159 @@
use crate::core::geometry::{Normal3f, Point2f, Point3f, Vector3f};
use crate::utils::sampling::PiecewiseConstant2D;
use crate::{gvec_from_slice, gvec_with_capacity, Float, GVec, Ptr, Transform};
#[repr(C)]
#[derive(Debug, Clone)]
pub struct TriangleMesh {
pub p: GVec<Point3f>,
pub n: GVec<Normal3f>,
pub s: GVec<Vector3f>,
pub uv: GVec<Point2f>,
pub vertex_indices: GVec<i32>,
pub face_indices: GVec<i32>,
pub n_triangles: u32,
pub n_vertices: u32,
pub reverse_orientation: bool,
pub transform_swaps_handedness: bool,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct BilinearPatchMesh {
pub p: GVec<Point3f>,
pub n: GVec<Normal3f>,
pub uv: GVec<Point2f>,
pub vertex_indices: GVec<i32>,
pub n_patches: u32,
pub n_vertices: u32,
pub reverse_orientation: bool,
pub transform_swaps_handedness: bool,
pub image_distribution: Ptr<PiecewiseConstant2D>,
}
unsafe impl Send for TriangleMesh {}
unsafe impl Sync for TriangleMesh {}
unsafe impl Send for BilinearPatchMesh {}
unsafe impl Sync for BilinearPatchMesh {}
impl TriangleMesh {
pub fn new(
render_from_object: &Transform,
reverse_orientation: bool,
vertex_indices: &[i32],
p: &[Point3f],
n: &[Normal3f],
s: &[Vector3f],
uv: &[Point2f],
face_indices: &[i32],
) -> Self {
let n_triangles = (vertex_indices.len() / 3) as u32;
let n_vertices = p.len() as u32;
let mut p_gvec = gvec_with_capacity(p.len());
for pt in p {
p_gvec.push(render_from_object.apply_to_point(*pt));
}
let mut n_gvec = gvec_with_capacity(n.len());
if !n.is_empty() {
assert_eq!(n_vertices as usize, n.len(), "Normal count mismatch");
for nn in n {
let mut transformed = render_from_object.apply_to_normal(*nn);
if reverse_orientation {
transformed = -transformed;
}
n_gvec.push(transformed);
}
}
let mut s_gvec = gvec_with_capacity(s.len());
if !s.is_empty() {
assert_eq!(n_vertices as usize, s.len(), "Tangent count mismatch");
for ss in s {
s_gvec.push(render_from_object.apply_to_vector(*ss));
}
}
assert!(
uv.is_empty() || uv.len() == n_vertices as usize,
"UV count mismatch"
);
assert!(
face_indices.is_empty() || face_indices.len() == n_triangles as usize,
"Face index count mismatch"
);
Self {
vertex_indices: gvec_from_slice(vertex_indices),
p: p_gvec,
n: n_gvec,
s: s_gvec,
uv: gvec_from_slice(uv),
face_indices: gvec_from_slice(face_indices),
n_triangles,
n_vertices,
reverse_orientation,
transform_swaps_handedness: render_from_object.swaps_handedness(),
}
}
pub fn positions(&self) -> &[Point3f] {
&self.p
}
pub fn indices(&self) -> &[i32] {
&self.vertex_indices
}
pub fn normals(&self) -> &[Normal3f] {
&self.n
}
pub fn uvs(&self) -> &[Point2f] {
&self.uv
}
}
impl BilinearPatchMesh {
pub fn new(
render_from_object: &Transform,
reverse_orientation: bool,
vertex_indices: &[i32],
p: &[Point3f],
n: &[Normal3f],
uv: &[Point2f],
image_distribution: Option<&PiecewiseConstant2D>,
) -> Self {
let n_patches = (vertex_indices.len() / 4) as u32;
let n_vertices = p.len() as u32;
let mut p_gvec = gvec_with_capacity(p.len());
for pt in p {
p_gvec.push(render_from_object.apply_to_point(*pt));
}
let mut n_gvec = gvec_with_capacity(n.len());
if !n.is_empty() {
assert_eq!(n_vertices as usize, n.len());
for nn in n {
let mut transformed = render_from_object.apply_to_normal(*nn);
if reverse_orientation {
transformed = -transformed;
}
n_gvec.push(transformed);
}
}
assert!(uv.is_empty() || uv.len() == n_vertices as usize);
Self {
vertex_indices: gvec_from_slice(vertex_indices),
p: p_gvec,
n: n_gvec,
uv: gvec_from_slice(uv),
image_distribution: Ptr::from(image_distribution),
n_patches,
n_vertices,
reverse_orientation,
transform_swaps_handedness: render_from_object.swaps_handedness(),
}
}
}

View file

@ -4,6 +4,7 @@ pub mod cylinder;
pub mod disk; pub mod disk;
pub mod sphere; pub mod sphere;
pub mod triangle; pub mod triangle;
pub mod mesh;
pub use bilinear::*; pub use bilinear::*;
pub use curves::*; pub use curves::*;
@ -11,3 +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::*;

View file

@ -1,21 +1,19 @@
use crate::Float; use crate::core::geometry::{spherical_triangle_area, SqrtExt, Tuple, VectorLike};
use crate::core::geometry::{ use crate::core::geometry::{
Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3, Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3,
Vector3f, Vector3f,
}; };
use crate::core::geometry::{SqrtExt, Tuple, VectorLike, spherical_triangle_area};
use crate::core::interaction::{ use crate::core::interaction::{
Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction, Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction,
}; };
use crate::core::pbrt::gamma;
use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait}; use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
use crate::utils::Ptr; use crate::shapes::mesh::TriangleMesh;
use crate::utils::math::{difference_of_products, square}; use crate::utils::math::{difference_of_products, square};
use crate::utils::mesh::DeviceTriangleMesh;
use crate::utils::sampling::{ use crate::utils::sampling::{
bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle, bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle,
sample_uniform_triangle, sample_uniform_triangle,
}; };
use crate::{gamma, Float, GVec, Ptr};
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -35,7 +33,7 @@ impl TriangleIntersection {
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct TriangleShape { pub struct TriangleShape {
pub mesh: Ptr<DeviceTriangleMesh>, pub mesh: Ptr<TriangleMesh>,
pub tri_index: i32, pub tri_index: i32,
} }
@ -43,80 +41,58 @@ impl TriangleShape {
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4; pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4;
pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22; pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22;
#[inline(always)] fn mesh(&self) -> &TriangleMesh {
&*self.mesh
}
fn get_vertex_indices(&self) -> [usize; 3] { fn get_vertex_indices(&self) -> [usize; 3] {
unsafe { let mesh = self.mesh();
let base_ptr = self.mesh.vertex_indices.add((self.tri_index as usize) * 3); let base = (self.tri_index as usize) * 3;
[ [
*base_ptr.add(0) as usize, mesh.vertex_indices[base] as usize,
*base_ptr.add(1) as usize, mesh.vertex_indices[base + 1] as usize,
*base_ptr.add(2) as usize, mesh.vertex_indices[base + 2] as usize,
] ]
}
} }
#[inline(always)]
fn get_points(&self) -> [Point3f; 3] { fn get_points(&self) -> [Point3f; 3] {
let mesh = self.mesh();
let [v0, v1, v2] = self.get_vertex_indices(); let [v0, v1, v2] = self.get_vertex_indices();
unsafe { [mesh.p[v0], mesh.p[v1], mesh.p[v2]]
[
*self.mesh.p.add(v0),
*self.mesh.p.add(v1),
*self.mesh.p.add(v2),
]
}
} }
#[inline(always)]
fn get_uvs(&self) -> Option<[Point2f; 3]> {
if self.mesh.uv.is_null() {
return None;
}
let [v0, v1, v2] = self.get_vertex_indices();
unsafe {
Some([
*self.mesh.uv.add(v0),
*self.mesh.uv.add(v1),
*self.mesh.uv.add(v2),
])
}
}
#[inline(always)]
fn get_tangents(&self) -> Option<[Vector3f; 3]> {
if self.mesh.s.is_null() {
return None;
}
let [v0, v1, v2] = self.get_vertex_indices();
unsafe {
Some([
*self.mesh.s.add(v0),
*self.mesh.s.add(v1),
*self.mesh.s.add(v2),
])
}
}
#[inline(always)]
fn get_shading_normals(&self) -> Option<[Normal3f; 3]> { fn get_shading_normals(&self) -> Option<[Normal3f; 3]> {
if self.mesh.n.is_null() { let mesh = self.mesh();
if mesh.n.is_empty() {
return None; return None;
} }
let [v0, v1, v2] = self.get_vertex_indices(); let [v0, v1, v2] = self.get_vertex_indices();
unsafe { Some([mesh.n[v0], mesh.n[v1], mesh.n[v2]])
Some([
*self.mesh.n.add(v0),
*self.mesh.n.add(v1),
*self.mesh.n.add(v2),
])
}
} }
pub fn new(mesh: Ptr<DeviceTriangleMesh>, tri_index: i32) -> Self { fn get_tangents(&self) -> Option<[Vector3f; 3]> {
let mesh = self.mesh();
if mesh.s.is_empty() {
return None;
}
let [v0, v1, v2] = self.get_vertex_indices();
Some([mesh.s[v0], mesh.s[v1], mesh.s[v2]])
}
fn get_uvs(&self) -> Option<[Point2f; 3]> {
let mesh = self.mesh();
if mesh.s.is_empty() {
return None;
}
let [v0, v1, v2] = self.get_vertex_indices();
Some([mesh.uv[v0], mesh.uv[v1], mesh.uv[v2]])
}
pub fn new(mesh: Ptr<TriangleMesh>, tri_index: i32) -> Self {
Self { mesh, tri_index } Self { mesh, tri_index }
} }
pub fn get_mesh(&self) -> Ptr<DeviceTriangleMesh> { pub fn get_mesh(&self) -> Ptr<TriangleMesh> {
self.mesh self.mesh
} }
@ -209,8 +185,8 @@ impl TriangleShape {
flip_normal, flip_normal,
); );
isect.face_index = if !self.mesh.face_indices.is_null() { isect.face_index = if !self.mesh.face_indices.is_empty() {
unsafe { *self.mesh.face_indices.add(self.tri_index as usize) } unsafe { *self.mesh.face_indices.as_ptr().add(self.tri_index as usize) }
} else { } else {
0 0
}; };
@ -218,7 +194,7 @@ impl TriangleShape {
isect.common.n = ng; isect.common.n = ng;
isect.shading.n = ng; isect.shading.n = ng;
if !self.mesh.p.is_null() || !self.mesh.s.is_null() { if !self.mesh.p.is_empty() || !self.mesh.s.is_empty() {
self.compute_shading_geometry(&mut isect, &ti, uv, dpdu, determinant, degenerate); self.compute_shading_geometry(&mut isect, &ti, uv, dpdu, determinant, degenerate);
} }
isect isect

View file

@ -62,16 +62,16 @@ impl ColorSpaceId {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub struct RGBColorSpace { pub struct RGBColorSpace {
pub r: Point2f, pub r: Point2f,
pub g: Point2f, pub g: Point2f,
pub b: Point2f, pub b: Point2f,
pub w: Point2f, pub w: Point2f,
pub illuminant: DenselySampledSpectrum,
pub rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
pub xyz_from_rgb: SquareMatrix3f, pub xyz_from_rgb: SquareMatrix3f,
pub rgb_from_xyz: SquareMatrix3f, pub rgb_from_xyz: SquareMatrix3f,
pub illuminant: Ptr<DenselySampledSpectrum>,
pub rgb_to_spectrum_table: RGBToSpectrumTable,
} }
unsafe impl Send for RGBColorSpace {} unsafe impl Send for RGBColorSpace {}

View file

@ -85,7 +85,7 @@ impl RGBIlluminantSpectrum {
Self { Self {
scale, scale,
rsp, rsp,
illuminant: Ptr::from(&illuminant), illuminant,
} }
} }
} }

View file

@ -1,11 +1,10 @@
use super::cie::*; use super::cie::*;
use super::sampled::{LAMBDA_MAX, LAMBDA_MIN}; use super::sampled::{LAMBDA_MAX, LAMBDA_MIN};
use crate::Float;
use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::spectrum::{Spectrum, SpectrumTrait};
use crate::spectra::{N_SPECTRUM_SAMPLES, SampledSpectrum, SampledWavelengths}; use crate::spectra::{SampledSpectrum, SampledWavelengths, N_SPECTRUM_SAMPLES};
use crate::utils::find_interval; use crate::utils::find_interval;
use crate::utils::ptr::Ptr; use crate::{gvec, gvec_with_capacity, Float, GVec, Ptr};
use core::slice; use core::hash::{Hash, Hasher};
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
#[repr(C)] #[repr(C)]
@ -31,20 +30,73 @@ impl SpectrumTrait for ConstantSpectrum {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Clone)]
pub struct DenselySampledSpectrum { pub struct DenselySampledSpectrum {
pub lambda_min: i32, pub lambda_min: i32,
pub lambda_max: i32, pub lambda_max: i32,
pub values: Ptr<Float>, pub values: GVec<Float>,
} }
unsafe impl Send for DenselySampledSpectrum {} unsafe impl Send for DenselySampledSpectrum {}
unsafe impl Sync for DenselySampledSpectrum {} unsafe impl Sync for DenselySampledSpectrum {}
impl DenselySampledSpectrum { impl DenselySampledSpectrum {
pub fn new(lambda_min: i32, lambda_max: i32, values: GVec<Float>) -> Self {
let func_integral = 0.0;
Self {
lambda_min,
lambda_max,
values,
}
}
pub fn new_zero(lambda_min: i32, lambda_max: i32) -> Self {
let n = (lambda_max - lambda_min + 1).max(0) as usize;
let mut values = gvec_with_capacity(n);
values.resize(n, 0.0);
Self {
lambda_min,
lambda_max,
values,
}
}
pub fn from_spectrum(spec: &Spectrum) -> Self {
let mut values = gvec_with_capacity((LAMBDA_MAX - LAMBDA_MIN + 1) as usize);
for lambda in LAMBDA_MIN..=LAMBDA_MAX {
values.push(spec.evaluate(lambda as Float));
}
Self {
lambda_min: LAMBDA_MIN,
lambda_max: LAMBDA_MAX,
values,
}
}
pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self
where
F: Fn(Float) -> Float,
{
let mut values = gvec_with_capacity((lambda_max - lambda_min + 1) as usize);
for lambda in lambda_min..=lambda_max {
values.push(f(lambda as Float));
}
Self {
lambda_min,
lambda_max,
values,
}
}
pub fn scale(&mut self, s: Float) {
for v in &mut self.values {
*v *= s;
}
}
#[inline(always)] #[inline(always)]
pub fn count(&self) -> usize { pub fn count(&self) -> usize {
if self.values.is_null() { if self.values.is_empty() {
0 0
} else { } else {
(self.lambda_max - self.lambda_min + 1) as usize (self.lambda_max - self.lambda_min + 1) as usize
@ -52,8 +104,8 @@ impl DenselySampledSpectrum {
} }
#[inline(always)] #[inline(always)]
fn get(&self, idx: u32) -> Float { pub fn value(&self, idx: u32) -> Float {
unsafe { *self.values.add(idx as usize) } unsafe { *self.values.as_ptr().add(idx as usize) }
} }
} }
@ -67,21 +119,41 @@ impl PartialEq for DenselySampledSpectrum {
impl Eq for DenselySampledSpectrum {} impl Eq for DenselySampledSpectrum {}
impl Hash for DenselySampledSpectrum {
fn hash<H: Hasher>(&self, state: &mut H) {
self.lambda_min.hash(state);
self.lambda_max.hash(state);
for &val in self.values.iter() {
val.to_bits().hash(state);
}
}
}
impl SpectrumTrait for DenselySampledSpectrum { impl SpectrumTrait for DenselySampledSpectrum {
fn max_value(&self) -> Float {
if self.values.is_empty() {
return 0.0;
}
let mut max_val = Float::NEG_INFINITY;
for i in 0..self.count() {
let val = self.value(i as u32);
if val > max_val {
max_val = val;
}
}
max_val
}
fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum { fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
let mut s = SampledSpectrum::default(); let mut s = SampledSpectrum::default();
let n = self.count() as i32; let n = self.count() as i32;
for i in 0..N_SPECTRUM_SAMPLES { for i in 0..N_SPECTRUM_SAMPLES {
let offset = lambda[i].round() as i32 - self.lambda_min; let offset = lambda[i].round() as i32 - self.lambda_min;
s[i] = if offset < 0 || offset >= n {
if offset < 0 || offset >= n { 0.0
s[i] = 0.0;
} else { } else {
unsafe { self.value(offset as u32)
s[i] = *self.values.add(offset as usize); };
}
}
} }
s s
} }
@ -92,47 +164,66 @@ impl SpectrumTrait for DenselySampledSpectrum {
if offset < 0 || offset >= n { if offset < 0 || offset >= n {
0.0 0.0
} else { } else {
unsafe { *self.values.add(offset as usize) } self.value(offset as u32)
} }
} }
fn max_value(&self) -> Float {
if self.values.is_null() {
return 0.;
}
let n = self.count();
let mut max_val = Float::NEG_INFINITY;
for i in 0..n {
unsafe {
let val = *self.values.add(i);
if val > max_val {
max_val = val;
}
}
}
max_val
}
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub struct PiecewiseLinearSpectrum { pub struct PiecewiseLinearSpectrum {
pub lambdas: Ptr<Float>, pub lambdas: GVec<Float>,
pub values: Ptr<Float>, pub values: GVec<Float>,
pub count: u32, pub count: u32,
} }
impl PiecewiseLinearSpectrum { impl PiecewiseLinearSpectrum {
#[inline(always)] #[inline(always)]
fn lambda(&self, i: u32) -> Float { pub fn count(&self) -> usize {
unsafe { *self.lambdas.add(i as usize) } self.count.try_into().unwrap()
}
#[inline(always)]
pub fn lambda(&self, idx: u32) -> Float {
unsafe { *self.lambdas.as_ptr().add(idx as usize) }
} }
#[inline(always)] #[inline(always)]
fn value(&self, i: u32) -> Float { pub fn value(&self, idx: u32) -> Float {
unsafe { *self.values.add(i as usize) } unsafe { *self.values.as_ptr().add(idx as usize) }
}
pub fn new(lambdas: GVec<Float>, values: GVec<Float>) -> Self {
assert_eq!(lambdas.len(), values.len());
let count = lambdas.len() as u32;
Self {
lambdas,
values,
count,
}
}
pub fn from_interleaved(data: &[Float], _normalize: bool) -> Self {
assert!(
data.len() % 2 == 0,
"Interleaved data must have even length"
);
let n = data.len() / 2;
let mut pairs: GVec<(Float, Float)> = gvec_with_capacity(n);
for chunk in data.chunks(2) {
pairs.push((chunk[0], chunk[1]));
}
pairs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
let mut lambdas = gvec_with_capacity(n);
let mut values = gvec_with_capacity(n);
for (l, v) in pairs.iter() {
lambdas.push(*l);
values.push(*v);
}
Self::new(lambdas, values)
} }
} }
@ -141,7 +232,7 @@ unsafe impl Sync for PiecewiseLinearSpectrum {}
impl SpectrumTrait for PiecewiseLinearSpectrum { impl SpectrumTrait for PiecewiseLinearSpectrum {
fn evaluate(&self, lambda: Float) -> Float { fn evaluate(&self, lambda: Float) -> Float {
if self.lambdas.is_null() { if self.lambdas.is_empty() {
return 0.0; return 0.0;
} }
@ -165,7 +256,7 @@ impl SpectrumTrait for PiecewiseLinearSpectrum {
} }
fn max_value(&self) -> Float { fn max_value(&self) -> Float {
if self.values.is_null() { if self.values.is_empty() {
return 0.; return 0.;
} }
@ -174,7 +265,7 @@ impl SpectrumTrait for PiecewiseLinearSpectrum {
for i in 0..n { for i in 0..n {
unsafe { unsafe {
let val = *self.values.add(i as usize); let val = *self.values.as_ptr().add(i as usize);
if val > max_val { if val > max_val {
max_val = val; max_val = val;
} }

View file

@ -18,7 +18,7 @@ pub struct GPUSpectrumImageTexture {
pub scale: Float, pub scale: Float,
pub invert: bool, pub invert: bool,
pub is_single_channel: bool, pub is_single_channel: bool,
pub color_space: RGBColorSpace, pub color_space: Ptr<RGBColorSpace>,
pub spectrum_type: SpectrumType, pub spectrum_type: SpectrumType,
} }

202
shared/src/utils/alloc.rs Normal file
View file

@ -0,0 +1,202 @@
extern crate alloc;
use alloc::alloc::Global;
use alloc::vec::Vec;
use alloc::boxed::Box;
use core::alloc::{AllocError, Allocator, Layout};
use core::ptr::NonNull;
// CPU fallback, delegates to Global
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemAlloc;
unsafe impl Allocator for SystemAlloc {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
Global.allocate(layout)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
Global.deallocate(ptr, layout)
}
unsafe fn grow(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
Global.grow(ptr, old_layout, new_layout)
}
unsafe fn shrink(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
Global.shrink(ptr, old_layout, new_layout)
}
}
// Unified memory via cudaMallocManaged
#[cfg(feature = "cuda")]
pub mod cuda {
use super::*;
use cust::memory::{cuda_free_unified, cuda_malloc_unified, UnifiedPointer};
#[derive(Debug, Clone, Copy, Default)]
pub struct CudaAlloc;
unsafe impl Allocator for CudaAlloc {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
if layout.size() == 0 {
// Zero-sized allocations: return a dangling aligned pointer
// with zero length, which is valid for ZSTs.
let ptr = NonNull::new(layout.align() as *mut u8).ok_or(AllocError)?;
return Ok(NonNull::slice_from_raw_parts(ptr, 0));
}
let ptr = cuda_malloc_unified::<u8>(layout.size()).map_err(|_| AllocError)?;
let raw = ptr.as_raw_mut();
core::mem::forget(ptr); // Arena owns the raw pointer, not the RAII wrapper
let nn = NonNull::new(raw).ok_or(AllocError)?;
Ok(NonNull::slice_from_raw_parts(nn, layout.size()))
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
if layout.size() == 0 {
return;
}
let _ = cuda_free_unified(UnifiedPointer::wrap(ptr.as_ptr()));
}
}
}
// Host-visible GPU memory via gpu-allocator
#[cfg(feature = "vulkan")]
pub mod vulkan {
use super::*;
use ash::vk;
use gpu_allocator::vulkan::{
Allocation, AllocationCreateDesc, AllocationScheme, Allocator as VkAllocator,
AllocatorCreateDesc,
};
use gpu_allocator::MemoryLocation;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
/// Wraps a gpu-allocator instance. Clone is cheap
#[derive(Clone)]
pub struct VulkanAlloc {
inner: Arc<Mutex<VulkanInner>>,
}
struct VulkanInner {
allocator: VkAllocator,
allocations: HashMap<usize, Allocation>,
}
impl VulkanAlloc {
pub fn new(
instance: &ash::Instance,
device: ash::Device,
physical_device: vk::PhysicalDevice,
) -> Self {
let allocator = VkAllocator::new(&AllocatorCreateDesc {
instance: instance.clone(),
device: device.clone(),
physical_device,
debug_settings: Default::default(),
buffer_device_address: false,
allocation_sizes: Default::default(),
})
.expect("Failed to create Vulkan allocator");
Self {
inner: Arc::new(Mutex::new(VulkanInner {
allocator,
allocations: HashMap::new(),
})),
}
}
}
unsafe impl Allocator for VulkanAlloc {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
if layout.size() == 0 {
let ptr = NonNull::new(layout.align() as *mut u8).ok_or(AllocError)?;
return Ok(NonNull::slice_from_raw_parts(ptr, 0));
}
let mut inner = self.inner.lock();
let allocation = inner
.allocator
.allocate(&AllocationCreateDesc {
name: "gvec_alloc",
requirements: vk::MemoryRequirements {
size: layout.size() as u64,
alignment: layout.align() as u64,
memory_type_bits: u32::MAX,
},
location: MemoryLocation::CpuToGpu,
linear: true,
allocation_scheme: AllocationScheme::GpuAllocatorManaged,
})
.map_err(|_| AllocError)?;
let ptr = allocation.mapped_ptr().ok_or(AllocError)?.as_ptr() as *mut u8;
let nn = NonNull::new(ptr).ok_or(AllocError)?;
inner.allocations.insert(ptr as usize, allocation);
Ok(NonNull::slice_from_raw_parts(nn, layout.size()))
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
if layout.size() == 0 {
return;
}
let mut inner = self.inner.lock();
if let Some(allocation) = inner.allocations.remove(&(ptr.as_ptr() as usize)) {
inner
.allocator
.free(allocation)
.expect("Vulkan free failed");
}
}
}
}
#[cfg(feature = "cuda")]
pub type GpuAlloc = cuda::CudaAlloc;
#[cfg(all(feature = "vulkan", not(feature = "cuda")))]
pub type GpuAlloc = vulkan::VulkanAlloc;
#[cfg(not(any(feature = "cuda", feature = "vulkan")))]
pub type GpuAlloc = SystemAlloc;
pub type GVec<T> = alloc::vec::Vec<T, GpuAlloc>;
pub type GBox<T> = alloc::boxed::Box<T, GpuAlloc>;
pub fn gvec<T>() -> GVec<T> {
Vec::new_in(GpuAlloc::default())
}
pub fn gvec_with_capacity<T>(cap: usize) -> GVec<T> {
Vec::with_capacity_in(cap, GpuAlloc::default())
}
pub fn gvec_from_slice<T: Clone>(slice: &[T]) -> GVec<T> {
let mut v = gvec_with_capacity(slice.len());
v.extend_from_slice(slice);
v
}
pub fn gbox<T>(value: T) -> GBox<T> {
Box::new_in(value, GpuAlloc::default())
}

View file

@ -1,9 +1,8 @@
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2i, Bounds3f, Bounds3i, Point2i, Point3f, Point3i, Vector2i, Vector3f, Vector3i, Bounds2i, Bounds3f, Bounds3i, Point2i, Point3f, Point3i, Vector2i, Vector3f, Vector3i,
}; };
use crate::core::pbrt::Float;
use crate::utils::Ptr;
use crate::utils::math::lerp; use crate::utils::math::lerp;
use crate::{gvec, gvec_from_slice, Float, GVec};
use core::ops::{Add, Index, IndexMut, Mul, Sub}; use core::ops::{Add, Index, IndexMut, Mul, Sub};
pub trait Interpolatable: pub trait Interpolatable:
@ -16,107 +15,121 @@ impl<T> Interpolatable for T where
{ {
} }
#[repr(C)] #[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)] pub struct Array2D<T> {
pub struct DeviceArray2D<T> { extent: Bounds2i,
pub values: *mut T, values: GVec<T>,
pub extent: Bounds2i,
pub stride: i32,
} }
unsafe impl<T: Send> Send for DeviceArray2D<T> {} impl<T> Array2D<T> {
unsafe impl<T: Sync> Sync for DeviceArray2D<T> {} pub fn extent(&self) -> Bounds2i {
self.extent
impl<T> DeviceArray2D<T> {
#[inline]
pub fn x_size(&self) -> u32 {
(self.extent.p_max.x() - self.extent.p_min.x()) as u32
} }
pub fn stride(&self) -> i32 {
#[inline] self.extent.p_max.x() - self.extent.p_min.x()
pub fn y_size(&self) -> u32 {
(self.extent.p_max.y() - self.extent.p_min.y()) as u32
} }
pub fn x_size(&self) -> usize {
#[inline] self.stride() as usize
pub fn size(&self) -> u32 {
self.extent.area() as u32
} }
pub fn y_size(&self) -> usize {
#[inline(always)] (self.extent.p_max.y() - self.extent.p_min.y()) as usize
fn offset(&self, p: Point2i) -> isize {
let ox = p.x() - self.extent.p_min.x();
let oy = p.y() - self.extent.p_min.y();
(ox + oy * self.stride) as isize
} }
#[inline]
pub fn index(&self, x: i32, y: i32) -> u32 {
let nx = x - self.extent.p_min.x();
let ny = y - self.extent.p_min.y();
nx as u32 + self.x_size() * ny as u32
}
#[inline(always)]
pub fn get(&self, p: Point2i) -> &T {
unsafe { &*self.values.offset(self.offset(p)) }
}
#[inline(always)]
pub fn get_mut(&mut self, p: Point2i) -> &mut T {
unsafe { &mut *self.values.offset(self.offset(p)) }
}
#[inline]
pub fn get_linear(&self, index: usize) -> &T {
unsafe { &*self.values.add(index) }
}
#[inline]
pub fn get_linear_mut(&mut self, index: usize) -> &mut T {
unsafe { &mut *self.values.add(index) }
}
pub fn as_slice(&self) -> &[T] { pub fn as_slice(&self) -> &[T] {
unsafe { core::slice::from_raw_parts(self.values, self.size() as usize) } &self.values
} }
pub fn as_mut_slice(&mut self) -> &mut [T] { pub fn as_mut_slice(&mut self) -> &mut [T] {
unsafe { core::slice::from_raw_parts_mut(self.values, self.size() as usize) } &mut self.values
}
pub fn as_ptr(&self) -> *const T {
self.values.as_ptr()
}
pub fn as_mut_ptr(&mut self) -> *mut T {
self.values.as_mut_ptr()
}
pub fn len(&self) -> usize {
self.values.len()
} }
} }
impl<T> Index<Point2i> for DeviceArray2D<T> { unsafe impl<T: Send> Send for Array2D<T> {}
unsafe impl<T: Sync> Sync for Array2D<T> {}
impl<T: Default + Clone> Array2D<T> {
pub fn new(extent: Bounds2i) -> Self {
let n = extent.area() as usize;
let mut values = gvec();
values.resize(n, T::default());
Self { extent, values }
}
pub fn new_dims(nx: i32, ny: i32) -> Self {
Self::new(Bounds2i::from_points(
Point2i::new(0, 0),
Point2i::new(nx, ny),
))
}
pub fn new_filled(nx: i32, ny: i32, val: T) -> Self {
let extent = Bounds2i::from_points(Point2i::new(0, 0), Point2i::new(nx, ny));
let n = (nx * ny) as usize;
let mut values = gvec();
values.resize(n, val);
Self { extent, values }
}
pub fn from_slice(extent: Bounds2i, slice: &[T]) -> Self {
assert_eq!(slice.len(), extent.area() as usize);
Self {
extent,
values: gvec_from_slice(slice),
}
}
pub fn get(&self, p: Point2i) -> &T {
&self[p]
}
pub fn get_linear_mut(&mut self, index: usize) -> &mut T {
unsafe { &mut *self.values.as_mut_ptr().add(index) }
}
}
impl<T> Index<(i32, i32)> for Array2D<T> {
type Output = T; type Output = T;
#[inline(always)] fn index(&self, (x, y): (i32, i32)) -> &T {
fn index(&self, p: Point2i) -> &Self::Output { let offset = (y - self.extent.p_min.y()) * self.stride() + (x - self.extent.p_min.x());
self.get(p) &self.values[offset as usize]
} }
} }
impl<T> IndexMut<Point2i> for DeviceArray2D<T> { impl<T> IndexMut<(i32, i32)> for Array2D<T> {
fn index_mut(&mut self, p: Point2i) -> &mut Self::Output { fn index_mut(&mut self, (x, y): (i32, i32)) -> &mut T {
self.get_mut(p) let offset = (y - self.extent.p_min.y()) * self.stride() + (x - self.extent.p_min.x());
&mut self.values[offset as usize]
} }
} }
impl<T> Index<(i32, i32)> for DeviceArray2D<T> { impl<T> Index<Point2i> for Array2D<T> {
type Output = T; type Output = T;
fn index(&self, (x, y): (i32, i32)) -> &Self::Output { fn index(&self, p: Point2i) -> &T {
&self[Point2i::new(x, y)] let offset =
(p.y() - self.extent.p_min.y()) * self.stride() + (p.x() - self.extent.p_min.x());
&self.values[offset as usize]
} }
} }
impl<T> IndexMut<(i32, i32)> for DeviceArray2D<T> { impl<T> IndexMut<Point2i> for Array2D<T> {
fn index_mut(&mut self, (x, y): (i32, i32)) -> &mut Self::Output { fn index_mut(&mut self, p: Point2i) -> &mut T {
&mut self[Point2i::new(x, y)] let offset =
(p.y() - self.extent.p_min.y()) * self.stride() + (p.x() - self.extent.p_min.x());
&mut self.values[offset as usize]
} }
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub struct SampledGrid<T> { pub struct SampledGrid<T> {
pub values: Ptr<T>, pub values: GVec<T>,
pub values_len: u32, pub values_len: u32,
pub nx: i32, pub nx: i32,
pub ny: i32, pub ny: i32,
@ -126,12 +139,11 @@ pub struct SampledGrid<T> {
unsafe impl<T: Sync> Sync for SampledGrid<T> {} unsafe impl<T: Sync> Sync for SampledGrid<T> {}
unsafe impl<T: Send> Send for SampledGrid<T> {} unsafe impl<T: Send> Send for SampledGrid<T> {}
impl<T> SampledGrid<T> { impl<T: core::clone::Clone> SampledGrid<T> {
#[cfg(not(target_os = "cuda"))]
pub fn new(slice: &[T], nx: i32, ny: i32, nz: i32) -> Self { pub fn new(slice: &[T], nx: i32, ny: i32, nz: i32) -> Self {
assert_eq!(slice.len(), (nx * ny * nz) as usize); assert_eq!(slice.len(), (nx * ny * nz) as usize);
Self { Self {
values: Ptr::from(slice), values: gvec_from_slice(slice),
values_len: (nx * ny * nz) as u32, values_len: (nx * ny * nz) as u32,
nx, nx,
ny, ny,
@ -141,7 +153,7 @@ impl<T> SampledGrid<T> {
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
values: Ptr::null(), values: gvec(),
values_len: 0, values_len: 0,
nx: 0, nx: 0,
ny: 0, ny: 0,
@ -150,7 +162,7 @@ impl<T> SampledGrid<T> {
} }
pub fn is_valid(&self) -> bool { pub fn is_valid(&self) -> bool {
!self.values.is_null() && self.nx > 0 && self.ny > 0 && self.nz > 0 !self.values.is_empty() && self.nx > 0 && self.ny > 0 && self.nz > 0
} }
pub fn bytes_allocated(&self) -> u32 { pub fn bytes_allocated(&self) -> u32 {
@ -189,7 +201,7 @@ impl<T> SampledGrid<T> {
let idx = (p.z() * self.ny + p.y()) * self.nx + p.x(); let idx = (p.z() * self.ny + p.y()) * self.nx + p.x();
unsafe { unsafe {
let val = &*self.values.add(idx as usize); let val = &*self.values.as_ptr().add(idx as usize);
convert(val) convert(val)
} }
} }

View file

@ -1,10 +1,10 @@
use crate::core::color::{RGB, XYZ}; use crate::core::color::{RGB, XYZ};
use crate::core::geometry::{Lerp, MulAdd, Point, Point2f, Point2i, Vector, Vector3f, VectorLike}; use crate::core::geometry::{Lerp, MulAdd, Point, Point2f, Point2i, Vector, Vector3f, VectorLike};
use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI_OVER_4}; use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI_OVER_4};
use crate::utils::gpu_array_from_fn;
use crate::utils::hash::{hash_buffer, mix_bits}; use crate::utils::hash::{hash_buffer, mix_bits};
use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV}; use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
use crate::{gvec, gvec_with_capacity, GVec, Ptr};
use crate::utils::{Ptr, gpu_array_from_fn};
use core::fmt::{self, Display, Write}; use core::fmt::{self, Display, Write};
use core::iter::{Product, Sum}; use core::iter::{Product, Sum};
use core::mem; use core::mem;
@ -137,7 +137,12 @@ pub fn windowed_sinc(x: Float, radius: Float, tau: Float) -> Float {
if x.abs() > radius { if x.abs() > radius {
return 0.; return 0.;
} }
sinc(x) * sinc(x / tau)
if x < 1e-5 {
1.0
} else {
sinc(x) * sinc(x / tau)
}
} }
#[inline] #[inline]
@ -261,7 +266,11 @@ pub fn quadratic(a: Float, b: Float, c: Float) -> Option<(Float, Float)> {
pub fn smooth_step(x: Float, a: Float, b: Float) -> Float { pub fn smooth_step(x: Float, a: Float, b: Float) -> Float {
if a == b { if a == b {
if x < a { return 0. } else { return 1. } if x < a {
return 0.;
} else {
return 1.;
}
} }
let t = clamp((x - a) / (b - a), 0., 1.); let t = clamp((x - a) / (b - a), 0., 1.);
@ -760,27 +769,65 @@ pub fn inverse_radical_inverse(mut inverse: u64, base: u64, n_digits: u64) -> u6
// Digit scrambling // Digit scrambling
#[repr(C)] #[repr(C)]
#[derive(Default, Debug, Copy, Clone)] #[derive(Debug, Clone)]
pub struct DeviceDigitPermutation { pub struct DigitPermutation {
pub base: i32, pub base: i32,
pub n_digits: u32, pub n_digits: u32,
pub permutations: Ptr<u16>, pub permutations: GVec<u16>,
} }
impl DeviceDigitPermutation { impl Default for DigitPermutation {
fn default() -> Self {
Self {
base: i32::default(),
n_digits: u32::default(),
permutations: gvec(),
}
}
}
impl DigitPermutation {
pub fn new(base: i32, seed: u64) -> Self {
assert!(base < 65536);
let mut n_digits: u32 = 0;
let inv_base = 1. / base as Float;
let mut inv_base_m = 1.;
while 1.0 - ((base as Float - 1.0) * inv_base_m) < 1.0 {
n_digits += 1;
inv_base_m *= inv_base;
}
let mut permutations = gvec_with_capacity(n_digits as usize * base as usize);
for digit_index in 0..n_digits {
let hash_input = [base as u64, digit_index as u64, seed];
let dseed = hash_buffer(&hash_input, 0);
for digit_value in 0..base {
let index = (digit_index as i32 * base + digit_value) as usize;
permutations[index] =
permutation_element(digit_value as u32, base as u32, dseed as u32) as u16;
}
}
Self {
base,
n_digits,
permutations,
}
}
#[inline(always)] #[inline(always)]
pub fn permute(&self, digit_index: i32, digit_value: i32) -> i32 { pub fn permute(&self, digit_index: i32, digit_value: i32) -> i32 {
let idx = (digit_index * self.base as i32 + digit_value) as usize; let idx = (digit_index * self.base as i32 + digit_value) as usize;
let permutation = unsafe { *self.permutations.add(idx.into()) }; let permutation = unsafe { *self.permutations.as_ptr().add(idx.into()) };
permutation as i32 permutation as i32
} }
} }
pub fn scrambled_radical_inverse( pub fn scrambled_radical_inverse(base_index: u32, mut a: u64, perm: &DigitPermutation) -> Float {
base_index: u32,
mut a: u64,
perm: &DeviceDigitPermutation,
) -> Float {
let base = PRIMES[base_index as usize] as u64; let base = PRIMES[base_index as usize] as u64;
let limit = (u64::MAX / base).saturating_sub(base); let limit = (u64::MAX / base).saturating_sub(base);
@ -1443,7 +1490,11 @@ where
} }
let det: T = (0..N).map(|i| lum[i][i]).product(); let det: T = (0..N).map(|i| lum[i][i]).product();
if parity < 0 { -det } else { det } if parity < 0 {
-det
} else {
det
}
} }
} }
} }

View file

@ -1,39 +0,0 @@
use crate::Float;
use crate::core::geometry::{Normal3f, Point2f, Point3f, Vector3f};
use crate::utils::Ptr;
use crate::utils::Transform;
use crate::utils::sampling::DevicePiecewiseConstant2D;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct DeviceTriangleMesh {
pub p: Ptr<Point3f>,
pub n: Ptr<Normal3f>,
pub s: Ptr<Vector3f>,
pub uv: Ptr<Point2f>,
pub vertex_indices: Ptr<i32>,
pub face_indices: Ptr<i32>,
pub n_triangles: u32,
pub n_vertices: u32,
pub reverse_orientation: bool,
pub transform_swaps_handedness: bool,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DeviceBilinearPatchMesh {
pub image_distribution: Ptr<DevicePiecewiseConstant2D>,
pub p: Ptr<Point3f>,
pub n: Ptr<Normal3f>,
pub uv: Ptr<Point2f>,
pub vertex_indices: Ptr<i32>,
pub n_patches: u32,
pub n_vertices: u32,
pub reverse_orientation: bool,
pub transform_swaps_handedness: bool,
}
unsafe impl Send for DeviceTriangleMesh {}
unsafe impl Sync for DeviceTriangleMesh {}
unsafe impl Send for DeviceBilinearPatchMesh {}
unsafe impl Sync for DeviceBilinearPatchMesh {}

View file

@ -1,9 +1,9 @@
pub mod alloc;
pub mod complex; pub mod complex;
pub mod containers; pub mod containers;
pub mod hash; pub mod hash;
pub mod interval; pub mod interval;
pub mod math; pub mod math;
pub mod mesh;
pub mod noise; pub mod noise;
pub mod options; pub mod options;
pub mod ptr; pub mod ptr;
@ -17,15 +17,7 @@ pub mod transform;
pub use options::PBRTOptions; pub use options::PBRTOptions;
pub use ptr::Ptr; pub use ptr::Ptr;
pub use transform::{AnimatedTransform, Transform, TransformGeneric}; pub use transform::{AnimatedTransform, Transform, TransformGeneric};
pub use containers::Array2D;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{
parse_macro_input, Attribute, Data, DeriveInput, Expr, Fields, GenericArgument, Ident, Lit,
PathArguments, Type, Variant,
};
use crate::Float; use crate::Float;
use core::sync::atomic::{AtomicU32, Ordering}; use core::sync::atomic::{AtomicU32, Ordering};
@ -138,514 +130,3 @@ pub fn gpu_array_from_fn<T, const N: usize>(mut f: impl FnMut(usize) -> T) -> [T
} }
} }
/// # Enum variant attributes
///
/// | Attribute | Effect |
/// |-----------|--------|
/// | *(none)* | Inner type has `DeviceRepr`; auto-call `upload_value` |
/// | `#[device(clone)]` | Same type on both sides, just clone |
/// | `#[device(custom = "method")]` | You provide `fn method(inner: &T, arena) -> DeviceT` |
/// | `#[device(variant_type = "T")]` | Override the device-side variant's inner type |
///
/// # Container attribute
///
/// `#[device(name = "DeviceFoo")]` — override the generated type name (default: `Device{Name}`).
#[proc_macro_derive(Device, attributes(device))]
pub fn derive_device(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match derive_impl(input) {
Ok(tokens) => tokens.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream2> {
match &input.data {
Data::Struct(_) => derive_struct(input),
Data::Enum(_) => derive_enum(input),
Data::Union(_) => Err(syn::Error::new_spanned(
&input.ident,
"Device derive does not support unions",
)),
}
}
// Struct derivation
fn derive_struct(input: DeriveInput) -> syn::Result<TokenStream2> {
let host_name = &input.ident;
let vis = &input.vis;
let device_name = get_device_name(&input.attrs, host_name)?;
let fields = match &input.data {
Data::Struct(s) => match &s.fields {
Fields::Named(named) => &named.named,
_ => {
return Err(syn::Error::new_spanned(
host_name,
"Device derive only supports structs with named fields",
))
}
},
_ => unreachable!(),
};
let mut device_fields = Vec::new();
let mut upload_stmts = Vec::new();
let mut device_field_inits = Vec::new();
let mut spread_expr: Option<Expr> = None;
for field in fields {
let field_name = field.ident.as_ref().unwrap();
let attrs = parse_field_attrs(&field.attrs)?;
if attrs.skip {
continue;
}
if let Some(ref expr_str) = attrs.spread {
spread_expr = Some(syn::parse_str(expr_str).map_err(|e| {
syn::Error::new_spanned(field, format!("invalid device(spread): {}", e))
})?);
continue;
}
if let Some(expr_str) = &attrs.expr {
let expr: Expr = syn::parse_str(expr_str).map_err(|e| {
syn::Error::new_spanned(field, format!("invalid device(expr): {}", e))
})?;
let ty = &field.ty;
device_fields.push(quote! { pub #field_name: #ty });
upload_stmts.push(quote! { let #field_name = #expr; });
device_field_inits.push(quote! { #field_name });
continue;
}
match classify_type(&field.ty) {
FieldClass::VecCopy(inner_ty) => {
let len_name = format_ident!("{}_len", field_name);
device_fields.push(quote! { pub #field_name: Ptr<#inner_ty> });
device_fields.push(quote! { pub #len_name: usize });
upload_stmts.push(quote! {
let (#field_name, #len_name) = arena.alloc_slice(&self.#field_name);
});
device_field_inits.push(quote! { #field_name });
device_field_inits.push(quote! { #len_name });
}
FieldClass::VecUploadable(inner_ty) => {
let len_name = format_ident!("{}_len", field_name);
device_fields.push(quote! {
pub #field_name: Ptr<<#inner_ty as DeviceRepr>::Target>
});
device_fields.push(quote! { pub #len_name: usize });
upload_stmts.push(quote! {
let __up: Vec<<#inner_ty as DeviceRepr>::Target> = self.#field_name
.iter()
.map(|item| DeviceRepr::upload_value(item, arena))
.collect();
let (#field_name, #len_name) = arena.alloc_slice(&__up);
});
device_field_inits.push(quote! { #field_name });
device_field_inits.push(quote! { #len_name });
}
FieldClass::Option(inner_ty) => {
device_fields.push(quote! {
pub #field_name: Ptr<<#inner_ty as DeviceRepr>::Target>
});
upload_stmts.push(quote! {
let #field_name = match &self.#field_name {
Some(val) => DeviceRepr::upload(val, arena),
None => Ptr::null(),
};
});
device_field_inits.push(quote! { #field_name });
}
FieldClass::Arc(inner_ty) => {
if attrs.flatten {
device_fields.push(quote! {
pub #field_name: <#inner_ty as DeviceRepr>::Target
});
upload_stmts.push(quote! {
let #field_name = DeviceRepr::upload_value(&*self.#field_name, arena);
});
} else {
device_fields.push(quote! {
pub #field_name: Ptr<<#inner_ty as DeviceRepr>::Target>
});
upload_stmts.push(quote! {
let #field_name = DeviceRepr::upload(&*self.#field_name, arena);
});
}
device_field_inits.push(quote! { #field_name });
}
FieldClass::Plain => {
let ty = &field.ty;
if attrs.copy_upload {
device_fields.push(quote! { pub #field_name: #ty });
upload_stmts.push(quote! {
let #field_name = self.#field_name.clone();
});
} else if attrs.flatten {
device_fields.push(quote! {
pub #field_name: <#ty as DeviceRepr>::Target
});
upload_stmts.push(quote! {
let #field_name = DeviceRepr::upload_value(&self.#field_name, arena);
});
} else if attrs.upload {
device_fields.push(quote! {
pub #field_name: Ptr<<#ty as DeviceRepr>::Target>
});
upload_stmts.push(quote! {
let #field_name = DeviceRepr::upload(&self.#field_name, arena);
});
} else {
device_fields.push(quote! { pub #field_name: #ty });
upload_stmts.push(quote! {
let #field_name = self.#field_name;
});
}
device_field_inits.push(quote! { #field_name });
}
}
}
let constructor = if let Some(spread) = spread_expr {
quote! {
#device_name {
#(#device_field_inits,)*
..#spread
}
}
} else {
quote! {
#device_name {
#(#device_field_inits,)*
}
}
};
Ok(quote! {
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#vis struct #device_name {
#(#device_fields,)*
}
unsafe impl Send for #device_name {}
unsafe impl Sync for #device_name {}
impl DeviceRepr for #host_name {
type Target = #device_name;
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> Self::Target {
#(#upload_stmts)*
#constructor
}
}
})
}
// Enum derivation
fn derive_enum(input: DeriveInput) -> syn::Result<TokenStream2> {
let host_name = &input.ident;
let vis = &input.vis;
let device_name = get_device_name(&input.attrs, host_name)?;
let variants = match &input.data {
Data::Enum(e) => &e.variants,
_ => unreachable!(),
};
let mut device_variants = Vec::new();
let mut match_arms = Vec::new();
for variant in variants {
let var_name = &variant.ident;
let var_attrs = parse_variant_attrs(&variant.attrs)?;
let inner_ty = get_variant_inner_type(variant)?;
// Determine the device-side inner type for this variant
let device_inner: Type = if let Some(ref ty_str) = var_attrs.variant_type {
syn::parse_str(ty_str).map_err(|e| {
syn::Error::new_spanned(variant, format!("invalid variant_type: {}", e))
})?
} else if var_attrs.clone_variant {
// clone: same type on both sides
inner_ty.clone()
} else {
// auto-upload: use DeviceRepr::Target
syn::parse_str(&format!("<{} as DeviceRepr>::Target", quote!(#inner_ty))).map_err(
|e| {
syn::Error::new_spanned(variant, format!("cannot construct Target type: {}", e))
},
)?
};
device_variants.push(quote! { #var_name(#device_inner) });
if var_attrs.clone_variant {
match_arms.push(quote! {
#host_name::#var_name(inner) => #device_name::#var_name(inner.clone())
});
} else if let Some(ref method) = var_attrs.custom {
let method_ident = format_ident!("{}", method);
match_arms.push(quote! {
#host_name::#var_name(inner) => {
#device_name::#var_name(Self::#method_ident(inner, arena))
}
});
} else {
// Default: inner implements DeviceRepr
match_arms.push(quote! {
#host_name::#var_name(inner) => {
#device_name::#var_name(DeviceRepr::upload_value(inner, arena))
}
});
}
}
Ok(quote! {
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#vis enum #device_name {
#(#device_variants,)*
}
unsafe impl Send for #device_name {}
unsafe impl Sync for #device_name {}
impl DeviceRepr for #host_name {
type Target = #device_name;
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> Self::Target {
match self {
#(#match_arms,)*
}
}
}
})
}
fn get_variant_inner_type(variant: &Variant) -> syn::Result<Type> {
match &variant.fields {
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
Ok(fields.unnamed.first().unwrap().ty.clone())
}
Fields::Unit => Err(syn::Error::new_spanned(
variant,
"Device derive: enum variants must have exactly one field, e.g. Variant(Type)",
)),
_ => Err(syn::Error::new_spanned(
variant,
"Device derive: only single-field tuple variants supported, e.g. Variant(Type)",
)),
}
}
// Attribute parsing for variants
struct VariantAttrs {
clone_variant: bool,
custom: Option<String>,
variant_type: Option<String>,
}
fn parse_variant_attrs(attrs: &[Attribute]) -> syn::Result<VariantAttrs> {
let mut result = VariantAttrs {
clone_variant: false,
custom: None,
variant_type: None,
};
for attr in attrs {
if !attr.path().is_ident("device") {
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("clone") {
result.clone_variant = true;
Ok(())
} else if meta.path.is_ident("custom") {
let value = meta.value()?;
let lit: Lit = value.parse()?;
if let Lit::Str(s) = lit {
result.custom = Some(s.value());
Ok(())
} else {
Err(meta.error("expected string literal"))
}
} else if meta.path.is_ident("variant_type") {
let value = meta.value()?;
let lit: Lit = value.parse()?;
if let Lit::Str(s) = lit {
result.variant_type = Some(s.value());
Ok(())
} else {
Err(meta.error("expected string literal"))
}
} else {
Err(meta.error("unknown device variant attribute"))
}
})?;
}
Ok(result)
}
// Attribute parsing for fields
struct FieldAttrs {
skip: bool,
expr: Option<String>,
copy_upload: bool,
flatten: bool,
upload: bool,
spread: Option<String>,
}
fn parse_field_attrs(attrs: &[Attribute]) -> syn::Result<FieldAttrs> {
let mut result = FieldAttrs {
skip: false,
expr: None,
copy_upload: false,
flatten: false,
upload: false,
spread: None,
};
for attr in attrs {
if !attr.path().is_ident("device") {
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("skip") {
result.skip = true;
Ok(())
} else if meta.path.is_ident("expr") {
let value = meta.value()?;
let lit: Lit = value.parse()?;
if let Lit::Str(s) = lit {
result.expr = Some(s.value());
Ok(())
} else {
Err(meta.error("expected string literal"))
}
} else if meta.path.is_ident("copy_upload") {
result.copy_upload = true;
Ok(())
} else if meta.path.is_ident("flatten") {
result.flatten = true;
Ok(())
} else if meta.path.is_ident("upload") {
result.upload = true;
Ok(())
} else if meta.path.is_ident("spread") {
let value = meta.value()?;
let lit: Lit = value.parse()?;
if let Lit::Str(s) = lit {
result.spread = Some(s.value());
Ok(())
} else {
Err(meta.error("expected string literal"))
}
} else {
Err(meta.error("unknown device attribute"))
}
})?;
}
Ok(result)
}
// Container-level name attribute
fn get_device_name(attrs: &[Attribute], host_name: &Ident) -> syn::Result<Ident> {
for attr in attrs {
if !attr.path().is_ident("device") {
continue;
}
let mut name = None;
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("name") {
let value = meta.value()?;
let lit: Lit = value.parse()?;
if let Lit::Str(s) = lit {
name = Some(format_ident!("{}", s.value()));
Ok(())
} else {
Err(meta.error("expected string literal"))
}
} else {
Ok(())
}
})?;
if let Some(n) = name {
return Ok(n);
}
}
Ok(format_ident!("Device{}", host_name))
}
// Type classification
enum FieldClass {
VecCopy(Type),
VecUploadable(Type),
Option(Type),
Arc(Type),
Plain,
}
fn classify_type(ty: &Type) -> FieldClass {
if let Some(inner) = extract_generic_arg(ty, "Vec") {
if is_copy_primitive(&inner) {
FieldClass::VecCopy(inner)
} else {
FieldClass::VecUploadable(inner)
}
} else if let Some(inner) = extract_generic_arg(ty, "Option") {
FieldClass::Option(inner)
} else if let Some(inner) = extract_generic_arg(ty, "Arc") {
FieldClass::Arc(inner)
} else {
FieldClass::Plain
}
}
fn extract_generic_arg(ty: &Type, wrapper: &str) -> Option<Type> {
if let Type::Path(type_path) = ty {
let seg = type_path.path.segments.last()?;
if seg.ident != wrapper {
return None;
}
if let PathArguments::AngleBracketed(args) = &seg.arguments {
if let Some(GenericArgument::Type(inner)) = args.args.first() {
return Some(inner.clone());
}
}
}
None
}
fn is_copy_primitive(ty: &Type) -> bool {
if let Type::Path(type_path) = ty {
if let Some(seg) = type_path.path.segments.last() {
let name = seg.ident.to_string();
return matches!(
name.as_str(),
"f32"
| "f64"
| "u8"
| "u16"
| "u32"
| "u64"
| "i8"
| "i16"
| "i32"
| "i64"
| "usize"
| "isize"
| "bool"
| "Float"
);
}
}
false
}

View file

@ -50,7 +50,6 @@ impl<T> Ptr<T> {
unsafe { &*self.ptr } unsafe { &*self.ptr }
} }
/// Get as Option - safe for optional fields
#[inline(always)] #[inline(always)]
pub fn get<'a>(self) -> Option<&'a T> { pub fn get<'a>(self) -> Option<&'a T> {
if self.is_null() { if self.is_null() {
@ -141,3 +140,12 @@ impl<T> From<*const T> for Ptr<T> {
Self { ptr } Self { ptr }
} }
} }
impl<T> From<Option<&T>> for Ptr<T> {
fn from(opt: Option<&T>) -> Self {
match opt {
Some(r) => Ptr::from(r),
None => Ptr::null(),
}
}
}

View file

@ -1,20 +1,20 @@
use crate::core::geometry::{ use crate::core::geometry::{
Bounds2f, Frame, Point2f, Point2i, Point3f, Vector2f, Vector2i, Vector3f, VectorLike, Bounds2f, Frame, Point2f, Point2i, Point3f, Vector2f, Vector2i, Vector3f, VectorLike,
}; };
use crate::utils::containers::DeviceArray2D; use crate::core::image::Image;
use crate::utils::find_interval; use crate::utils::find_interval;
use crate::utils::math::{ use crate::utils::math::{
catmull_rom_weights, clamp, difference_of_products, evaluate_polynomial, lerp, logistic, catmull_rom_weights, clamp, difference_of_products, evaluate_polynomial, lerp, logistic,
newton_bisection, safe_sqrt, square, sum_of_products, newton_bisection, safe_sqrt, square, sum_of_products,
}; };
use crate::utils::ptr::Ptr;
use crate::utils::rng::Rng; use crate::utils::rng::Rng;
use crate::{gvec, gvec_from_slice, gvec_with_capacity, Array2D, GVec, Ptr};
use crate::{Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4}; use crate::{Float, INV_2_PI, INV_4_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4};
use num_traits::Float as NumFloat; use num_traits::Float as NumFloat;
use num_traits::Num; use num_traits::Num;
#[cfg(feature = "cpu_debug")] #[cfg(feature = "cpu_debug")]
use crate::{RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS, check_rare}; use crate::{check_rare, RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS};
pub fn linear_pdf<T>(x: T, a: T, b: T) -> T pub fn linear_pdf<T>(x: T, a: T, b: T) -> T
where where
@ -703,132 +703,228 @@ pub struct PLSample {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Clone)]
pub struct DevicePiecewiseConstant1D { pub struct PiecewiseConstant1D {
pub func: Ptr<Float>, pub func: GVec<Float>,
pub cdf: Ptr<Float>, pub cdf: GVec<Float>,
pub min: Float, pub min: Float,
pub max: Float, pub max: Float,
pub n: u32,
pub func_integral: Float, pub func_integral: Float,
} }
unsafe impl Send for DevicePiecewiseConstant1D {} unsafe impl Send for PiecewiseConstant1D {}
unsafe impl Sync for DevicePiecewiseConstant1D {} unsafe impl Sync for PiecewiseConstant1D {}
impl DevicePiecewiseConstant1D { impl PiecewiseConstant1D {
pub fn new(f: &[Float]) -> Self {
Self::new_with_bounds(f, 0.0, 1.0)
}
pub fn new_with_bounds(f: &[Float], min: Float, max: Float) -> Self {
let n = f.len();
let mut cdf = gvec_with_capacity(n + 1);
cdf.push(0.0);
let delta = (max - min) / n as Float;
for i in 0..n {
cdf.push(cdf[i] + f[i] * delta);
}
let func_integral = cdf[n];
if func_integral > 0.0 {
for c in &mut cdf {
*c /= func_integral;
}
}
Self {
func: gvec_from_slice(f),
cdf,
min,
max,
func_integral,
}
}
pub fn from_func<F>(f: F, min: Float, max: Float, n: usize) -> Self
where
F: Fn(Float) -> Float,
{
let delta = (max - min) / n as Float;
let delta = (max - min) / n as Float;
let mut values = gvec_with_capacity(n);
for i in 0..n {
values.push(f(min + (i as Float + 0.5) * delta));
}
Self::new_with_bounds(&values, min, max)
}
pub fn n(&self) -> usize {
self.func.len()
}
pub fn func(&self) -> &[Float] {
&self.func
}
pub fn cdf(&self) -> &[Float] {
&self.cdf
}
pub fn integral(&self) -> Float { pub fn integral(&self) -> Float {
self.func_integral self.func_integral
} }
pub fn size(&self) -> u32 { pub fn find_interval(&self, u: Float) -> usize {
self.n let n = self.func.len();
} let mut size = n;
let mut first = 0usize;
fn find_interval(&self, u: Float) -> usize {
let mut size = self.n as usize;
let mut first = 0;
while size > 0 { while size > 0 {
let half = size >> 1; let half = size >> 1;
let middle = first + half; let middle = first + half;
if self.cdf[middle] <= u {
let cdf_val = unsafe { *self.cdf.add(middle) };
if cdf_val <= u {
first = middle + 1; first = middle + 1;
size -= half + 1; size -= half + 1;
} else { } else {
size = half; size = half;
} }
} }
first.saturating_sub(1).min(n - 1)
(first - 1).clamp(0, self.n as usize - 1)
} }
pub fn sample(&self, u: Float) -> (Float, Float, usize) { pub fn sample(&self, u: Float) -> (Float, Float, usize) {
// Find offset via binary search on CDF
let offset = self.find_interval(u); let offset = self.find_interval(u);
let cdf_offset = self.cdf[offset];
let cdf_offset = unsafe { *self.cdf.add(offset) }; let cdf_next = self.cdf[offset + 1];
let cdf_next = unsafe { *self.cdf.add(offset + 1) };
let du = if cdf_next - cdf_offset > 0.0 { let du = if cdf_next - cdf_offset > 0.0 {
(u - cdf_offset) / (cdf_next - cdf_offset) (u - cdf_offset) / (cdf_next - cdf_offset)
} else { } else {
0.0 0.0
}; };
let n = self.func.len();
let delta = (self.max - self.min) / self.n as Float; let delta = (self.max - self.min) / n as Float;
let x = self.min + (offset as Float + du) * delta; let x = self.min + (offset as Float + du) * delta;
let pdf = if self.func_integral > 0.0 { let pdf = if self.func_integral > 0.0 {
(unsafe { *self.func.add(offset) }) / self.func_integral self.func[offset] / self.func_integral
} else { } else {
0.0 0.0
}; };
(x, pdf, offset) (x, pdf, offset)
} }
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Clone)]
pub struct DevicePiecewiseConstant2D { pub struct PiecewiseConstant2D {
pub conditionals: Ptr<DevicePiecewiseConstant1D>, // Array of n_v conditionals pub conditionals: GVec<PiecewiseConstant1D>,
pub marginal: DevicePiecewiseConstant1D, pub marginal: PiecewiseConstant1D,
pub n_u: u32, pub n_u: u32,
pub n_v: u32, pub n_v: u32,
} }
unsafe impl Send for DevicePiecewiseConstant2D {} impl PiecewiseConstant2D {
unsafe impl Sync for DevicePiecewiseConstant2D {} pub fn new(data: &Array2D<Float>) -> Self {
Self::new_with_bounds(data, Bounds2f::unit())
}
impl DevicePiecewiseConstant2D { pub fn new_with_bounds(data: &Array2D<Float>, domain: Bounds2f) -> Self {
// pub fn resolution(&self) -> Point2i { Self::from_slice(data.as_slice(), data.x_size(), data.y_size(), domain)
// Point2i::new( }
// self.p_conditional_v[0u32].size() as i32,
// self.p_conditional_v[1u32].size() as i32,
// )
// }
pub fn integral(&self) -> f32 { pub fn from_slice(data: &[Float], n_u: usize, n_v: usize, domain: Bounds2f) -> Self {
assert_eq!(data.len(), n_u * n_v);
let mut conditionals = gvec_with_capacity(n_v);
let mut marginal_func = gvec_with_capacity(n_v);
for v in 0..n_v {
let row = data[v * n_u..(v + 1) * n_u].to_vec();
let conditional =
PiecewiseConstant1D::new_with_bounds(&row, domain.p_min.x(), domain.p_max.x());
marginal_func.push(conditional.integral());
conditionals.push(conditional);
}
let marginal = PiecewiseConstant1D::new_with_bounds(
&marginal_func,
domain.p_min.y(),
domain.p_max.y(),
);
Self {
conditionals,
marginal,
n_u: n_u.try_into().unwrap(),
n_v: n_v.try_into().unwrap(),
}
}
pub fn from_image(image: &Image) -> Self {
let res = image.resolution();
let n_u = res.x() as usize;
let n_v = res.y() as usize;
let mut data = gvec_with_capacity(n_u * n_v);
for v in 0..n_v {
for u in 0..n_u {
data.push(image.get_channels_average(Point2i::new(u as i32, v as i32)));
}
}
PiecewiseConstant2D::from_slice(&data, n_u, n_v, Bounds2f::unit())
}
pub fn integral(&self) -> Float {
self.marginal.integral() self.marginal.integral()
} }
pub fn sample(&self, u: Point2f) -> (Point2f, f32, Point2i) { pub fn pdf(&self, p: Point2f) -> Float {
let (d1, pdf1, off_y) = self.marginal.sample(u.y()); let u_offset = ((p.x() * self.n_u as Float) as usize).min(self.n_u as usize - 1);
let (d0, pdf0, off_x) = (unsafe { self.conditionals.add(off_y) }).sample(u.x()); let v_offset = ((p.y() * self.n_v as Float) as usize).min(self.n_v as usize - 1);
let pdf = pdf0 * pdf1; let conditional = unsafe { &*self.conditionals.as_ptr().add(v_offset) };
let offset = Point2i::new(off_x as i32, off_y as i32); let func_val = unsafe { *conditional.func.as_ptr().add(u_offset) };
(Point2f::new(d0, d1), pdf, offset) func_val / self.integral()
} }
pub fn pdf(&self, p: Point2f) -> Float { pub fn sample(&self, u: Point2f) -> (Point2f, Float, Point2i) {
// Find which row let (d1, pdf1, off_y) = self.marginal.sample(u.y());
// let delta_v = 1.0 / self.n_v as Float; let (d0, pdf0, off_x) = self.conditionals[off_y].sample(u.x());
let v_offset = ((p.y() * self.n_v as Float) as usize).min(self.n_v as usize - 1); let pdf = pdf0 * pdf1;
(
let conditional = unsafe { &*self.conditionals.add(v_offset) }; Point2f::new(d0, d1),
pdf,
// Find which column Point2i::new(off_x as i32, off_y as i32),
// let delta_u = 1.0 / self.n_u as Float; )
let u_offset = ((p.x() * self.n_u as Float) as usize).min(self.n_u as usize - 1);
let func_val = unsafe { *conditional.func.add(u_offset) };
func_val / self.marginal.func_integral
} }
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Clone)]
pub struct DeviceSummedAreaTable { pub struct SummedAreaTable {
pub sum: DeviceArray2D<f64>, pub sum: Array2D<f64>,
} }
impl DeviceSummedAreaTable { impl SummedAreaTable {
// pub fn new(values: &Array2D<Float>) -> Self {
let width = values.x_size() as i32;
let height = values.y_size() as i32;
let mut sum = Array2D::<f64>::new_dims(width, height);
sum[(0, 0)] = values[(0, 0)] as f64;
for x in 1..width {
sum[(x, 0)] = values[(x, 0)] as f64 + sum[(x - 1, 0)];
}
for y in 1..height {
sum[(0, y)] = values[(0, y)] as f64 + sum[(0, y - 1)];
}
for y in 1..height {
for x in 1..width {
sum[(x, y)] =
values[(x, y)] as f64 + sum[(x - 1, y)] + sum[(x, y - 1)] - sum[(x - 1, y - 1)];
}
}
Self { sum }
}
pub fn integral(&self, extent: Bounds2f) -> Float { pub fn integral(&self, extent: Bounds2f) -> Float {
let s = self.lookup(extent.p_max.x(), extent.p_max.y()) let s = self.lookup(extent.p_max.x(), extent.p_max.y())
- self.lookup(extent.p_min.x(), extent.p_max.y()) - self.lookup(extent.p_min.x(), extent.p_max.y())
@ -874,13 +970,18 @@ impl DeviceSummedAreaTable {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Clone)]
pub struct DeviceWindowedPiecewiseConstant2D { pub struct WindowedPiecewiseConstant2D {
pub sat: DeviceSummedAreaTable, pub sat: SummedAreaTable,
pub func: DeviceArray2D<Float>, pub func: Array2D<Float>,
} }
impl DeviceWindowedPiecewiseConstant2D { impl WindowedPiecewiseConstant2D {
pub fn new(func: Array2D<Float>) -> Self {
let sat = SummedAreaTable::new(&func);
Self { sat, func }
}
pub fn sample(&self, u: Point2f, b: Bounds2f) -> Option<(Point2f, Float)> { pub fn sample(&self, u: Point2f, b: Bounds2f) -> Option<(Point2f, Float)> {
let b_int = self.sat.integral(b); let b_int = self.sat.integral(b);
if b_int == 0.0 { if b_int == 0.0 {
@ -894,7 +995,8 @@ impl DeviceWindowedPiecewiseConstant2D {
}; };
let nx = self.func.x_size(); let nx = self.func.x_size();
let px_val = Self::sample_bisection(px, u.x(), b.p_min.x(), b.p_max.x(), nx); let px_val =
Self::sample_bisection(px, u.x(), b.p_min.x(), b.p_max.x(), nx.try_into().unwrap());
let nx_f = nx as Float; let nx_f = nx as Float;
@ -922,7 +1024,8 @@ impl DeviceWindowedPiecewiseConstant2D {
}; };
let ny = self.func.y_size(); let ny = self.func.y_size();
let py_val = Self::sample_bisection(py, u.y(), b.p_min.y(), b.p_max.y(), ny); let py_val =
Self::sample_bisection(py, u.y(), b.p_min.y(), b.p_max.y(), ny.try_into().unwrap());
let p = Point2f::new(px_val, py_val); let p = Point2f::new(px_val, py_val);
@ -990,81 +1093,250 @@ pub struct Bin {
} }
#[repr(C)] #[repr(C)]
#[derive(Copy, Debug, Clone)] #[derive(Debug, Clone)]
pub struct AliasTable { pub struct AliasTable {
pub bins: Ptr<Bin>, pub bins: GVec<Bin>,
pub size: u32,
} }
unsafe impl Send for AliasTable {} unsafe impl Send for AliasTable {}
unsafe impl Sync for AliasTable {} unsafe impl Sync for AliasTable {}
impl AliasTable { impl AliasTable {
#[inline(always)] pub fn new(weights: &[Float]) -> Self {
fn bin(&self, idx: u32) -> Ptr<Bin> { let n = weights.len();
unsafe { self.bins.add(idx as usize) } if n == 0 {
return Self { bins: gvec() };
}
let sum: f64 = weights.iter().map(|&w| w as f64).sum();
assert!(sum > 0.0, "Sum of weights must be positive");
let mut bins = gvec_with_capacity(n);
for &w in weights {
bins.push(Bin {
p: (w as f64 / sum) as Float,
q: 0.0,
alias: 0,
});
}
struct Outcome {
p_hat: f64,
index: usize,
}
let mut under = gvec_with_capacity(n);
let mut over = gvec_with_capacity(n);
for (i, bin) in bins.iter().enumerate() {
let p_hat = (bin.p as f64) * (n as f64);
if p_hat < 1.0 {
under.push(Outcome { p_hat, index: i });
} else {
over.push(Outcome { p_hat, index: i });
}
}
while !under.is_empty() && !over.is_empty() {
let un = under.pop().unwrap();
let ov = over.pop().unwrap();
bins[un.index].q = un.p_hat as Float;
bins[un.index].alias = ov.index as u32;
let p_excess = un.p_hat + ov.p_hat - 1.0;
if p_excess < 1.0 {
under.push(Outcome {
p_hat: p_excess,
index: ov.index,
});
} else {
over.push(Outcome {
p_hat: p_excess,
index: ov.index,
});
}
}
while let Some(ov) = over.pop() {
bins[ov.index].q = 1.0;
bins[ov.index].alias = ov.index as u32;
}
while let Some(un) = under.pop() {
bins[un.index].q = 1.0;
bins[un.index].alias = un.index as u32;
}
Self { bins }
} }
pub fn size(&self) -> u32 { pub fn size(&self) -> u32 {
self.size self.bins.len() as u32
}
pub fn is_empty(&self) -> bool {
self.bins.is_empty()
} }
pub fn pmf(&self, index: u32) -> Float { pub fn pmf(&self, index: u32) -> Float {
if index >= self.size() { if index >= self.size() {
return 0.0; return 0.0;
} }
self.bin(index).p self.bins[index as usize].p
} }
pub fn sample(&self, u: Float) -> (u32, Float, Float) { pub fn sample(&self, u: Float) -> (u32, Float, Float) {
if self.size == 0 { if self.bins.is_empty() {
return (0, 0.0, 0.0); return (0, 0.0, 0.0);
} }
let n = self.size as Float; let n = self.bins.len() as Float;
let val = u * n; let val = u * n;
let offset = (val.min(n - 1.0)) as u32; let offset = (val.min(n - 1.0)) as u32;
let up = (val - offset as Float).min(ONE_MINUS_EPSILON);
let up = (val - (offset as Float)).min(ONE_MINUS_EPSILON); let bin = &self.bins[offset as usize];
let bin = self.bin(offset);
if up < bin.q { if up < bin.q {
debug_assert!(bin.p > 0.0);
let pmf = bin.p; let pmf = bin.p;
let u_remapped = (up / bin.q).min(ONE_MINUS_EPSILON); let u_remapped = (up / bin.q).min(ONE_MINUS_EPSILON);
(offset, pmf, u_remapped) (offset, pmf, u_remapped)
} else { } else {
let alias_idx = bin.alias; let alias_idx = bin.alias;
let alias_p = self.bins[alias_idx as usize].p;
let alias_p = self.bin(alias_idx).p;
debug_assert!(alias_p > 0.0);
let u_remapped = ((up - bin.q) / (1.0 - bin.q)).min(ONE_MINUS_EPSILON); let u_remapped = ((up - bin.q) / (1.0 - bin.q)).min(ONE_MINUS_EPSILON);
(alias_idx, alias_p, u_remapped) (alias_idx, alias_p, u_remapped)
} }
} }
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Clone)]
pub struct PiecewiseLinear2D<const N: usize> { pub struct PiecewiseLinear2D<const N: usize> {
pub size: Vector2i, pub size: Vector2i,
pub inv_patch_size: Vector2f, pub inv_patch_size: Vector2f,
pub param_size: [u32; N], pub param_size: [u32; N],
pub param_strides: [u32; N], pub param_strides: [u32; N],
pub param_values: [Ptr<Float>; N], pub param_values: [GVec<Float>; N],
pub data: Ptr<Float>, pub data: GVec<Float>,
pub marginal_cdf: Ptr<Float>, pub marginal_cdf: GVec<Float>,
pub conditional_cdf: Ptr<Float>, pub conditional_cdf: GVec<Float>,
} }
impl<const N: usize> PiecewiseLinear2D<N> { impl<const N: usize> PiecewiseLinear2D<N> {
pub fn new(
data: &[Float],
x_size: i32,
y_size: i32,
param_res: [usize; N],
param_values: [&[Float]; N],
normalize: bool,
build_cdf: bool,
) -> Self {
if build_cdf && !normalize {
panic!("PiecewiseLinear2D: build_cdf implies normalize=true");
}
let size = Vector2i::new(x_size, y_size);
let inv_patch_size =
Vector2f::new(1.0 / (x_size - 1) as Float, 1.0 / (y_size - 1) as Float);
let mut param_size = [0u32; N];
let mut param_strides = [0u32; N];
let owned_param_values: [GVec<Float>; N] =
core::array::from_fn(|i| gvec_from_slice(param_values[i]));
let mut slices: u32 = 1;
for i in (0..N).rev() {
assert!(param_res[i] >= 1, "Parameter resolution must be >= 1");
param_size[i] = param_res[i] as u32;
param_strides[i] = if param_res[i] > 1 { slices } else { 0 };
slices *= param_size[i];
}
let n_values = (x_size * y_size) as usize;
let mut new_data = gvec_with_capacity(slices as usize * n_values);
new_data.resize(slices as usize * n_values, 0.0);
let mut marginal_cdf = if build_cdf {
let mut v = gvec_with_capacity(slices as usize * y_size as usize);
v.resize(slices as usize * y_size as usize, 0.0);
v
} else {
gvec()
};
let mut conditional_cdf = if build_cdf {
let mut v = gvec_with_capacity(slices as usize * n_values);
v.resize(slices as usize * n_values, 0.0);
v
} else {
gvec()
};
let mut data_offset = 0;
for slice in 0..slices as usize {
let slice_offset = slice * n_values;
let current_data = &data[data_offset..data_offset + n_values];
let mut sum = 0.0_f64;
if normalize {
for y in 0..(y_size - 1) {
for x in 0..(x_size - 1) {
let i = (y * x_size + x) as usize;
let v00 = current_data[i] as f64;
let v10 = current_data[i + 1] as f64;
let v01 = current_data[i + x_size as usize] as f64;
let v11 = current_data[i + 1 + x_size as usize] as f64;
sum += 0.25 * (v00 + v10 + v01 + v11);
}
}
}
let normalization = if normalize && sum > 0.0 {
1.0 / sum as Float
} else {
1.0
};
for k in 0..n_values {
new_data[slice_offset + k] = current_data[k] * normalization;
}
if build_cdf {
let marginal_slice_offset = slice * y_size as usize;
for y in 0..y_size as usize {
let mut cdf_sum = 0.0;
let i_base = y * x_size as usize;
conditional_cdf[slice_offset + i_base] = 0.0;
for x in 0..(x_size - 1) as usize {
let i = i_base + x;
cdf_sum +=
0.5 * (new_data[slice_offset + i] + new_data[slice_offset + i + 1]);
conditional_cdf[slice_offset + i + 1] = cdf_sum;
}
}
marginal_cdf[marginal_slice_offset] = 0.0;
let mut marginal_sum = 0.0;
for y in 0..(y_size - 1) as usize {
let cdf1 = conditional_cdf[slice_offset + (y + 1) * x_size as usize - 1];
let cdf2 = conditional_cdf[slice_offset + (y + 2) * x_size as usize - 1];
marginal_sum += 0.5 * (cdf1 + cdf2);
marginal_cdf[marginal_slice_offset + y + 1] = marginal_sum;
}
}
data_offset += n_values;
}
Self {
size,
inv_patch_size,
param_size,
param_strides,
data: new_data,
marginal_cdf,
conditional_cdf,
param_values: owned_param_values,
}
}
pub fn sample(&self, mut sample: Point2f, params: [Float; N]) -> PLSample { pub fn sample(&self, mut sample: Point2f, params: [Float; N]) -> PLSample {
sample = Point2f::new( sample = Point2f::new(
sample.x().clamp(0.0, ONE_MINUS_EPSILON), sample.x().clamp(0.0, ONE_MINUS_EPSILON),
@ -1079,7 +1351,7 @@ impl<const N: usize> PiecewiseLinear2D<N> {
let conditional_offset = slice_offset * conditional_size; let conditional_offset = slice_offset * conditional_size;
let fetch_marginal = |idx: u32| { let fetch_marginal = |idx: u32| {
self.lookup( self.lookup(
self.marginal_cdf, &self.marginal_cdf,
marginal_offset + idx, marginal_offset + idx,
marginal_size, marginal_size,
&param_weights, &param_weights,
@ -1089,13 +1361,13 @@ impl<const N: usize> PiecewiseLinear2D<N> {
let marginal_cdf_row = fetch_marginal(row); let marginal_cdf_row = fetch_marginal(row);
sample[1] -= marginal_cdf_row; sample[1] -= marginal_cdf_row;
let r0 = self.lookup( let r0 = self.lookup(
self.conditional_cdf, &self.conditional_cdf,
conditional_offset + (row + 1) * self.size.x() as u32 - 1, conditional_offset + (row + 1) * self.size.x() as u32 - 1,
conditional_size, conditional_size,
&param_weights, &param_weights,
); );
let r1 = self.lookup( let r1 = self.lookup(
self.conditional_cdf, &self.conditional_cdf,
conditional_offset + (row + 2) * self.size.x() as u32 - 1, conditional_offset + (row + 2) * self.size.x() as u32 - 1,
conditional_size, conditional_size,
&param_weights, &param_weights,
@ -1112,13 +1384,13 @@ impl<const N: usize> PiecewiseLinear2D<N> {
let conditional_row_offset = conditional_offset + row * self.size.x() as u32; let conditional_row_offset = conditional_offset + row * self.size.x() as u32;
let fetch_conditional = |idx: u32| { let fetch_conditional = |idx: u32| {
let v0 = self.lookup( let v0 = self.lookup(
self.conditional_cdf, &self.conditional_cdf,
conditional_row_offset + idx, conditional_row_offset + idx,
conditional_size, conditional_size,
&param_weights, &param_weights,
); );
let v1 = self.lookup( let v1 = self.lookup(
self.conditional_cdf, &self.conditional_cdf,
conditional_row_offset + idx + self.size.x() as u32, conditional_row_offset + idx + self.size.x() as u32,
conditional_size, conditional_size,
&param_weights, &param_weights,
@ -1130,16 +1402,16 @@ impl<const N: usize> PiecewiseLinear2D<N> {
}); });
sample[0] -= fetch_conditional(col); sample[0] -= fetch_conditional(col);
let offset = conditional_row_offset + col; let offset = conditional_row_offset + col;
let v00 = self.lookup(self.data, offset, slice_size, &param_weights); let v00 = self.lookup(&self.data, offset, slice_size, &param_weights);
let v10 = self.lookup(self.data, offset + 1, slice_size, &param_weights); let v10 = self.lookup(&self.data, offset + 1, slice_size, &param_weights);
let v01 = self.lookup( let v01 = self.lookup(
self.data, &self.data,
offset + self.size.x() as u32, offset + self.size.x() as u32,
slice_size, slice_size,
&param_weights, &param_weights,
); );
let v11 = self.lookup( let v11 = self.lookup(
self.data, &self.data,
offset + self.size.x() as u32 + 1, offset + self.size.x() as u32 + 1,
slice_size, slice_size,
&param_weights, &param_weights,
@ -1176,16 +1448,16 @@ impl<const N: usize> PiecewiseLinear2D<N> {
let frac = Point2f::new(p.x() - col as Float, p.y() - row as Float); let frac = Point2f::new(p.x() - col as Float, p.y() - row as Float);
let slice_size = (self.size.x() * self.size.y()) as u32; let slice_size = (self.size.x() * self.size.y()) as u32;
let offset = slice_offset * slice_size + (row * self.size.x() + col) as u32; let offset = slice_offset * slice_size + (row * self.size.x() + col) as u32;
let v00 = self.lookup(self.data, offset, slice_size, &param_weights); let v00 = self.lookup(&self.data, offset, slice_size, &param_weights);
let v10 = self.lookup(self.data, offset + 1, slice_size, &param_weights); let v10 = self.lookup(&self.data, offset + 1, slice_size, &param_weights);
let v01 = self.lookup( let v01 = self.lookup(
self.data, &self.data,
offset + self.size.x() as u32, offset + self.size.x() as u32,
slice_size, slice_size,
&param_weights, &param_weights,
); );
let v11 = self.lookup( let v11 = self.lookup(
self.data, &self.data,
offset + self.size.x() as u32 + 1, offset + self.size.x() as u32 + 1,
slice_size, slice_size,
&param_weights, &param_weights,
@ -1199,26 +1471,26 @@ impl<const N: usize> PiecewiseLinear2D<N> {
u[0] = w1.x() * (c0 + 0.5 * w1.x() * (c1 - c0)); u[0] = w1.x() * (c0 + 0.5 * w1.x() * (c1 - c0));
let conditional_row_offset = slice_offset * slice_size + (row * self.size.x()) as u32; let conditional_row_offset = slice_offset * slice_size + (row * self.size.x()) as u32;
let v0 = self.lookup( let v0 = self.lookup(
self.conditional_cdf, &self.conditional_cdf,
conditional_row_offset + col as u32, conditional_row_offset + col as u32,
slice_size, slice_size,
&param_weights, &param_weights,
); );
let v1 = self.lookup( let v1 = self.lookup(
self.conditional_cdf, &self.conditional_cdf,
conditional_row_offset + col as u32 + self.size.x() as u32, conditional_row_offset + col as u32 + self.size.x() as u32,
slice_size, slice_size,
&param_weights, &param_weights,
); );
u[0] += (1.0 - u.y()) * v0 + u.y() * v1; u[0] += (1.0 - u.y()) * v0 + u.y() * v1;
let r0 = self.lookup( let r0 = self.lookup(
self.conditional_cdf, &self.conditional_cdf,
conditional_row_offset + self.size.x() as u32 - 1, conditional_row_offset + self.size.x() as u32 - 1,
slice_size, slice_size,
&param_weights, &param_weights,
); );
let r1 = self.lookup( let r1 = self.lookup(
self.conditional_cdf, &self.conditional_cdf,
conditional_row_offset + self.size.x() as u32 * 2 - 1, conditional_row_offset + self.size.x() as u32 * 2 - 1,
slice_size, slice_size,
&param_weights, &param_weights,
@ -1227,7 +1499,7 @@ impl<const N: usize> PiecewiseLinear2D<N> {
u[1] = w1.y() * (r0 + 0.5 * w1.y() * (r1 - r0)); u[1] = w1.y() * (r0 + 0.5 * w1.y() * (r1 - r0));
let marginal_offset = slice_offset * self.size.y() as u32 + row as u32; let marginal_offset = slice_offset * self.size.y() as u32 + row as u32;
u[1] += self.lookup( u[1] += self.lookup(
self.marginal_cdf, &self.marginal_cdf,
marginal_offset, marginal_offset,
self.size.y() as u32, self.size.y() as u32,
&param_weights, &param_weights,
@ -1251,16 +1523,16 @@ impl<const N: usize> PiecewiseLinear2D<N> {
let w0 = Point2f::new(1.0 - w1.x(), 1.0 - w1.y()); let w0 = Point2f::new(1.0 - w1.x(), 1.0 - w1.y());
let slice_size = (self.size.x() * self.size.y()) as u32; let slice_size = (self.size.x() * self.size.y()) as u32;
let offset = slice_offset * slice_size + (row * self.size.x() + col) as u32; let offset = slice_offset * slice_size + (row * self.size.x() + col) as u32;
let v00 = self.lookup(self.data, offset, slice_size, &param_weights); let v00 = self.lookup(&self.data, offset, slice_size, &param_weights);
let v10 = self.lookup(self.data, offset + 1, slice_size, &param_weights); let v10 = self.lookup(&self.data, offset + 1, slice_size, &param_weights);
let v01 = self.lookup( let v01 = self.lookup(
self.data, &self.data,
offset + self.size.x() as u32, offset + self.size.x() as u32,
slice_size, slice_size,
&param_weights, &param_weights,
); );
let v11 = self.lookup( let v11 = self.lookup(
self.data, &self.data,
offset + self.size.x() as u32 + 1, offset + self.size.x() as u32 + 1,
slice_size, slice_size,
&param_weights, &param_weights,
@ -1272,8 +1544,7 @@ impl<const N: usize> PiecewiseLinear2D<N> {
#[inline(always)] #[inline(always)]
fn get_param_value(&self, dim: usize, idx: usize) -> Float { fn get_param_value(&self, dim: usize, idx: usize) -> Float {
// Safety: Bounds checking against param_size ensures this is valid unsafe { *self.param_values[dim].as_ptr().add(idx) }
unsafe { *self.param_values[dim].add(idx) }
} }
fn get_slice_info(&self, params: [Float; N]) -> (u32, [(Float, Float); N]) { fn get_slice_info(&self, params: [Float; N]) -> (u32, [(Float, Float); N]) {
@ -1313,7 +1584,7 @@ impl<const N: usize> PiecewiseLinear2D<N> {
fn lookup( fn lookup(
&self, &self,
data: Ptr<Float>, data: &GVec<Float>,
i0: u32, i0: u32,
size: u32, size: u32,
param_weight: &[(Float, Float); N], param_weight: &[(Float, Float); N],
@ -1335,7 +1606,7 @@ impl<const N: usize> PiecewiseLinear2D<N> {
current_mask >>= 1; current_mask >>= 1;
} }
let idx = (i0 + offset) as usize; let idx = (i0 + offset) as usize;
let val = unsafe { *data.add(idx) }; let val = unsafe { *data.as_ptr().add(idx) };
result += weight * val; result += weight * val;
} }
result result

View file

@ -1,3 +0,0 @@
pub mod perspective;
pub mod realistic;
pub mod spherical;

View file

@ -1 +0,0 @@

View file

@ -1,100 +0,0 @@
use crate::core::image::Image;
use shared::cameras::{EXIT_PUPIL_SAMPLES, LensElementInterface, RealisticCamera};
use shared::core::camera::CameraBase;
use shared::core::geometry::{Bounds2f, Point2f};
use shared::utils::math::square;
use shared::{Float, Ptr};
use std::sync::Arc;
#[derive(Clone)]
struct RealisticCameraData {
aperture_image: Arc<Image>,
element_interfaces: Vec<LensElementInterface>,
}
#[derive(Clone)]
pub struct RealisticCameraHost {
device: RealisticCamera,
data: RealisticCameraData,
}
impl RealisticCameraHost {
pub fn device(&self) -> RealisticCamera {
self.device
}
pub fn new(
base: CameraBase,
lens_params: &[Float],
focus_distance: Float,
set_aperture_diameter: Float,
aperture_image: Arc<Image>,
) -> Self {
let film_ptr = base.film;
if film_ptr.is_null() {
panic!("Camera must have a film");
}
let film = &*film_ptr;
let aspect = film.full_resolution().x() as Float / film.full_resolution().y() as Float;
let diagonal = film.diagonal();
let x = (square(diagonal) / (1.0 + square(diagonal))).sqrt();
let y = x * aspect;
let physical_extent =
Bounds2f::from_points(Point2f::new(-x / 2., -y / 2.), Point2f::new(x / 2., y / 2.));
let mut element_interfaces: Vec<LensElementInterface> = Vec::new();
for i in (0..lens_params.len()).step_by(4) {
let curvature_radius = lens_params[i] / 1000.0;
let thickness = lens_params[i + 1] / 1000.0;
let eta = lens_params[i + 2];
let mut aperture_diameter = lens_params[i + 3] / 1000.0;
if curvature_radius == 0.0 {
aperture_diameter /= 1000.0;
if set_aperture_diameter > aperture_diameter {
println!("Aperture is larger than possible")
} else {
aperture_diameter = set_aperture_diameter;
}
}
let el_int = LensElementInterface {
curvature_radius,
thickness,
eta,
aperture_radius: aperture_diameter / 2.0,
};
element_interfaces.push(el_int);
}
let half_diag = film.diagonal() / 2.0;
let mut exit_pupil_bounds = [Bounds2f::default(); EXIT_PUPIL_SAMPLES];
for i in 0..EXIT_PUPIL_SAMPLES {
let r0 = (i as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
let r1 = ((i + 1) as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
exit_pupil_bounds[i] =
RealisticCamera::compute_exit_pupil_bounds(&element_interfaces, r0, r1);
}
let n_elements = element_interfaces.len();
let data = RealisticCameraData {
element_interfaces: element_interfaces.clone(),
aperture_image: aperture_image.clone(),
};
let device = RealisticCamera {
base,
focus_distance,
element_interfaces: Ptr::from(element_interfaces.as_ptr()),
n_elements,
physical_extent,
set_aperture_diameter,
aperture_image: Ptr::from(aperture_image.device()),
exit_pupil_bounds,
};
Self { device, data }
}
}

View file

@ -1 +0,0 @@

View file

@ -1,5 +1,5 @@
use rayon::prelude::*; use rayon::prelude::*;
use shared::core::aggregates::{{DeviceBVHAggregate, LinearBVHNode}; use shared::core::aggregates::{BVHAggregate, 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;
@ -307,7 +307,7 @@ impl<P: PrimitiveTrait + Clone + Send + Sync> BVHAggregate<P> {
pub fn build_hlbvh( pub fn build_hlbvh(
bvh_primitives: &[BVHPrimitiveInfo], bvh_primitives: &[BVHPrimitiveInfo],
total_nodes: &AtomicUsize, total_nodes: &AtomicUsize,
original_primitives: &[P], _original_primitives: &[P],
max_prims_in_node: usize, max_prims_in_node: usize,
) -> Box<BVHBuildNode> { ) -> Box<BVHBuildNode> {
let bounds = bvh_primitives let bounds = bvh_primitives

View file

@ -1,9 +1,8 @@
use crate::cameras::realistic::RealisticCameraHost;
use crate::core::image::ImageMetadata; use crate::core::image::ImageMetadata;
use crate::core::image::{Image, ImageIO}; 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::utils::{Arena, FileLoc, ParameterDictionary}; use crate::{Arena, FileLoc, ParameterDictionary};
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};
@ -13,8 +12,7 @@ use shared::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f, Vector3f};
use shared::core::image::PixelFormat; use shared::core::image::PixelFormat;
use shared::core::medium::Medium; use shared::core::medium::Medium;
use shared::utils::math::square; use shared::utils::math::square;
use shared::Ptr; use shared::{Float, Ptr, PI};
use shared::{Float, PI};
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
@ -380,19 +378,19 @@ impl CameraFactory for Camera {
} }
} }
let camera = RealisticCameraHost::new( let camera = RealisticCamera::new(
base, base,
&lens_params, &lens_params,
focal_distance, focal_distance,
aperture_diameter, aperture_diameter,
Arc::from(aperture_image.unwrap()), Ptr::from(&*aperture_image.unwrap()),
); );
// arena.alloc(camera); arena.alloc(camera);
Ok(Camera::Realistic(camera.device())) Ok(Camera::Realistic(camera))
} }
"spherical" => { "spherical" => {
let full_res = film.full_resolution(); let _full_res = film.full_resolution();
let camera_params = let camera_params =
CameraBaseParameters::new(camera_transform, film, medium, params, loc)?; CameraBaseParameters::new(camera_transform, film, medium, params, loc)?;
let base = CameraBase::create(camera_params); let base = CameraBase::create(camera_params);

View file

@ -1,50 +1,33 @@
use crate::utils::read_float_file; use crate::utils::read_float_file;
use anyhow::Result; use anyhow::Result;
use shared::core::color::{Coeffs, RES, RGBToSpectrumTable}; use shared::core::color::{Coeffs, RES, RGBToSpectrumTable};
use shared::{Float, Ptr}; use shared::{Float, Ptr, gvec_from_slice};
use std::ops::Deref; use std::ops::Deref;
use std::path::Path; use std::path::Path;
pub struct RGBToSpectrumTableData { pub trait CreateRGBToSpectrumTable {
_z_nodes: Vec<Float>, fn from_data(z_nodes: &[Float], coeffs: &[Float]) -> Self;
_coeffs: Vec<Float>, fn load(base_dir: &Path, name: &str) -> Result<Self> where Self: Sized;
pub view: RGBToSpectrumTable,
} }
impl Deref for RGBToSpectrumTableData { impl CreateRGBToSpectrumTable for RGBToSpectrumTable {
type Target = RGBToSpectrumTable; fn new(z_nodes: &[Float], coeffs: &[Float]) -> Self {
fn deref(&self) -> &Self::Target {
&self.view
}
}
impl RGBToSpectrumTableData {
pub fn new(z_nodes: Vec<Float>, coeffs: Vec<Float>) -> Self {
eprintln!("z_nodes.len() = {}, coeffs.len() = {}", z_nodes.len(), coeffs.len());
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);
let view = RGBToSpectrumTable {
z_nodes: Ptr::from(z_nodes.as_ptr()),
coeffs: Ptr::from(coeffs.as_ptr() as *const Coeffs),
n_nodes: z_nodes.len() as u32,
};
Self { Self {
_z_nodes: z_nodes, z_nodes: gvec_from_slice(z_nodes),
_coeffs: coeffs, coeffs: gvec_from_slice(unsafe {
view, core::slice::from_raw_parts(
coeffs.as_ptr() as *const Coeffs,
coeffs.len() / 3,
)
}),
} }
} }
pub fn load(base_dir: &Path, name: &str) -> Result<Self> { fn load(base_dir: &Path, name: &str) -> Result<Self> {
let z_path = base_dir.join(format!("{}_znodes.dat", name)); let scale = read_float_file(base_dir.join(format!("{}_scale.dat", name)).to_str().unwrap())?;
let c_path = base_dir.join(format!("{}_coeffs.dat", name)); let coeffs = read_float_file(base_dir.join(format!("{}_coeffs.dat", name)).to_str().unwrap())?;
Ok(Self::new(&scale, &coeffs))
let z_nodes = read_float_file(&z_path.to_str().unwrap())?;
let coeffs = read_float_file(&c_path.to_str().unwrap())?;
Ok(Self::new(z_nodes, coeffs))
} }
} }

View file

@ -1,53 +1,62 @@
use crate::Arena; use crate::core::image::{HostImage, ImageChannelDesc, ImageChannelValues, ImageIO, ImageMetadata};
use crate::core::image::{Image, ImageChannelDesc, ImageChannelValues, ImageIO, ImageMetadata};
use crate::films::*; use crate::films::*;
use crate::spectra::data::get_named_spectrum; use crate::spectra::data::get_named_spectrum;
use crate::spectra::piecewise::PiecewiseLinearSpectrumBuffer; use anyhow::{anyhow, Result};
use anyhow::{Result, anyhow};
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use rayon::prelude::IntoParallelIterator; use rayon::prelude::IntoParallelIterator;
use shared::core::camera::CameraTransform; use shared::core::camera::CameraTransform;
use shared::core::color::{RGB, SRGB, XYZ, white_balance}; use shared::core::color::{white_balance, RGB, SRGB, XYZ};
use shared::core::film::{DevicePixelSensor, Film, FilmBase, GBufferFilm, RGBFilm, SpectralFilm}; use shared::core::film::{Film, FilmBase, GBufferFilm, PixelSensor, RGBFilm, SpectralFilm};
use shared::core::filter::{Filter, FilterTrait}; use shared::core::filter::{Filter, FilterTrait};
use shared::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i}; use shared::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i};
use shared::core::image::PixelFormat; use shared::core::image::PixelFormat;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::spectra::{RGBColorSpace, cie::SWATCHES_RAW}; use shared::spectra::{
use shared::utils::math::{SquareMatrix, linear_least_squares}; cie::SWATCHES_RAW, DenselySampledSpectrum, PiecewiseLinearSpectrum, RGBColorSpace,
};
use shared::utils::math::{linear_least_squares, SquareMatrix};
use shared::{Float, Ptr}; use shared::{Float, Ptr};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, LazyLock}; use std::sync::{Arc, LazyLock};
use crate::spectra::{ use crate::spectra::{get_spectra_context, CIE_X_DATA, CIE_Y_DATA, CIE_Z_DATA};
CIE_X_DATA, CIE_Y_DATA, CIE_Z_DATA, DenselySampledSpectrumBuffer, get_spectra_context, use crate::{Arena, FileLoc, ParameterDictionary};
};
use crate::utils::{FileLoc, ParameterDictionary};
const N_SWATCH_REFLECTANCES: usize = 24; const N_SWATCH_REFLECTANCES: usize = 24;
const SWATCH_REFLECTANCES: LazyLock<[Spectrum; N_SWATCH_REFLECTANCES]> = LazyLock::new(|| { const SWATCH_REFLECTANCES: LazyLock<[Spectrum; N_SWATCH_REFLECTANCES]> = LazyLock::new(|| {
std::array::from_fn(|i| { std::array::from_fn(|i| {
let raw_data = SWATCHES_RAW[i]; let raw_data = SWATCHES_RAW[i];
let pls = PiecewiseLinearSpectrumBuffer::from_interleaved(raw_data, false); let pls = PiecewiseLinearSpectrum::from_interleaved(raw_data, false);
Spectrum::Piecewise(pls.device) Spectrum::Piecewise(pls)
}) })
}); });
#[derive(Debug, Clone)] pub trait CreatePixelSensor: Sized {
struct SensorStorage { fn create(
r_bar: DenselySampledSpectrumBuffer, params: &ParameterDictionary,
g_bar: DenselySampledSpectrumBuffer, output_colorspace: Arc<RGBColorSpace>,
b_bar: DenselySampledSpectrumBuffer, exposure_time: Float,
loc: &FileLoc,
) -> Result<Self>;
fn new(
r: &Spectrum,
g: &Spectrum,
b: &Spectrum,
output_colorspace: Arc<RGBColorSpace>,
sensor_illum: Option<&Spectrum>,
imaging_ratio: Float,
) -> Self;
fn new_with_white_balance(
output_colorspace: &RGBColorSpace,
sensor_illum: Option<&Spectrum>,
imaging_ratio: Float,
) -> Self;
} }
#[derive(Debug, Clone)] impl CreatePixelSensor for PixelSensor {
pub struct PixelSensor { fn create(
device: DevicePixelSensor,
data: SensorStorage,
}
impl PixelSensor {
pub fn create(
params: &ParameterDictionary, params: &ParameterDictionary,
output_colorspace: Arc<RGBColorSpace>, output_colorspace: Arc<RGBColorSpace>,
exposure_time: Float, exposure_time: Float,
@ -65,9 +74,9 @@ impl PixelSensor {
let imaging_ratio = exposure_time * iso / 100.; let imaging_ratio = exposure_time * iso / 100.;
let d_illum = if white_balance_temp == 0. { let d_illum = if white_balance_temp == 0. {
DenselySampledSpectrumBuffer::generate_cie_d(6500.) DenselySampledSpectrum::generate_cie_d(6500.)
} else { } else {
DenselySampledSpectrumBuffer::generate_cie_d(white_balance_temp) DenselySampledSpectrum::generate_cie_d(white_balance_temp)
}; };
let sensor_illum: Option<Arc<Spectrum>> = if white_balance_temp != 0. { let sensor_illum: Option<Arc<Spectrum>> = if white_balance_temp != 0. {
@ -121,28 +130,25 @@ impl PixelSensor {
sensor_illum: Option<&Spectrum>, sensor_illum: Option<&Spectrum>,
imaging_ratio: Float, imaging_ratio: Float,
) -> Self { ) -> Self {
// As seen in usages of this constructos, sensor_illum can be null
// Going with the colorspace's own illuminant, but this might not be the right choice
// TODO: Test this
let illum: &Spectrum = match sensor_illum { let illum: &Spectrum = match sensor_illum {
Some(arc_illum) => arc_illum, Some(arc_illum) => arc_illum,
None => &Spectrum::Dense(output_colorspace.as_ref().illuminant), None => &Spectrum::Dense(output_colorspace.as_ref().illuminant),
}; };
let r_bar = DenselySampledSpectrumBuffer::from_spectrum(r); let r_bar = DenselySampledSpectrum::from_spectrum(r);
let g_bar = DenselySampledSpectrumBuffer::from_spectrum(g); let g_bar = DenselySampledSpectrum::from_spectrum(g);
let b_bar = DenselySampledSpectrumBuffer::from_spectrum(b); let b_bar = DenselySampledSpectrum::from_spectrum(b);
let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES]; let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES];
let swatches = Self::get_swatches(); let swatches = Self::get_swatches();
for i in 0..N_SWATCH_REFLECTANCES { for i in 0..N_SWATCH_REFLECTANCES {
let rgb = DevicePixelSensor::project_reflectance::<RGB>( let rgb = PixelSensor::project_reflectance::<RGB>(
&swatches[i], &swatches[i],
illum, illum,
&Spectrum::Dense(r_bar.device()), &Spectrum::Dense(r_bar),
&Spectrum::Dense(g_bar.device()), &Spectrum::Dense(g_bar),
&Spectrum::Dense(b_bar.device()), &Spectrum::Dense(b_bar),
); );
for c in 0..3 { for c in 0..3 {
rgb_camera[i][c] = rgb[c]; rgb_camera[i][c] = rgb[c];
@ -151,11 +157,11 @@ impl PixelSensor {
let mut xyz_output = [[0.; 3]; N_SWATCH_REFLECTANCES]; let mut xyz_output = [[0.; 3]; N_SWATCH_REFLECTANCES];
let spectra = get_spectra_context(); let spectra = get_spectra_context();
let sensor_white_g = illum.inner_product(&Spectrum::Dense(g_bar.device())); let sensor_white_g = illum.inner_product(&Spectrum::Dense(g_bar));
let sensor_white_y = illum.inner_product(&Spectrum::Dense(spectra.y)); let sensor_white_y = illum.inner_product(&Spectrum::Dense(spectra.y));
for i in 0..N_SWATCH_REFLECTANCES { for i in 0..N_SWATCH_REFLECTANCES {
let s = swatches[i].clone(); let s = swatches[i].clone();
let xyz = DevicePixelSensor::project_reflectance::<XYZ>( let xyz = PixelSensor::project_reflectance::<XYZ>(
&s, &s,
illum, illum,
&Spectrum::Dense(spectra.x), &Spectrum::Dense(spectra.x),
@ -170,21 +176,13 @@ impl PixelSensor {
let xyz_from_sensor_rgb = linear_least_squares(rgb_camera, xyz_output) let xyz_from_sensor_rgb = linear_least_squares(rgb_camera, xyz_output)
.expect("Could not convert sensor illuminance to XYZ space"); .expect("Could not convert sensor illuminance to XYZ space");
let data = SensorStorage { PixelSensor {
r_bar: r_bar.clone(), r_bar: r_bar.clone(),
g_bar: g_bar.clone(), g_bar: g_bar.clone(),
b_bar: b_bar.clone(), b_bar: b_bar.clone(),
};
let device = DevicePixelSensor {
r_bar: r_bar.device(),
g_bar: g_bar.device(),
b_bar: b_bar.device(),
imaging_ratio, imaging_ratio,
xyz_from_sensor_rgb, xyz_from_sensor_rgb,
}; }
Self { device, data }
} }
fn new_with_white_balance( fn new_with_white_balance(
@ -206,25 +204,13 @@ impl PixelSensor {
xyz_from_sensor_rgb = SquareMatrix::<Float, 3>::default(); xyz_from_sensor_rgb = SquareMatrix::<Float, 3>::default();
} }
let data = SensorStorage { PixelSensor {
r_bar: r_bar.clone(), r_bar: r_bar.clone(),
g_bar: g_bar.clone(), g_bar: g_bar.clone(),
b_bar: b_bar.clone(), b_bar: b_bar.clone(),
};
let device = DevicePixelSensor {
r_bar: r_bar.device(),
g_bar: g_bar.device(),
b_bar: b_bar.device(),
xyz_from_sensor_rgb, xyz_from_sensor_rgb,
imaging_ratio, imaging_ratio,
}; }
Self { data, device }
}
pub fn device(&self) -> DevicePixelSensor {
self.device
} }
fn get_swatches() -> Arc<[Spectrum; N_SWATCH_REFLECTANCES]> { fn get_swatches() -> Arc<[Spectrum; N_SWATCH_REFLECTANCES]> {
@ -236,7 +222,7 @@ pub trait CreateFilmBase {
fn create( fn create(
params: &ParameterDictionary, params: &ParameterDictionary,
filter: Filter, filter: Filter,
sensor: Option<&DevicePixelSensor>, sensor: Option<&PixelSensor>,
loc: &FileLoc, loc: &FileLoc,
) -> Result<Self> ) -> Result<Self>
where where
@ -247,7 +233,7 @@ impl CreateFilmBase for FilmBase {
fn create( fn create(
params: &ParameterDictionary, params: &ParameterDictionary,
filter: Filter, filter: Filter,
sensor: Option<&DevicePixelSensor>, sensor: Option<&PixelSensor>,
loc: &FileLoc, loc: &FileLoc,
) -> Result<Self> ) -> Result<Self>
where where
@ -314,7 +300,7 @@ pub trait FilmTrait: Sync {
image.write(filename, metadata).expect("Something") image.write(filename, metadata).expect("Something")
} }
fn get_image(&self, _metadata: &ImageMetadata, splat_scale: Float) -> Image { fn get_image(&self, _metadata: &ImageMetadata, splat_scale: Float) -> HostImage {
let write_fp16 = true; let write_fp16 = true;
let format = if write_fp16 { let format = if write_fp16 {
PixelFormat::F16 PixelFormat::F16
@ -362,8 +348,8 @@ pub trait FilmTrait: Sync {
}) })
.collect(); .collect();
let mut image = Image::new(format, resolution, channel_names, SRGB.into()); let mut image = HostImage::new(format, resolution, channel_names, SRGB.into());
let rgb_desc = ImageChannelDesc::new(&[0, 1, 2]); let _rgb_desc = ImageChannelDesc::new(&[0, 1, 2]);
for (iy, row_data) in processed_rows.into_iter().enumerate() { for (iy, row_data) in processed_rows.into_iter().enumerate() {
for (ix, rgb_chunk) in row_data.chunks_exact(3).enumerate() { for (ix, rgb_chunk) in row_data.chunks_exact(3).enumerate() {
@ -381,10 +367,6 @@ pub trait FilmTrait: Sync {
); );
} }
// self.base().pixel_bounds = pixel_bounds;
// self.base().full_resolution = resolution;
// self.colorspace = colorspace;
image image
} }
} }

View file

@ -1,20 +1,28 @@
use crate::filters::*;
use crate::utils::containers::Array2D;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::DeviceRepr;
use crate::utils::{FileLoc, ParameterDictionary}; use crate::utils::{FileLoc, ParameterDictionary};
use crate::Arena;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use shared::core::filter::{DeviceFilterSampler, 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::*;
use shared::Float; use shared::{Array2D, Float};
pub trait FilterFactory { pub trait FilterFactory {
fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result<Filter>; fn create(
name: &str,
params: &ParameterDictionary,
loc: &FileLoc,
arena: &Arena,
) -> Result<Filter>;
} }
impl FilterFactory for Filter { impl FilterFactory for Filter {
fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result<Self> { fn create(
name: &str,
params: &ParameterDictionary,
loc: &FileLoc,
arena: &Arena,
) -> Result<Self> {
match name { match name {
"box" => { "box" => {
let xw = params.get_one_float("xradius", 0.5)?; let xw = params.get_one_float("xradius", 0.5)?;
@ -27,7 +35,6 @@ impl FilterFactory for Filter {
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(filter))
} }
"mitchell" => { "mitchell" => {
let xw = params.get_one_float("xradius", 2.)?; let xw = params.get_one_float("xradius", 2.)?;
@ -54,52 +61,3 @@ impl FilterFactory for Filter {
} }
} }
} }
#[repr(C)]
#[derive(Clone, Debug, Copy)]
pub struct FilterSampler {
pub domain: Bounds2f,
pub distrib: PiecewiseConstant2D,
pub f: Array2D<Float>,
}
impl FilterSampler {
pub fn new<F>(radius: Vector2f, func: F) -> Self
where
F: Fn(Point2f) -> Float,
{
let domain = Bounds2f::from_points(
Point2f::new(-radius.x(), -radius.y()),
Point2f::new(radius.x(), radius.y()),
);
let nx = (32.0 * radius.x()) as i32;
let ny = (32.0 * radius.y()) as i32;
let mut f = Array2D::new_dims(nx, ny);
for y in 0..f.y_size() {
for x in 0..f.x_size() {
let p = domain.lerp(Point2f::new(
(x as Float + 0.5) / f.x_size() as Float,
(y as Float + 0.5) / f.y_size() as Float,
));
f[(x as i32, y as i32)] = func(p);
}
}
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
Self { domain, distrib, f }
}
}
impl DeviceRepr for FilterSampler {
type Target = DeviceFilterSampler;
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> DeviceFilterSampler {
DeviceFilterSampler {
domain: self.domain,
distrib: self.distrib.upload_value(arena),
f: self.f.upload_value(arena),
}
}
}

View file

@ -4,8 +4,6 @@ use shared::spectra::RGBColorSpace;
use shared::utils::math::SquareMatrix; use shared::utils::math::SquareMatrix;
use std::collections::HashMap; use std::collections::HashMap;
// use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ImageChannelDesc { pub struct ImageChannelDesc {
pub offset: Vec<usize>, pub offset: Vec<usize>,

View file

@ -6,7 +6,7 @@ use shared::Float;
use shared::Ptr; 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::{DeviceImage, 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 smallvec::{SmallVec, smallvec};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@ -69,133 +69,66 @@ impl DerefMut for ImageChannelValues {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum PixelStorage { pub struct HostImage {
U8(Vec<u8>), pub inner: Image,
F16(Vec<f16>),
F32(Vec<f32>),
}
impl PixelStorage {
pub fn as_pixels(&self) -> Pixels {
match self {
PixelStorage::U8(vec) => Pixels::new_u8(Ptr::from_raw(vec.as_ptr())),
PixelStorage::F16(vec) => Pixels::new_f16(Ptr::from_raw(vec.as_ptr() as *const u16)),
PixelStorage::F32(vec) => Pixels::new_f32(Ptr::from_raw(vec.as_ptr())),
}
}
pub fn format(&self) -> PixelFormat {
match self {
PixelStorage::U8(_) => PixelFormat::U8,
PixelStorage::F16(_) => PixelFormat::F16,
PixelStorage::F32(_) => PixelFormat::F32,
}
}
pub fn len(&self) -> usize {
match self {
PixelStorage::U8(d) => d.len(),
PixelStorage::F16(d) => d.len(),
PixelStorage::F32(d) => d.len(),
}
}
}
#[derive(Debug, Clone)]
pub struct Image {
pub pixels: PixelStorage,
pub channel_names: Vec<String>, pub channel_names: Vec<String>,
pub device: DeviceImage,
} }
// impl Deref for Image {
// type Target = DeviceImage;
// #[inline]
// fn deref(&self) -> &Self::Target {
// &self.device
// }
// }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ImageAndMetadata { pub struct ImageAndMetadata {
pub image: Image, pub image: HostImage,
pub metadata: ImageMetadata, pub metadata: ImageMetadata,
} }
impl Image { impl HostImage {
// Constructors pub fn from_u8(
fn from_storage( data: &[u8],
storage: PixelStorage,
resolution: Point2i, resolution: Point2i,
channel_names: &[impl AsRef<str>], channel_names: &[impl AsRef<str>],
encoding: ColorEncoding, encoding: ColorEncoding,
) -> Self { ) -> Self {
let n_channels = channel_names.len() as i32; let n_channels = channel_names.len() as i32;
let expected = (resolution.x() * resolution.y()) as usize * n_channels as usize;
let channel_names = channel_names
.iter()
.map(|s| s.as_ref().to_string())
.collect();
assert_eq!(storage.len(), expected, "Pixel data size mismatch");
let device = DeviceImage {
base: ImageBase {
format: storage.format(),
encoding,
resolution,
n_channels,
},
pixels: storage.as_pixels(),
};
Self { Self {
pixels: storage, inner: Image::from_u8(data, resolution, n_channels, encoding),
channel_names, channel_names: channel_names.iter().map(|s| s.as_ref().to_string()).collect(),
device,
} }
} }
pub fn from_u8( pub fn from_f32(
data: Vec<u8>, data: &[f32],
resolution: Point2i, resolution: Point2i,
channel_names: &[impl AsRef<str>], channel_names: &[impl AsRef<str>],
encoding: ColorEncoding,
) -> Self { ) -> Self {
Self::from_storage(PixelStorage::U8(data), resolution, channel_names, encoding) 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(),
}
} }
pub fn from_f16( pub fn from_f16(
data: Vec<half::f16>, data: &[u16],
resolution: Point2i, resolution: Point2i,
channel_names: &[impl AsRef<str>], channel_names: &[impl AsRef<str>],
) -> Self { ) -> Self {
Self::from_storage(PixelStorage::F16(data), resolution, channel_names, LINEAR) let n_channels = channel_names.len() as i32;
} Self {
inner: Image::from_f16(data, resolution, n_channels),
pub fn from_f32( channel_names: channel_names.iter().map(|s| s.as_ref().to_string()).collect(),
data: Vec<f32>, }
resolution: Point2i,
channel_names: &[impl AsRef<str>],
) -> Self {
Self::from_storage(PixelStorage::F32(data), resolution, channel_names, LINEAR)
} }
pub fn new( pub fn new(
format: PixelFormat, format: PixelFormat,
resolution: Point2i, resolution: Point2i,
channel_names: &[impl AsRef<str>], channel_names: &[impl AsRef<str>],
encoding: Arc<ColorEncoding>, encoding: ColorEncoding,
) -> Self { ) -> Self {
let n_channels = channel_names.len(); let n_channels = channel_names.len() as i32;
let pixel_count = (resolution.x() * resolution.y()) as usize * n_channels; Self {
inner: Image::new(format, resolution, n_channels, encoding),
let storage = match format { channel_names: channel_names.iter().map(|s| s.as_ref().to_string()).collect(),
PixelFormat::U8 => PixelStorage::U8(vec![0; pixel_count].into()), }
PixelFormat::F16 => PixelStorage::F16(vec![f16::ZERO; pixel_count].into()),
PixelFormat::F32 => PixelStorage::F32(vec![0.0; pixel_count].into()),
};
Self::from_storage(storage, resolution, channel_names, *encoding)
} }
pub fn new_constant( pub fn new_constant(
@ -204,206 +137,29 @@ impl Image {
values: &[f32], values: &[f32],
) -> Self { ) -> Self {
let n_channels = channel_names.len(); let n_channels = channel_names.len();
if values.len() != n_channels { assert_eq!(values.len(), n_channels, "values length must match channel count");
panic!(
"Image::new_constant: values length ({}) must match channel count ({})",
values.len(),
n_channels
);
}
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);
for _ in 0..n_pixels { for _ in 0..n_pixels {
data.extend_from_slice(values); data.extend_from_slice(values);
} }
Self::from_f32(data, resolution, channel_names) Self::from_f32(&data, resolution, channel_names)
}
// Access
pub fn device(&self) -> &DeviceImage {
&self.device
}
pub fn resolution(&self) -> Point2i {
self.base.resolution
}
fn n_channels(&self) -> i32 {
self.base().n_channels
}
pub fn format(&self) -> PixelFormat {
self.base().format
} }
pub fn channel_names(&self) -> Vec<&str> { pub fn channel_names(&self) -> Vec<&str> {
self.channel_names.iter().map(|s| s.as_str()).collect() self.channel_names.iter().map(|s| s.as_str()).collect()
} }
pub fn encoding(&self) -> ColorEncoding {
self.base().encoding
}
fn pixel_offset(&self, p: Point2i) -> usize {
let width = self.resolution().x() as usize;
let idx = p.y() as usize * width + p.x() as usize;
idx * self.n_channels() as usize
}
// Read
pub fn as_f32_slice(&self) -> Option<&[f32]> {
match &self.pixels {
PixelStorage::F32(data) => Some(data.as_slice()),
_ => None,
}
}
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, mut p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float {
if !self.device.base.remap_pixel_coords(&mut p, wrap_mode) {
return 0.0;
}
let offset = self.pixel_offset(p) + c as usize;
match &self.pixels {
PixelStorage::U8(data) => self.device.base.encoding.to_linear_scalar(data[offset]),
PixelStorage::F16(data) => data[offset].to_f32(),
PixelStorage::F32(data) => data[offset],
}
}
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 {
if !self.device.base.remap_pixel_coords(&mut p, wrap_mode) {
return ImageChannelValues(smallvec![0.0; self.n_channels() as usize]);
}
let offset = self.pixel_offset(p);
let nc = self.n_channels() as usize;
let mut values = SmallVec::with_capacity(nc);
match &self.pixels {
PixelStorage::U8(data) => {
for i in 0..nc {
values.push(self.device.base.encoding.to_linear_scalar(data[offset + i]));
}
}
PixelStorage::F16(data) => {
for i in 0..nc {
values.push(data[offset + i].to_f32());
}
}
PixelStorage::F32(data) => {
for i in 0..nc {
values.push(data[offset + i]);
}
}
}
ImageChannelValues(values)
}
// Write
pub fn set_channel(&mut self, p: Point2i, c: i32, mut value: Float) {
if value.is_nan() {
value = 0.0;
}
let res = self.resolution();
if p.x() < 0 || p.x() >= res.x() || p.y() < 0 || p.y() >= res.y() {
return;
}
let offset = self.pixel_offset(p) + c as usize;
match &mut self.pixels {
PixelStorage::U8(data) => {
data[offset] = self.device.base.encoding.from_linear_scalar(value);
}
PixelStorage::F16(data) => {
data[offset] = f16::from_f32(value);
}
PixelStorage::F32(data) => {
data[offset] = value;
}
}
}
pub fn set_channels(&mut self, p: Point2i, values: &ImageChannelValues) {
for i in 0..values.len() {
self.set_channel(p, i.try_into().unwrap(), values[i])
}
}
// Descriptions
pub fn get_channels_with_desc(
&self,
p: Point2i,
desc: &ImageChannelDesc,
wrap_mode: WrapMode2D,
) -> ImageChannelValues {
let mut pp = p;
if !self.device.base.remap_pixel_coords(&mut pp, wrap_mode) {
return ImageChannelValues(smallvec![0.0; desc.offset.len()]);
}
let pixel_offset = self.pixel_offset(pp);
let mut values = SmallVec::with_capacity(desc.offset.len());
match &self.pixels {
PixelStorage::U8(data) => {
for &c in &desc.offset {
let raw = data[pixel_offset + c];
values.push(self.device.base.encoding.to_linear_scalar(raw));
}
}
PixelStorage::F16(data) => {
for &c in &desc.offset {
values.push(data[pixel_offset + c].to_f32());
}
}
PixelStorage::F32(data) => {
for &c in &desc.offset {
values.push(data[pixel_offset + c]);
}
}
}
ImageChannelValues(values)
}
pub fn channel_names_from_desc(&self, desc: &ImageChannelDesc) -> Vec<&str> {
desc.offset
.iter()
.map(|&i| self.channel_names[i].as_str())
.collect()
}
pub fn get_channel_desc( pub fn get_channel_desc(
&self, &self,
requested_channels: &[impl AsRef<str> + std::fmt::Display], requested: &[impl AsRef<str> + std::fmt::Display],
) -> Result<ImageChannelDesc> { ) -> Result<ImageChannelDesc> {
let mut offset = Vec::with_capacity(requested_channels.len()); let mut offset = Vec::with_capacity(requested.len());
for req in requested {
for req in requested_channels.iter() {
match self.channel_names.iter().position(|n| n == req.as_ref()) { match self.channel_names.iter().position(|n| n == req.as_ref()) {
Some(idx) => { Some(idx) => offset.push(idx),
offset.push(idx);
}
None => { None => {
return Err(anyhow!( return Err(anyhow!(
"Missing channel '{}'. Available: {:?}", "Missing channel '{}'. Available: {:?}",
@ -413,81 +169,98 @@ impl Image {
} }
} }
} }
Ok(ImageChannelDesc { offset }) Ok(ImageChannelDesc { offset })
} }
pub fn all_channels_desc(&self) -> ImageChannelDesc { pub fn all_channels_desc(&self) -> ImageChannelDesc {
ImageChannelDesc { ImageChannelDesc {
offset: (0..self.n_channels() as usize).collect(), offset: (0..self.inner.n_channels() as usize).collect(),
}
}
pub fn channel_names_from_desc(&self, desc: &ImageChannelDesc) -> Vec<&str> {
desc.offset.iter().map(|&i| self.channel_names[i].as_str()).collect()
}
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 {
if !self.inner.remap_pixel_coords(&mut p, wrap_mode) {
return ImageChannelValues(SmallVec::from_elem(0.0, self.inner.n_channels() as usize));
}
let offset = self.inner.pixel_offset(p);
let nc = self.inner.n_channels() as usize;
let mut values = SmallVec::with_capacity(nc);
for i in 0..nc {
values.push(unsafe { self.inner.pixels.read(offset + i, &self.inner.encoding) });
}
ImageChannelValues(values)
}
pub fn get_channels_with_desc(
&self,
p: Point2i,
desc: &ImageChannelDesc,
wrap_mode: WrapMode2D,
) -> ImageChannelValues {
let mut pp = p;
if !self.inner.remap_pixel_coords(&mut pp, wrap_mode) {
return ImageChannelValues(SmallVec::from_elem(0.0, desc.offset.len()));
}
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) });
}
ImageChannelValues(values)
}
pub fn set_channels(&mut self, p: Point2i, values: &ImageChannelValues) {
for i in 0..values.len() {
self.inner.set_channel(p, i as i32, values[i]);
} }
} }
pub fn select_channels(&self, desc: &ImageChannelDesc) -> Self { pub fn select_channels(&self, desc: &ImageChannelDesc) -> Self {
let new_names: Vec<String> = desc let new_names: Vec<String> = desc.offset.iter().map(|&i| self.channel_names[i].clone()).collect();
.offset let res = self.inner.resolution();
.iter()
.map(|&i| self.channel_names[i].clone())
.collect();
let res = self.resolution();
let pixel_count = (res.x() * res.y()) as usize; let pixel_count = (res.x() * res.y()) as usize;
let src_nc = self.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();
let new_storage = match &self.pixels { // Always produce f32 output for simplicity
PixelStorage::U8(src) => { let mut dst = vec![0.0f32; pixel_count * dst_nc];
let mut dst = vec![0u8; pixel_count * dst_nc]; for i in 0..pixel_count {
for i in 0..pixel_count { 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] = src[i * src_nc + in_c]; dst[i * dst_nc + out_idx] = unsafe {
} self.inner.pixels.read(src_offset + in_c, &self.inner.encoding)
} };
PixelStorage::U8(dst)
} }
PixelStorage::F16(src) => { }
let mut dst = vec![f16::ZERO; pixel_count * dst_nc];
for i in 0..pixel_count {
for (out_idx, &in_c) in desc.offset.iter().enumerate() {
dst[i * dst_nc + out_idx] = src[i * src_nc + in_c];
}
}
PixelStorage::F16(dst)
}
PixelStorage::F32(src) => {
let mut dst = vec![0.0f32; pixel_count * dst_nc];
for i in 0..pixel_count {
for (out_idx, &in_c) in desc.offset.iter().enumerate() {
dst[i * dst_nc + out_idx] = src[i * src_nc + in_c];
}
}
PixelStorage::F32(dst)
}
};
Self::from_storage(new_storage, res, &new_names, self.encoding()) Self::from_f32(&dst, res, &new_names)
} }
pub fn get_sampling_distribution<F>(&self, dxd_a: F, domain: Bounds2f) -> Array2D<Float> pub fn get_sampling_distribution<F>(&self, dxd_a: F, domain: Bounds2f) -> Array2D<Float>
where where
F: Fn(Point2f) -> Float + Sync + Send, F: Fn(Point2f) -> Float + Sync + Send,
{ {
let width = self.resolution().x(); let width = self.inner.resolution().x();
let height = self.resolution().y(); let height = self.inner.resolution().y();
let mut dist: Array2D<Float> = Array2D::new_dims(width, height); let mut dist = Array2D::new_dims(width, height);
dist.values 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)| {
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;
let value = self.get_channels(Point2i::new(x, y)).average(); let value = self.get_channels(Point2i::new(x, y)).average();
let u = (x as Float + 0.5) / width as Float; let u = (x as Float + 0.5) / width as Float;
let v = (y as Float + 0.5) / height as Float; let v = (y as Float + 0.5) / height as Float;
let p = domain.lerp(Point2f::new(u, v)); let p = domain.lerp(Point2f::new(u, v));
@ -499,29 +272,36 @@ impl Image {
} }
pub fn get_sampling_distribution_uniform(&self) -> Array2D<Float> { pub fn get_sampling_distribution_uniform(&self) -> Array2D<Float> {
let default_domain = Bounds2f::from_points(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0)); let domain = Bounds2f::from_points(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0));
self.get_sampling_distribution(|_| 1.0, domain)
}
self.get_sampling_distribution(|_| 1.0, default_domain) pub fn has_any_infinite_pixels(&self) -> bool {
self.inner.has_any_infinite_pixels()
}
pub fn has_any_nan_pixels(&self) -> bool {
self.inner.has_any_nan_pixels()
} }
pub fn mse( pub fn mse(
&self, &self,
desc: ImageChannelDesc, desc: &ImageChannelDesc,
ref_img: &Image, ref_img: &HostImage,
generate_mse_image: bool, generate_mse_image: bool,
) -> (ImageChannelValues, Option<Image>) { ) -> (ImageChannelValues, Option<HostImage>) {
let res = self.resolution(); let res = self.inner.resolution();
assert_eq!(res, ref_img.inner.resolution());
let mut sum_se: Vec<f64> = vec![0.; desc.size()];
let names_ref = self.channel_names_from_desc(&desc);
let ref_desc = ref_img let ref_desc = ref_img
.get_channel_desc(&self.channel_names_from_desc(&desc)) .get_channel_desc(&self.channel_names_from_desc(desc))
.expect("Channels not found in image"); .expect("Channels not found in reference image");
assert_eq!(res, ref_img.resolution());
let width = res.x() as usize; let width = res.x() as usize;
let height = res.y() as usize; let height = res.y() as usize;
let n_channels = desc.offset.len(); let n_channels = desc.size();
let mut sum_se: Vec<f64> = vec![0.0; n_channels];
let mut mse_pixels = if generate_mse_image { let mut mse_pixels = if generate_mse_image {
vec![0.0f32; width * height * n_channels] vec![0.0f32; width * height * n_channels]
} else { } else {
@ -530,18 +310,15 @@ impl Image {
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 = let v = self.get_channels_with_desc(
self.get_channels_with_desc(Point2i::new(x, y), &desc, WrapMode::Clamp.into()); Point2i::new(x, y), desc, WrapMode::Clamp.into(),
let v_ref = self.get_channels_with_desc(
Point2i::new(x, y),
&ref_desc,
WrapMode::Clamp.into(),
); );
for c in 0..desc.size() { let v_ref = ref_img.get_channels_with_desc(
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); let se = square(v[c] as f64 - v_ref[c] as f64);
if se.is_infinite() { if se.is_infinite() { continue; }
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;
@ -556,7 +333,8 @@ impl Image {
sum_se.iter().map(|&s| (s / pixel_count) as Float).collect(); sum_se.iter().map(|&s| (s / pixel_count) as Float).collect();
let mse_image = if generate_mse_image { let mse_image = if generate_mse_image {
Some(Image::new(PixelFormat::F32, res, &names_ref, LINEAR.into())) let names = self.channel_names_from_desc(desc);
Some(HostImage::from_f32(&mse_pixels, res, &names))
} else { } else {
None None
}; };
@ -564,53 +342,11 @@ impl Image {
(ImageChannelValues(mse_values), mse_image) (ImageChannelValues(mse_values), mse_image)
} }
pub fn update_view_pointers(&mut self) { pub fn image(&self) -> &Image {
self.device.pixels = match &self.pixels { &self.inner
PixelStorage::U8(vec) => Pixels::new_u8(Ptr::from_raw(vec.as_ptr())),
PixelStorage::F16(vec) => Pixels::new_f16(Ptr::from_raw(vec.as_ptr() as *const u16)),
PixelStorage::F32(vec) => Pixels::new_f32(Ptr::from_raw(vec.as_ptr())),
};
} }
pub fn has_any_infinite_pixels(&self) -> bool { pub fn into_image(self) -> Image {
if self.format() == PixelFormat::F32 { self.inner
return false;
}
for y in 0..self.resolution().y() {
for x in 0..self.resolution().x() {
for c in 0..self.n_channels() {
if self.get_channel(Point2i::new(x, y), c).is_infinite() {
return true;
}
}
}
}
return false;
}
pub fn has_any_nan_pixels(&self) -> bool {
if self.format() == PixelFormat::F32 {
return false;
}
for y in 0..self.resolution().y() {
for x in 0..self.resolution().x() {
for c in 0..self.n_channels() {
if self.get_channel(Point2i::new(x, y), c).is_nan() {
return true;
}
}
}
}
return false;
}
}
impl std::ops::Deref for Image {
type Target = DeviceImage;
fn deref(&self) -> &DeviceImage {
&self.device
} }
} }

View file

@ -1,12 +1,12 @@
use super::Image; use super::HostImage;
use crate::core::image::PixelStorage;
use crate::core::image::pixel::PixelStorageTrait; use crate::core::image::pixel::PixelStorageTrait;
use crate::core::image::PixelStorage;
use rayon::prelude::*; use rayon::prelude::*;
use shared::Float;
use shared::core::color::ColorEncoding; use shared::core::color::ColorEncoding;
use shared::core::geometry::{Bounds2i, Point2i}; use shared::core::geometry::{Bounds2i, Point2i};
use shared::core::image::{PixelFormat, WrapMode, WrapMode2D}; use shared::core::image::{PixelFormat, WrapMode, WrapMode2D};
use shared::utils::math::windowed_sinc; use shared::utils::math::windowed_sinc;
use shared::Float;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -15,71 +15,105 @@ pub struct ResampleWeight {
pub weight: [Float; 4], pub weight: [Float; 4],
} }
impl Image { impl HostImage {
pub fn flip_y(&mut self) { pub fn flip_y(&mut self) {
let res = self.resolution(); let res = self.inner.resolution;
let nc = self.n_channels() as usize; let nc = self.inner.n_channels as usize;
match &mut self.pixels { match self.inner.format {
PixelStorage::U8(d) => flip_y_kernel(d, res, nc), PixelFormat::U8 => flip_y_kernel(self.inner.pixels.as_u8_mut(), res, nc),
PixelStorage::F16(d) => flip_y_kernel(d, res, nc), PixelFormat::F16 => flip_y_kernel(self.inner.pixels.as_f16_mut(), res, nc),
PixelStorage::F32(d) => flip_y_kernel(d, res, nc), PixelFormat::F32 => flip_y_kernel(self.inner.pixels.as_f32_slice_mut(), res, nc),
} }
} }
pub fn crop(&self, bounds: Bounds2i) -> Image { pub fn crop(&self, bounds: Bounds2i) -> HostImage {
let res = self.resolution(); let n_channels = self.inner.n_channels as usize;
let n_channels = self.n_channels() as usize;
let new_res = Point2i::new( let new_res = Point2i::new(
bounds.p_max.x() - bounds.p_min.x(), bounds.p_max.x() - bounds.p_min.x(),
bounds.p_max.y() - bounds.p_min.y(), bounds.p_max.y() - bounds.p_min.y(),
); );
let mut new_image = Image::new( let new_names = self.channel_names.clone();
self.format(), let mut new_image =
new_res, HostImage::new(self.inner.format, new_res, &new_names, self.inner.encoding);
&self.channel_names,
self.encoding().into(),
);
match (&self.pixels, &mut new_image.pixels) { match self.inner.format {
(PixelStorage::U8(src), PixelStorage::U8(dst)) => { PixelFormat::U8 => crop_kernel(
crop_kernel(src, dst, res, bounds, n_channels) self.inner.pixels.as_u8(),
} new_image.inner.pixels.as_u8_mut(),
(PixelStorage::F16(src), PixelStorage::F16(dst)) => { self.inner.resolution,
crop_kernel(src, dst, res, bounds, n_channels) bounds,
} n_channels,
(PixelStorage::F32(src), PixelStorage::F32(dst)) => { ),
crop_kernel(src, dst, res, bounds, n_channels) PixelFormat::F16 => crop_kernel(
} self.inner.pixels.as_f16(),
_ => panic!("Format mismatch in crop"), new_image.inner.pixels.as_f16_mut(),
self.inner.resolution,
bounds,
n_channels,
),
PixelFormat::F32 => crop_kernel(
self.inner.pixels.as_f32_slice(),
new_image.inner.pixels.as_f32_slice_mut(),
self.inner.resolution,
bounds,
n_channels,
),
} }
new_image new_image
} }
pub fn copy_rect_out(&self, extent: Bounds2i, buf: &mut [Float], wrap: WrapMode2D) { pub fn copy_rect_out(&self, extent: Bounds2i, buf: &mut [Float], wrap: WrapMode2D) {
match &self.pixels { match self.inner.format {
PixelStorage::U8(d) => copy_rect_out_kernel(d, self, extent, buf, wrap), PixelFormat::U8 => {
PixelStorage::F16(d) => copy_rect_out_kernel(d, self, extent, buf, wrap), copy_rect_out_kernel(self.inner.pixels.as_u8(), self, extent, buf, wrap)
PixelStorage::F32(d) => copy_rect_out_kernel(d, self, extent, buf, wrap), }
PixelFormat::F16 => {
copy_rect_out_kernel(self.inner.pixels.as_f16(), self, extent, buf, wrap)
}
PixelFormat::F32 => {
copy_rect_out_kernel(self.inner.pixels.as_f32_slice(), self, extent, buf, wrap)
}
} }
} }
pub fn copy_rect_in(&mut self, extent: Bounds2i, buf: &[Float]) { pub fn copy_rect_in(&mut self, extent: Bounds2i, buf: &[Float]) {
let res = self.resolution(); let res = self.inner.resolution;
let n_channels = self.n_channels() as usize; let n_channels = self.inner.n_channels as usize;
let encoding = self.encoding(); let encoding = self.inner.encoding;
let format = self.inner.format;
match &mut self.pixels { match format {
PixelStorage::U8(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf), PixelFormat::U8 => copy_rect_in_kernel(
PixelStorage::F16(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf), self.inner.pixels.as_u8_mut(),
PixelStorage::F32(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf), res,
n_channels,
encoding,
extent,
buf,
),
PixelFormat::F16 => copy_rect_in_kernel(
self.inner.pixels.as_f16_mut(),
res,
n_channels,
encoding,
extent,
buf,
),
PixelFormat::F32 => copy_rect_in_kernel(
self.inner.pixels.as_f32_slice_mut(),
res,
n_channels,
encoding,
extent,
buf,
),
} }
} }
pub fn float_resize_up(&self, new_res: Point2i, wrap_mode: WrapMode2D) -> Image { pub fn float_resize_up(&self, new_res: Point2i, wrap_mode: WrapMode2D) -> HostImage {
let res = self.resolution(); let res = self.resolution();
assert!(new_res.x() >= res.x() && new_res.y() >= res.y()); assert!(new_res.x() >= res.x() && new_res.y() >= res.y());
assert!( assert!(
@ -87,7 +121,7 @@ impl Image {
"ResizeUp requires Float format" "ResizeUp requires Float format"
); );
let resampled_image = Arc::new(Mutex::new(Image::new( let resampled_image = Arc::new(Mutex::new(HostImage::new(
PixelFormat::F32, PixelFormat::F32,
new_res, new_res,
&self.channel_names, &self.channel_names,
@ -132,7 +166,7 @@ impl Image {
.unwrap() .unwrap()
} }
pub fn generate_pyramid(base: Image, _wrap: WrapMode) -> Vec<Image> { pub fn generate_pyramid(base: HostImage, _wrap: WrapMode) -> Vec<HostImage> {
let mut levels = vec![base]; let mut levels = vec![base];
let internal_wrap = WrapMode2D { let internal_wrap = WrapMode2D {
uv: [WrapMode::Clamp; 2], uv: [WrapMode::Clamp; 2],
@ -146,17 +180,32 @@ impl Image {
} }
let new_res = Point2i::new((old.x() / 2).max(1), (old.y() / 2).max(1)); let new_res = Point2i::new((old.x() / 2).max(1), (old.y() / 2).max(1));
let mut next = Image::new( let mut next = HostImage::new(
prev.format(), prev.inner.format,
new_res, new_res,
&prev.channel_names, &prev.channel_names,
prev.encoding().into(), prev.inner.encoding,
); );
match &mut next.pixels { match next.inner.format {
PixelStorage::U8(d) => downsample_kernel(d, new_res, prev, internal_wrap), PixelFormat::U8 => downsample_kernel(
PixelStorage::F16(d) => downsample_kernel(d, new_res, prev, internal_wrap), next.inner.pixels.as_u8_mut(),
PixelStorage::F32(d) => downsample_kernel(d, new_res, prev, internal_wrap), new_res,
&prev.inner,
internal_wrap,
),
PixelFormat::F16 => downsample_kernel(
next.inner.pixels.as_f16_mut(),
new_res,
&prev.inner,
internal_wrap,
),
PixelFormat::F32 => downsample_kernel(
next.inner.pixels.as_f32_slice_mut(),
new_res,
&prev.inner,
internal_wrap,
),
} }
levels.push(next); levels.push(next);
} }
@ -202,7 +251,7 @@ fn crop_kernel<T: PixelStorageTrait>(
fn copy_rect_out_kernel<T: PixelStorageTrait>( fn copy_rect_out_kernel<T: PixelStorageTrait>(
src: &[T], src: &[T],
image: &Image, image: &HostImage,
extent: Bounds2i, extent: Bounds2i,
buf: &mut [Float], buf: &mut [Float],
wrap: WrapMode2D, wrap: WrapMode2D,

View file

@ -1,26 +1,26 @@
use crate::core::spectrum::SPECTRUM_CACHE; use crate::core::spectrum::SPECTRUM_CACHE;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::spectra::DenselySampledSpectrumBuffer; use crate::{Arena, FileLoc, ParameterDictionary};
use crate::utils::{Arena, FileLoc, ParameterDictionary}; use anyhow::{anyhow, Result};
use anyhow::{Result, anyhow};
use shared::core::camera::CameraTransform; use shared::core::camera::CameraTransform;
use shared::core::light::Light; use shared::core::light::Light;
use shared::core::medium::Medium; use shared::core::medium::Medium;
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::spectra::DenselySampledSpectrum;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::Transform; use shared::Transform;
use std::sync::Arc; use std::sync::Arc;
pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrumBuffer> { pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrum> {
let cache = &SPECTRUM_CACHE; let cache = &SPECTRUM_CACHE;
let dense_spectrum = DenselySampledSpectrumBuffer::from_spectrum(s); let dense_spectrum = DenselySampledSpectrum::from_spectrum(s);
cache.lookup(dense_spectrum).into() cache.lookup(dense_spectrum).into()
} }
// Placeholders for non-area lights that never inspect these arguments. // Placeholders for non-area lights that never inspect these arguments.
// TODO: refactor each light's create to only take what it actually needs, // TODO: refactor each light to only take what it actually needs,
// then delete these. // then delete this bullshit
fn dummy_shape() -> Shape { fn dummy_shape() -> Shape {
Shape::default() Shape::default()
} }
@ -29,7 +29,6 @@ fn dummy_alpha() -> FloatTexture {
FloatTexture::default() FloatTexture::default()
} }
/// Create a non-area light from a scene file directive.
pub fn create_light( pub fn create_light(
name: &str, name: &str,
render_from_light: Transform, render_from_light: Transform,
@ -110,8 +109,7 @@ pub fn create_light(
} }
} }
/// Create a diffuse area light bound to a specific shape. /// Create a diffuse area light bound to a specific shape
/// Called once per sub-shape (e.g. once per triangle in a mesh).
pub fn create_area_light( pub fn create_area_light(
render_from_light: Transform, render_from_light: Transform,
medium: Option<Medium>, medium: Option<Medium>,

View file

@ -1,32 +1,9 @@
use crate::spectra::dense::DenselySampledSpectrumBuffer;
use shared::core::geometry::{Bounds3f, Point3i}; use shared::core::geometry::{Bounds3f, Point3i};
use shared::core::medium::{GridMedium, HGPhaseFunction, HomogeneousMedium, RGBGridMedium}; use shared::core::medium::{GridMedium, HGPhaseFunction, HomogeneousMedium, RGBGridMedium};
use shared::core::spectrum::{Spectrum, SpectrumTrait}; use shared::core::spectrum::{Spectrum, SpectrumTrait};
use shared::spectra::{RGBIlluminantSpectrum, RGBUnboundedSpectrum}; use shared::spectra::{RGBIlluminantSpectrum, RGBUnboundedSpectrum, DenselySampledSpectrum};
use shared::utils::Transform;
use shared::utils::containers::SampledGrid; use shared::utils::containers::SampledGrid;
use shared::{Float, core::medium::MajorantGrid}; use shared::{Float, Transform, core::medium::MajorantGrid};
pub struct MajorantGridHost {
pub device: MajorantGrid,
voxels: Vec<Float>,
}
impl MajorantGridHost {
pub fn new(bounds: Bounds3f, res: Point3i) -> Self {
let n = (res.x() * res.y() * res.z()) as usize;
let voxels = vec![0.0; n];
let device = MajorantGrid {
bounds,
res,
voxels: std::ptr::null_mut(),
n_voxels: n as u32,
};
Self { device, voxels }
}
}
pub trait RGBGridMediumCreator { pub trait RGBGridMediumCreator {
fn new( fn new(
@ -53,7 +30,7 @@ impl RGBGridMediumCreator for RGBGridMedium {
le_grid: SampledGrid<RGBIlluminantSpectrum>, le_grid: SampledGrid<RGBIlluminantSpectrum>,
le_scale: Float, le_scale: Float,
) -> Self { ) -> Self {
let mut majorant_grid = MajorantGridHost::new(*bounds, Point3i::new(16, 16, 16)).device; let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
for z in 0..majorant_grid.res.x() { for z in 0..majorant_grid.res.x() {
for y in 0..majorant_grid.res.y() { for y in 0..majorant_grid.res.y() {
for x in 0..majorant_grid.res.x() { for x in 0..majorant_grid.res.x() {
@ -109,15 +86,15 @@ impl GridMediumCreator for GridMedium {
le: &Spectrum, le: &Spectrum,
le_scale: SampledGrid<Float>, le_scale: SampledGrid<Float>,
) -> Self { ) -> Self {
let mut sigma_a_spec = DenselySampledSpectrumBuffer::from_spectrum(sigma_a); let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(sigma_a);
let mut sigma_s_spec = DenselySampledSpectrumBuffer::from_spectrum(sigma_s); let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(sigma_s);
sigma_a_spec.scale(sigma_scale); sigma_a_spec.scale(sigma_scale);
sigma_s_spec.scale(sigma_scale); sigma_s_spec.scale(sigma_scale);
let le_spec = DenselySampledSpectrumBuffer::from_spectrum(le); let le_spec = DenselySampledSpectrum::from_spectrum(le);
let mut majorant_grid = MajorantGridHost::new(*bounds, Point3i::new(16, 16, 16)).device; let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16)).device;
let is_emissive = if temperature_grid.is_some() { let is_emissive = if temperature_grid.is_some() {
true true
} else { } else {
@ -169,9 +146,9 @@ impl HomogeneousMediumCreator for HomogeneousMedium {
le_scale: Float, le_scale: Float,
g: Float, g: Float,
) -> Self { ) -> Self {
let mut sigma_a_spec = DenselySampledSpectrumBuffer::from_spectrum(&sigma_a); let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(&sigma_a);
let mut sigma_s_spec = DenselySampledSpectrumBuffer::from_spectrum(&sigma_s); let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(&sigma_s);
let mut le_spec = DenselySampledSpectrumBuffer::from_spectrum(&le); let mut le_spec = DenselySampledSpectrum::from_spectrum(&le);
sigma_a_spec.scale(sigma_scale); sigma_a_spec.scale(sigma_scale);
sigma_s_spec.scale(sigma_scale); sigma_s_spec.scale(sigma_scale);

View file

@ -11,7 +11,7 @@ use crate::core::shape::{ShapeFactory, ShapeWithContext};
use crate::core::texture::{FloatTexture, SpectrumTexture}; 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, Upload}; use crate::utils::resolve_filename;
use crate::{Arena, FileLoc}; use crate::{Arena, FileLoc};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use parking_lot::Mutex; use parking_lot::Mutex;
@ -121,7 +121,7 @@ impl BasicScene {
sampler: SceneEntity, sampler: SceneEntity,
integ: SceneEntity, integ: SceneEntity,
accel: SceneEntity, accel: SceneEntity,
arena: Arc<Arena>, arena: &Arena,
) -> Result<()> { ) -> Result<()> {
*self.integrator.lock() = Some(integ); *self.integrator.lock() = Some(integ);
*self.accelerator.lock() = Some(accel); *self.accelerator.lock() = Some(accel);
@ -130,7 +130,7 @@ impl BasicScene {
*self.film_colorspace.lock() = Some(Arc::clone(cs)); *self.film_colorspace.lock() = Some(Arc::clone(cs));
} }
let filter = Filter::create(&filter.name, &filter.parameters, &filter.loc) let filter = Filter::create(&filter.name, &filter.parameters, &filter.loc, &arena)
.map_err(|e| anyhow!("Failed to create filter: {}", e))?; .map_err(|e| anyhow!("Failed to create filter: {}", e))?;
let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.)?; let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.)?;
let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.)?; let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.)?;

View file

@ -1,18 +1,19 @@
use crate::spectra::{DenselySampledSpectrumBuffer, cie_y}; use crate::spectra::cie_y;
use crate::utils::containers::InternCache; use crate::utils::containers::InternCache;
use parking_lot::Mutex; use parking_lot::Mutex;
use shared::Float; use shared::Float;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::spectra::DenselySampledSpectrum;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::LazyLock; use std::sync::LazyLock;
pub static SPECTRUM_CACHE: LazyLock<InternCache<DenselySampledSpectrumBuffer>> = pub static SPECTRUM_CACHE: LazyLock<InternCache<DenselySampledSpectrum>> =
LazyLock::new(InternCache::new); LazyLock::new(InternCache::new);
pub static SPECTRUM_FILE_CACHE: LazyLock<Mutex<HashMap<String, Spectrum>>> = pub static SPECTRUM_FILE_CACHE: LazyLock<Mutex<HashMap<String, Spectrum>>> =
LazyLock::new(|| Mutex::new(HashMap::new())); LazyLock::new(|| Mutex::new(HashMap::new()));
pub fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrumBuffer> { pub fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrum> {
&SPECTRUM_CACHE &SPECTRUM_CACHE
} }

View file

@ -1,21 +1,21 @@
use crate::textures::*; use crate::textures::*;
use crate::utils::mipmap::MIPMap; use crate::utils::mipmap::{MIPMapFilterOptions, MIPMap};
use crate::utils::mipmap::MIPMapFilterOptions; use crate::utils::TextureParameterDictionary;
use crate::utils::{Arena, FileLoc, TextureParameterDictionary}; use crate::{Arena, FileLoc};
use anyhow::{Result, anyhow}; use anyhow::{anyhow, Result};
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use shared::Float;
use shared::core::color::ColorEncoding; use shared::core::color::ColorEncoding;
use shared::core::geometry::Vector3f; 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, PlanarMapping, SphericalMapping, TextureEvalContext, TextureMapping2D, CylindricalMapping, PlanarMapping, SphericalMapping, TextureEvalContext, TextureMapping2D,
UVMapping, UVMapping, GPUFloatTexture, GPUSpectrumTexture
}; };
use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::spectra::{SampledSpectrum, SampledWavelengths};
use shared::textures::*; use shared::textures::*;
use shared::utils::Transform; use shared::utils::Transform;
use shared::Float;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock}; use std::sync::{Arc, Mutex, OnceLock};
@ -29,20 +29,30 @@ pub trait SpectrumTextureTrait {
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum; fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum;
} }
#[enum_dispatch(FloatTextureTrait)] #[derive(Clone, Debug)]
#[derive(Debug, Clone)]
pub enum FloatTexture { pub enum FloatTexture {
Constant(FloatConstantTexture), Constant(FloatConstantTexture),
Image(FloatImageTexture),
Mix(FloatMixTexture),
DirectionMix(FloatDirectionMixTexture),
Scaled(FloatScaledTexture),
Bilerp(FloatBilerpTexture),
Checkerboard(FloatCheckerboardTexture), Checkerboard(FloatCheckerboardTexture),
Dots(FloatDotsTexture), Dots(FloatDotsTexture),
FBm(FBmTexture), FBm(FBmTexture),
Windy(WindyTexture), Windy(WindyTexture),
Wrinkled(WrinkledTexture), Wrinkled(WrinkledTexture),
Mix(FloatMixTexture),
DirectionMix(FloatDirectionMixTexture),
// #[device(custom = "upload_image", variant_type = "GPUFloatImageTexture")]
Image(FloatImageTexture),
Bilerp(FloatBilerpTexture),
}
impl FloatTexture {
fn upload_image(inner: &FloatImageTexture, _arena: &Arena) -> GPUFloatImageTexture {
GPUFloatImageTexture {
mapping: inner.base.mapping,
tex_obj: inner.base.mipmap.texture_object(),
scale: inner.base.scale,
invert: inner.base.invert,
}
}
} }
impl Default for FloatTexture { impl Default for FloatTexture {
@ -95,21 +105,43 @@ impl FloatTexture {
} }
} }
#[derive(Clone, Debug)]
#[enum_dispatch(SpectrumTextureTrait)]
pub enum SpectrumTexture { pub enum SpectrumTexture {
// RGBConstant(RGBConstantTexture),
// RGBReflectanceConstant(RGBReflectanceConstantTexture),
Constant(SpectrumConstantTexture), Constant(SpectrumConstantTexture),
Bilerp(SpectrumBilerpTexture),
Checkerboard(SpectrumCheckerboardTexture), Checkerboard(SpectrumCheckerboardTexture),
Dots(SpectrumDotsTexture),
// #[device(
// custom = "upload_spectrum_image",
// variant_type = "GPUSpectrumImageTexture"
// )]
Image(SpectrumImageTexture), Image(SpectrumImageTexture),
Bilerp(SpectrumBilerpTexture),
Scaled(SpectrumScaledTexture),
Marble(MarbleTexture), Marble(MarbleTexture),
Mix(SpectrumMixTexture), Mix(SpectrumMixTexture),
DirectionMix(SpectrumDirectionMixTexture), DirectionMix(SpectrumDirectionMixTexture),
Dots(SpectrumDotsTexture), }
// Ptex(SpectrumPtexTexture),
Scaled(SpectrumScaledTexture), impl SpectrumTexture {
fn upload_spectrum_image(
inner: &SpectrumImageTexture,
arena: &Arena,
) -> GPUSpectrumImageTexture {
let tex_obj = arena.get_texture_object(&inner.base.mipmap);
GPUSpectrumImageTexture {
mapping: inner.base.mapping,
tex_obj,
scale: inner.base.scale,
invert: inner.base.invert,
is_single_channel: inner.base.mipmap.is_single_channel(),
color_space: inner
.base
.mipmap
.color_space
.clone()
.unwrap_or_else(crate::spectra::default_colorspace),
spectrum_type: inner.spectrum_type,
}
}
} }
pub trait CreateSpectrumTexture { pub trait CreateSpectrumTexture {

View file

@ -8,44 +8,6 @@ use shared::spectra::RGBColorSpace;
use shared::utils::AnimatedTransform; use shared::utils::AnimatedTransform;
use std::path::Path; use std::path::Path;
pub struct GBufferFilmHost {
pub device: GBufferFilm,
}
impl GBufferFilmHost {
pub fn new(
base: &FilmBase,
output_from_render: &AnimatedTransform,
apply_inverse: bool,
colorspace: &RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
) -> Self {
assert!(!base.pixel_bounds.is_empty());
let sensor_ptr = base.sensor;
if sensor_ptr.is_null() {
panic!("Film must have a sensor");
}
let sensor = &*sensor_ptr;
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor.xyz_from_sensor_rgb;
let filter_integral = base.filter.integral();
let pixels = Array2D::new(base.pixel_bounds);
let device = GBufferFilm {
base: base.clone(),
output_from_render: *output_from_render,
apply_inverse,
pixels: pixels.device,
colorspace: colorspace.clone(),
max_component_value,
write_fp16,
filter_integral,
output_rgbf_from_sensor_rgb,
};
Self { device }
}
}
impl CreateFilm for GBufferFilm { impl CreateFilm for GBufferFilm {
fn create( fn create(
@ -85,7 +47,7 @@ impl CreateFilm for GBufferFilm {
.into()); .into());
}; };
let film = GBufferFilmHost::new( let film = GBufferFilm::new(
&film_base, &film_base,
&output_from_render, &output_from_render,
apply_inverse, apply_inverse,
@ -94,6 +56,6 @@ impl CreateFilm for GBufferFilm {
write_fp16, write_fp16,
); );
Ok(Film::GBuffer(film.device)) Ok(Film::GBuffer(film))
} }
} }

View file

@ -8,58 +8,6 @@ use shared::core::film::{Film, FilmBase, RGBFilm, RGBPixel};
use shared::core::filter::FilterTrait; use shared::core::filter::FilterTrait;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
struct RGBFilmStorage {
pixels: Array2D<RGBPixel>,
}
pub struct RGBFilmHost {
pub device: RGBFilm,
storage: RGBFilmStorage,
}
impl RGBFilmHost {
pub fn new(
base: FilmBase,
colorspace: &RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
) -> Self {
let sensor_ptr = base.sensor;
if sensor_ptr.is_null() {
panic!("Film must have a sensor");
}
let sensor = &*sensor_ptr;
let filter_integral = base.filter.integral();
let sensor_matrix = sensor.xyz_from_sensor_rgb;
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix;
let width = base.pixel_bounds.p_max.x() - base.pixel_bounds.p_min.x();
let height = base.pixel_bounds.p_max.y() - base.pixel_bounds.p_min.y();
let count = (width * height) as usize;
let mut pixel_vec = Vec::with_capacity(count);
for _ in 0..count {
pixel_vec.push(RGBPixel::default());
}
let pixels: Array2D<RGBPixel> = Array2D::new(base.pixel_bounds);
let device_pixels = pixels.device.clone();
let storage = RGBFilmStorage { pixels };
let device = RGBFilm {
base,
max_component_value,
write_fp16,
filter_integral,
output_rgbf_from_sensor_rgb,
pixels: device_pixels,
};
Self { device, storage }
}
}
impl CreateFilm for RGBFilm { impl CreateFilm for RGBFilm {
fn create( fn create(
params: &ParameterDictionary, params: &ParameterDictionary,
@ -74,7 +22,7 @@ impl CreateFilm for RGBFilm {
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)?;
let film_base = FilmBase::create(params, filter, Some(&sensor.device()), loc)?; let film_base = FilmBase::create(params, filter, Some(&sensor.device()), loc)?;
let film = RGBFilmHost::new(film_base, &colorspace, max_component_value, write_fp16); let film = RGBFilm::new(film_base, &colorspace, max_component_value, write_fp16);
Ok(Film::RGB(film.device)) Ok(Film::RGB(film))
} }
} }

View file

@ -1,91 +1,15 @@
use super::*; use super::*;
use crate::core::film::{CreateFilmBase, PixelSensor}; use crate::core::film::{CreateFilmBase, PixelSensor};
use crate::utils::containers::Array2D;
use crate::{Arena, FileLoc, ParameterDictionary}; use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use shared::Float; use shared::Float;
use shared::core::camera::CameraTransform; use shared::core::camera::CameraTransform;
use shared::core::film::{FilmBase, SpectralFilm, SpectralPixel}; use shared::core::film::{FilmBase, SpectralFilm};
use shared::core::filter::FilterTrait; use shared::core::filter::FilterTrait;
use shared::spectra::{LAMBDA_MAX, LAMBDA_MIN, RGBColorSpace}; use shared::spectra::{LAMBDA_MAX, LAMBDA_MIN};
use shared::utils::AtomicFloat;
use shared::utils::containers::DeviceArray2D;
use shared::utils::math::SquareMatrix; use shared::utils::math::SquareMatrix;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
struct SpectralFilmStorage {
pixels: DeviceArray2D<SpectralPixel>,
bucket_sums: Vec<f64>,
weight_sums: Vec<f64>,
bucket_splats: Vec<AtomicFloat>,
}
pub struct SpectralFilmHost {
pub device: SpectralFilm,
storage: Arc<SpectralFilmStorage>,
}
impl SpectralFilmHost {
pub fn new(
base: &FilmBase,
lambda_min: Float,
lambda_max: Float,
n_buckets: usize,
colorspace: &RGBColorSpace,
max_component_value: Float,
write_fp16: bool,
) -> Self {
let n_pixels = base.pixel_bounds.area() as usize;
let total_buckets = n_pixels * n_buckets;
let bucket_sums = vec![0.0; total_buckets];
let weight_sums = vec![0.0; total_buckets];
let mut bucket_splats = Vec::with_capacity(total_buckets);
for _ in 0..total_buckets {
bucket_splats.push(AtomicFloat::new(0.0));
}
let mut pixels = Array2D::<SpectralPixel>::new(base.pixel_bounds);
for i in 0..n_pixels {
let pixel = pixels.get_linear_mut(i);
pixel.bucket_offset = i * n_buckets;
}
let storage = Arc::new(SpectralFilmStorage {
pixels: pixels.device,
bucket_sums,
weight_sums,
bucket_splats,
});
let device = SpectralFilm {
base: *base,
colorspace: colorspace.clone(),
lambda_min,
lambda_max,
n_buckets,
max_component_value,
write_fp16,
filter_integral: base.filter.integral(),
output_rgbf_from_sensor_rgb: SquareMatrix::identity(),
pixels: DeviceArray2D {
values: pixels.values.as_mut_ptr(),
extent: base.pixel_bounds,
stride: base.pixel_bounds.p_max.x() - base.pixel_bounds.p_min.x(),
},
bucket_sums: storage.bucket_sums.as_ptr() as *mut f64,
weight_sums: storage.weight_sums.as_ptr() as *mut f64,
bucket_splats: storage.bucket_splats.as_ptr() as *mut AtomicFloat,
};
Self { device, storage }
}
}
impl CreateFilm for SpectralFilm { impl CreateFilm for SpectralFilm {
fn create( fn create(
@ -96,6 +20,7 @@ impl CreateFilm for SpectralFilm {
loc: &FileLoc, loc: &FileLoc,
_arena: &Arena, _arena: &Arena,
) -> Result<Film> { ) -> Result<Film> {
// 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)?;
@ -117,7 +42,7 @@ impl CreateFilm for SpectralFilm {
)); ));
} }
let film = SpectralFilmHost::new( let film = SpectralFilm::new(
&film_base, &film_base,
lambda_min, lambda_min,
lambda_max, lambda_max,
@ -127,6 +52,6 @@ impl CreateFilm for SpectralFilm {
write_fp16, write_fp16,
); );
Ok(Film::Spectral(film.device)) Ok(Film::Spectral(film))
} }
} }

View file

@ -1 +0,0 @@
pub trait BoxFilterCreator {}

View file

@ -1,30 +0,0 @@
use crate::core::filter::CreateFilterSampler;
use shared::Float;
use shared::core::filter::FilterSampler;
use shared::core::geometry::{Point2f, Vector2f};
use shared::filters::GaussianFilter;
use shared::utils::math::gaussian;
#[derive(Clone, Debug, Device)]
#[device(name = "GaussianFilter")]
pub struct GaussianFilterHost {
pub radius: Vector2f,
pub sigma: Float,
pub exp_x: Float,
pub exp_y: Float,
#[device(flatten)]
pub sampler: FilterSampler,
}
impl GaussianFilterHost {
pub fn new(radius: Vector2f, sigma: Float) -> Self {
let exp_x = gaussian(radius.x(), 0.0, sigma);
let exp_y = gaussian(radius.y(), 0.0, sigma);
let sampler = FilterSampler::new(radius, move |p: Point2f| {
let gx = (gaussian(p.x(), 0.0, sigma) - exp_x).max(0.0);
let gy = (gaussian(p.y(), 0.0, sigma) - exp_y).max(0.0);
gx * gy
});
Self { radius, sigma, exp_x, exp_y, sampler }
}
}

View file

@ -1,40 +0,0 @@
use crate::core::filter::CreateFilterSampler;
use rand::Rng;
use shared::Float;
use shared::core::filter::FilterSampler;
use shared::core::geometry::{Point2f, Vector2f};
use shared::filters::LanczosSincFilter;
use shared::utils::math::{lerp, windowed_sinc};
#[derive(Clone, Debug, Device)]
#[device(name = "LanczosSincFilter")]
pub struct LanczosSincFilterHost {
pub radius: Vector2f,
pub tau: Float,
#[device(flatten)]
pub sampler: FilterSampler,
}
impl LanczosSincFilterHost {
pub fn new(radius: Vector2f, tau: Float) -> Self {
let sampler = FilterSampler::new(radius, move |p: Point2f| {
windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau)
});
Self { radius, tau, sampler }
}
}
fn windowed_sinc(x: Float, radius: Float, tau: Float) -> Float {
use std::f32::consts::PI;
let x = x.abs();
if x > radius {
return 0.0;
}
if x < 1e-5 {
1.0
} else {
let xpi = x * PI;
let xpit = xpi * tau;
(xpi.sin() / xpi) * (xpit.sin() / xpit)
}
}

View file

@ -1,42 +0,0 @@
use crate::core::filter::CreateFilterSampler;
use shared::Float;
use shared::core::filter::FilterSampler;
use shared::core::geometry::{Point2f, Vector2f};
use shared::filters::MitchellFilter;
#[derive(Clone, Debug, Device)]
#[device(name = "MitchellFilter")]
pub struct MitchellFilterHost {
pub radius: Vector2f,
pub b: Float,
pub c: Float,
#[device(flatten)]
pub sampler: FilterSampler,
}
impl MitchellFilterHost {
pub fn new(radius: Vector2f, b: Float, c: Float) -> Self {
let sampler = FilterSampler::new(radius, move |p: Point2f| {
mitchell_1d(p.x() / radius.x(), b, c) * mitchell_1d(p.y() / radius.y(), b, c)
});
Self { radius, b, c, sampler }
}
}
fn mitchell_1d(x: Float, b: Float, c: Float) -> Float {
let x = (2.0 * x).abs();
if x <= 1.0 {
((12.0 - 9.0 * b - 6.0 * c) * x * x * x
+ (-18.0 + 12.0 * b + 6.0 * c) * x * x
+ (6.0 - 2.0 * b))
/ 6.0
} else if x <= 2.0 {
((-b - 6.0 * c) * x * x * x
+ (6.0 * b + 30.0 * c) * x * x
+ (-12.0 * b - 48.0 * c) * x
+ (8.0 * b + 24.0 * c))
/ 6.0
} else {
0.0
}
}

View file

@ -1,11 +0,0 @@
pub mod boxf;
pub mod gaussian;
pub mod lanczos;
pub mod mitchell;
pub mod triangle;
pub use boxf::*;
pub use gaussian::*;
pub use lanczos::*;
pub use mitchell::*;
// pub use triangle::*;

View file

@ -1 +0,0 @@

View file

@ -1,6 +1,6 @@
use crate::core::color::RGBToSpectrumTableData; use crate::core::color::CreateRGBToSpectrumTable;
use shared::core::color::RES;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use shared::core::color::{RGBToSpectrumTable, RES};
use shared::Float; use shared::Float;
use shared::PBRTOptions; use shared::PBRTOptions;
use std::sync::OnceLock; use std::sync::OnceLock;
@ -30,7 +30,6 @@ fn aligned_cast(bytes: &[u8]) -> &[Float] {
} }
} }
static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../data/srgb_scale.dat"); static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../data/srgb_scale.dat");
static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../data/srgb_coeffs.dat"); static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../data/srgb_coeffs.dat");
static DCI_P3_SCALE_BYTES: &[u8] = include_bytes!("../data/dcip3_scale.dat"); static DCI_P3_SCALE_BYTES: &[u8] = include_bytes!("../data/dcip3_scale.dat");
@ -40,7 +39,6 @@ static ACES_COEFFS_BYTES: &[u8] = include_bytes!("../data/aces_coeffs.dat");
static REC2020_SCALE_BYTES: &[u8] = include_bytes!("../data/rec2020_scale.dat"); static REC2020_SCALE_BYTES: &[u8] = include_bytes!("../data/rec2020_scale.dat");
static REC2020_COEFFS_BYTES: &[u8] = include_bytes!("../data/rec2020_coeffs.dat"); static REC2020_COEFFS_BYTES: &[u8] = include_bytes!("../data/rec2020_coeffs.dat");
fn strip_to_len(bytes: &[u8], expected_len: usize) -> &'static [Float] { fn strip_to_len(bytes: &[u8], expected_len: usize) -> &'static [Float] {
let all: Vec<Float> = bytemuck::pod_collect_to_vec(bytes); let all: Vec<Float> = bytemuck::pod_collect_to_vec(bytes);
let skip = all.len() - expected_len; let skip = all.len() - expected_len;
@ -53,20 +51,24 @@ const COEFFS_LEN: usize = (RES * RES * RES) as usize * 3 * 3;
pub static SRGB_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(SRGB_SCALE_BYTES, RES as usize)); pub static SRGB_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(SRGB_SCALE_BYTES, RES as usize));
pub static SRGB_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(SRGB_COEFFS_BYTES, COEFFS_LEN)); pub static SRGB_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(SRGB_COEFFS_BYTES, COEFFS_LEN));
pub static DCI_P3_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(DCI_P3_SCALE_BYTES, RES as usize)); pub static DCI_P3_SCALE: Lazy<&[Float]> =
pub static DCI_P3_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(DCI_P3_COEFFS_BYTES, COEFFS_LEN)); Lazy::new(|| strip_to_len(DCI_P3_SCALE_BYTES, RES as usize));
pub static DCI_P3_COEFFS: Lazy<&[Float]> =
Lazy::new(|| strip_to_len(DCI_P3_COEFFS_BYTES, COEFFS_LEN));
pub static ACES_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(ACES_SCALE_BYTES, RES as usize)); pub static ACES_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(ACES_SCALE_BYTES, RES as usize));
pub static ACES_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(ACES_COEFFS_BYTES, COEFFS_LEN)); pub static ACES_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(ACES_COEFFS_BYTES, COEFFS_LEN));
pub static REC2020_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(REC2020_SCALE_BYTES, RES as usize)); pub static REC2020_SCALE: Lazy<&[Float]> =
pub static REC2020_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(REC2020_COEFFS_BYTES, COEFFS_LEN)); Lazy::new(|| strip_to_len(REC2020_SCALE_BYTES, RES as usize));
pub static REC2020_COEFFS: Lazy<&[Float]> =
Lazy::new(|| strip_to_len(REC2020_COEFFS_BYTES, COEFFS_LEN));
pub static SRGB_TABLE: Lazy<RGBToSpectrumTableData> = pub static SRGB_TABLE: Lazy<RGBToSpectrumTable> =
Lazy::new(|| RGBToSpectrumTableData::new(SRGB_SCALE.to_vec(), SRGB_COEFFS.to_vec())); Lazy::new(|| RGBToSpectrumTableData::new(SRGB_SCALE, SRGB_COEFFS));
pub static DCI_P3_TABLE: Lazy<RGBToSpectrumTableData> = pub static DCI_P3_TABLE: Lazy<RGBToSpectrumTable> =
Lazy::new(|| RGBToSpectrumTableData::new(DCI_P3_SCALE.to_vec(), DCI_P3_COEFFS.to_vec())); Lazy::new(|| RGBToSpectrumTableData::new(DCI_P3_SCALE.to_vec(), DCI_P3_COEFFS.to_vec()));
pub static REC2020_TABLE: Lazy<RGBToSpectrumTableData> = pub static REC2020_TABLE: Lazy<RGBToSpectrumTable> =
Lazy::new(|| RGBToSpectrumTableData::new(REC2020_SCALE.to_vec(), REC2020_COEFFS.to_vec())); Lazy::new(|| RGBToSpectrumTableData::new(REC2020_SCALE.to_vec(), REC2020_COEFFS.to_vec()));
pub static ACES_TABLE: Lazy<RGBToSpectrumTableData> = pub static ACES_TABLE: Lazy<RGBToSpectrumTable> =
Lazy::new(|| RGBToSpectrumTableData::new(ACES_SCALE.to_vec(), ACES_COEFFS.to_vec())); Lazy::new(|| RGBToSpectrumTableData::new(ACES_SCALE.to_vec(), ACES_COEFFS.to_vec()));

View file

@ -1,6 +1,5 @@
#![feature(f16)] #![feature(f16)]
#[allow(dead_code)] #[allow(dead_code)]
pub mod cameras;
pub mod core; pub mod core;
pub mod films; pub mod films;
pub mod filters; pub mod filters;
@ -16,5 +15,4 @@ pub mod utils;
#[cfg(feature = "cuda")] #[cfg(feature = "cuda")]
pub mod gpu; pub mod gpu;
pub use utils::{Arena, FileLoc, ParameterDictionary}; pub use utils::{Arena, FileLoc, ParameterDictionary};

View file

@ -4,8 +4,9 @@ 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;
use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename}; use crate::utils::resolve_filename;
use anyhow::{Result, anyhow}; use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{anyhow, Result};
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
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, MediumInterface};
@ -110,7 +111,7 @@ pub fn create(
_ => false, _ => false,
}; };
let (light_type, stored_alpha) = if is_constant_zero { let (light_type, _) = if is_constant_zero {
(LightType::DeltaPosition, None) (LightType::DeltaPosition, None)
} else { } else {
(LightType::Area, Some(alpha)) (LightType::Area, Some(alpha))

View file

@ -3,7 +3,6 @@ use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::utils::{Arena, FileLoc, ParameterDictionary}; use crate::utils::{Arena, FileLoc, ParameterDictionary};
use anyhow::Result; use anyhow::Result;
use shared::Float;
use shared::core::geometry::{Point3f, VectorLike}; use shared::core::geometry::{Point3f, VectorLike};
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, MediumInterface};
@ -13,8 +12,9 @@ use shared::core::texture::SpectrumType;
use shared::lights::DistantLight; use shared::lights::DistantLight;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::{Ptr, Transform}; use shared::utils::{Ptr, Transform};
use shared::Float;
pub trait CreateDistantLight { trait CreateDistantLight {
fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self; fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self;
} }

View file

@ -5,7 +5,8 @@ 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;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename}; use crate::utils::resolve_filename;
use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use shared::core::geometry::Point2i; use shared::core::geometry::Point2i;
use shared::core::light::{Light, LightBase, LightType}; use shared::core::light::{Light, LightBase, LightType};
@ -80,7 +81,7 @@ pub fn create(
]; ];
let t = let t =
Transform::from_flat(&swap_yz).expect("Could not create transform for GoniometricLight"); Transform::from_flat(&swap_yz).expect("Could not create transform for GoniometricLight");
let final_render_from_light = render_from_light * t; let _final_render_from_light = render_from_light * t;
let mi = match medium { let mi = match medium {
Some(m) => { Some(m) => {

View file

@ -1,15 +1,15 @@
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::spectra::get_spectra_context; use crate::spectra::get_spectra_context;
use crate::utils::resolve_filename;
use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D}; use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D};
use crate::utils::{resolve_filename, FileLoc, ParameterDictionary, Upload}; use crate::{Arena, FileLoc, ParameterDictionary};
use crate::Arena;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use rayon::prelude::*; use rayon::prelude::*;
use shared::core::camera::CameraTransform; 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::{DeviceImage, 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, MediumInterface};
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
@ -17,111 +17,10 @@ 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::{DenselySampledSpectrum, 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::{DevicePiecewiseConstant2D, DeviceWindowedPiecewiseConstant2D}; use shared::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D};
use shared::utils::{Ptr, Transform}; use shared::{Float, Ptr, Transform, PI};
use shared::{Float, PI};
use std::path::Path; use std::path::Path;
pub trait CreateImageInfiniteLight {
fn new(
render_from_light: Transform,
scale: Float,
image: Ptr<DeviceImage>,
image_color_space: Ptr<RGBColorSpace>,
distrib: Ptr<DevicePiecewiseConstant2D>,
compensated_distrib: Ptr<DevicePiecewiseConstant2D>,
) -> Self;
}
impl CreateImageInfiniteLight for ImageInfiniteLight {
fn new(
render_from_light: Transform,
scale: Float,
image: Ptr<DeviceImage>,
image_color_space: Ptr<RGBColorSpace>,
distrib: Ptr<DevicePiecewiseConstant2D>,
compensated_distrib: Ptr<DevicePiecewiseConstant2D>,
) -> Self {
let base = LightBase::new(
LightType::Infinite,
render_from_light,
MediumInterface::default(),
);
Self {
base,
image,
image_color_space,
scale,
distrib,
compensated_distrib,
scene_center: Point3f::default(),
scene_radius: 0.0,
}
}
}
pub trait CreatePortalInfiniteLight {
fn new(
render_from_light: Transform,
scale: Float,
image: Ptr<DeviceImage>,
image_color_space: Ptr<RGBColorSpace>,
portal: [Point3f; 4],
portal_frame: Frame,
distribution: Ptr<DeviceWindowedPiecewiseConstant2D>,
) -> Self;
}
impl CreatePortalInfiniteLight for PortalInfiniteLight {
fn new(
render_from_light: Transform,
scale: Float,
image: Ptr<DeviceImage>,
image_color_space: Ptr<RGBColorSpace>,
portal: [Point3f; 4],
portal_frame: Frame,
distribution: Ptr<DeviceWindowedPiecewiseConstant2D>,
) -> Self {
let base = LightBase::new(
LightType::Infinite,
render_from_light,
MediumInterface::default(),
);
Self {
base,
image,
image_color_space,
scale,
portal,
portal_frame,
distribution: *distribution,
scene_center: Point3f::default(),
scene_radius: 0.0,
}
}
}
pub trait CreateUniformInfiniteLight {
fn new(render_from_light: Transform, scale: Float, lemit: Ptr<DenselySampledSpectrum>) -> Self;
}
impl CreateUniformInfiniteLight for UniformInfiniteLight {
fn new(render_from_light: Transform, scale: Float, lemit: Ptr<DenselySampledSpectrum>) -> Self {
let base = LightBase::new(
LightType::Infinite,
render_from_light,
MediumInterface::default(),
);
Self {
base,
lemit,
scale,
scene_center: Point3f::default(),
scene_radius: 0.0,
}
}
}
pub fn create( pub fn create(
render_from_light: Transform, render_from_light: Transform,
_medium: Option<Medium>, _medium: Option<Medium>,

View file

@ -6,10 +6,3 @@ pub mod point;
pub mod projection; pub mod projection;
pub mod sampler; pub mod sampler;
pub mod spot; pub mod spot;
pub use distant::CreateDistantLight;
pub use infinite::{
CreateImageInfiniteLight, CreatePortalInfiniteLight, CreateUniformInfiniteLight,
};
pub use point::CreatePointLight;
pub use spot::CreateSpotLight;

View file

@ -11,8 +11,7 @@ use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType; use shared::core::texture::SpectrumType;
use shared::lights::PointLight; use shared::lights::PointLight;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::{Ptr, Transform}; use shared::{Float, PI, Ptr, Transform};
use shared::{Float, PI};
pub trait CreatePointLight { pub trait CreatePointLight {
fn new( fn new(
@ -20,6 +19,7 @@ pub trait CreatePointLight {
medium_interface: MediumInterface, medium_interface: MediumInterface,
le: Spectrum, le: Spectrum,
scale: Float, scale: Float,
arena: &Arena,
) -> Self; ) -> Self;
} }
@ -29,6 +29,7 @@ impl CreatePointLight for PointLight {
medium_interface: MediumInterface, medium_interface: MediumInterface,
le: Spectrum, le: Spectrum,
scale: Float, scale: Float,
arena: &Arena,
) -> Self { ) -> Self {
let base = LightBase::new( let base = LightBase::new(
LightType::DeltaPosition, LightType::DeltaPosition,
@ -36,12 +37,12 @@ impl CreatePointLight for PointLight {
medium_interface, medium_interface,
); );
let iemit = lookup_spectrum(&le); let iemit = lookup_spectrum(&le);
let i = Ptr::from(&iemit.device()); let i = arena.alloc((*iemit).clone());
Self { base, scale, i } Self { base, scale, i }
} }
} }
pub fn create( pub fn create(
render_from_light: Transform, render_from_light: Transform,
medium: Option<Medium>, medium: Option<Medium>,

View file

@ -2,7 +2,8 @@ use crate::core::image::{Image, 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::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename}; use crate::utils::resolve_filename;
use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use shared::Float; use shared::Float;
use shared::core::geometry::{ use shared::core::geometry::{
@ -77,7 +78,7 @@ pub fn create(
} }
let flip = Transform::scale(1., -1., 1.); let flip = Transform::scale(1., -1., 1.);
let render_from_light_flip = render_from_light * flip; let _render_from_light_flip = render_from_light * flip;
let mi = match medium { let mi = match medium {
Some(m) => { Some(m) => {

View file

@ -1,7 +1,7 @@
use crate::utils::sampling::AliasTableHost;
use crate::Arena; use crate::Arena;
use shared::core::light::{Light, LightTrait}; use shared::core::light::{Light, LightTrait};
use shared::lights::sampler::PowerLightSampler; use shared::lights::sampler::PowerLightSampler;
use shared::utils::sampling::AliasTable;
use shared::spectra::{SampledSpectrum, SampledWavelengths}; use shared::spectra::{SampledSpectrum, SampledWavelengths};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -9,7 +9,7 @@ use std::sync::Arc;
pub struct PowerSamplerHost { pub struct PowerSamplerHost {
pub lights: Vec<Arc<Light>>, pub lights: Vec<Arc<Light>>,
pub light_to_index: HashMap<usize, usize>, pub light_to_index: HashMap<usize, usize>,
pub alias_table: AliasTableHost, pub alias_table: AliasTable,
} }
impl PowerSamplerHost { impl PowerSamplerHost {
@ -18,7 +18,7 @@ impl PowerSamplerHost {
return Self { return Self {
lights: Vec::new(), lights: Vec::new(),
light_to_index: HashMap::new(), light_to_index: HashMap::new(),
alias_table: AliasTableHost::new(&[]), alias_table: AliasTable::new(&[]),
}; };
} }
@ -38,7 +38,7 @@ impl PowerSamplerHost {
light_power.push(phi.average()); light_power.push(phi.average());
} }
let alias_table = AliasTableHost::new(&light_power); let alias_table = AliasTable::new(&light_power);
Self { Self {
lights: lights_vec, lights: lights_vec,
@ -46,16 +46,15 @@ impl PowerSamplerHost {
alias_table, alias_table,
} }
} }
// pub fn to_device(&self, arena: &Arena) -> PowerLightSampler {
pub fn to_device(&self, arena: &Arena) -> PowerLightSampler { // let device_lights: Vec<Light> = self.lights.iter().map(|l| (**l).clone()).collect();
let device_lights: Vec<Light> = self.lights.iter().map(|l| (**l).clone()).collect(); // let (lights_ptr, _) = arena.alloc_slice(&device_lights);
let (lights_ptr, _) = arena.alloc_slice(&device_lights); // let alias_device = self.alias_table.to_device(arena);
let alias_device = self.alias_table.to_device(arena); //
// PowerLightSampler {
PowerLightSampler { // lights: lights_ptr,
lights: lights_ptr, // lights_len: self.lights.len() as u32,
lights_len: self.lights.len() as u32, // alias_table: alias_device,
alias_table: alias_device, // }
} // }
}
} }

View file

@ -13,14 +13,14 @@ 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::utils::{Ptr, Transform};
use shared::{Float, PI}; use shared::{Float, Ptr, Transform, PI};
pub trait CreateSpotLight { trait CreateSpotLight {
fn new( fn new(
render_from_light: Transform, render_from_light: Transform,
medium_interface: MediumInterface, _medium_interface: MediumInterface,
le: Spectrum, le: Spectrum,
scale: shared::Float, scale: Float,
cos_falloff_start: Float, cos_falloff_start: Float,
total_width: Float, total_width: Float,
) -> Self; ) -> Self;
@ -31,7 +31,7 @@ impl CreateSpotLight for SpotLight {
render_from_light: Transform, render_from_light: Transform,
_medium_interface: MediumInterface, _medium_interface: MediumInterface,
le: Spectrum, le: Spectrum,
scale: shared::Float, scale: Float,
cos_falloff_start: Float, cos_falloff_start: Float,
total_width: Float, total_width: Float,
) -> Self { ) -> Self {

View file

@ -3,8 +3,9 @@ use crate::core::material::CreateMaterial;
use crate::core::texture::SpectrumTexture; 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::{Arena, FileLoc, TextureParameterDictionary, Upload}; use crate::utils::TextureParameterDictionary;
use anyhow::{Result, bail}; use crate::{Arena, FileLoc};
use anyhow::{bail, Result};
use shared::core::material::Material; use shared::core::material::Material;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType; use shared::core::texture::SpectrumType;

View file

@ -2,15 +2,14 @@ use crate::core::image::Image;
use crate::core::material::CreateMaterial; 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::{Arena, FileLoc, TextureParameterDictionary, Upload}; use crate::{Arena, FileLoc};
use crate::utils::TextureParameterDictionary;
use shared::bxdfs::HairBxDF; use shared::bxdfs::HairBxDF;
use shared::core::material::Material; use shared::core::material::Material;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType; use shared::core::texture::SpectrumType;
use shared::materials::complex::*; use shared::materials::complex::*;
// use shared::spectra::SampledWavelengths;
use shared::textures::SpectrumConstantTexture; use shared::textures::SpectrumConstantTexture;
// use shared::utils::Ptr;
use anyhow::Result; use anyhow::Result;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;

View file

@ -1,14 +1,14 @@
use crate::core::image::{Image, ImageIO}; use crate::core::image::{Image, ImageIO};
use crate::core::shape::{ALL_BILINEAR_MESHES, CreateShape}; use crate::core::shape::{CreateShape, ALL_BILINEAR_MESHES};
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::shapes::mesh::BilinearPatchMesh; use crate::shapes::mesh::BilinearPatchMesh;
use crate::utils::sampling::PiecewiseConstant2D; use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::{Arena, FileLoc, ParameterDictionary}; use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow}; 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::BilinearPatchShape;
use shared::utils::{Ptr, Transform}; use shared::{Ptr, Transform};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;

View file

@ -1,20 +1,17 @@
use crate::Arena;
use crate::core::shape::CreateShape; use crate::core::shape::CreateShape;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::utils::{FileLoc, ParameterDictionary}; use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{Result, anyhow}; use anyhow::{anyhow, Result};
use shared::Float; use log::warn;
use shared::core::geometry::{Normal3f, Point3f}; use shared::core::geometry::{Normal3f, Point3f};
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::shapes::{CurveCommon, CurveShape, CurveType}; use shared::shapes::{CurveCommon, CurveShape, CurveType};
use shared::utils::Transform;
use shared::utils::math::lerp; use shared::utils::math::lerp;
use shared::utils::splines::{ use shared::utils::splines::{
cubic_bspline_to_bezier, elevate_quadratic_bezier_to_cubic, quadratic_bspline_to_bezier, cubic_bspline_to_bezier, elevate_quadratic_bezier_to_cubic, quadratic_bspline_to_bezier,
}; };
use shared::Ptr; use shared::Float;
use shared::{Ptr, Transform};
use log::warn;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -199,7 +196,7 @@ impl CreateShape for CurveShape {
curve_type, curve_type,
seg_normals.expect("Could not determine normals to curve segments"), seg_normals.expect("Could not determine normals to curve segments"),
split_depth.try_into().unwrap(), split_depth.try_into().unwrap(),
arena arena,
); );
curves.extend(new_curves); curves.extend(new_curves);

View file

@ -1,13 +1,12 @@
use crate::core::shape::CreateShape; use crate::core::shape::CreateShape;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::utils::{Arena, FileLoc, ParameterDictionary}; use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::Result; use anyhow::Result;
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::shapes::CylinderShape; use shared::shapes::CylinderShape;
use shared::utils::Transform;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use shared::Ptr; use shared::{Transform, Ptr};
impl CreateShape for CylinderShape { impl CreateShape for CylinderShape {
fn create( fn create(

View file

@ -1,13 +1,12 @@
use crate::core::shape::CreateShape; use crate::core::shape::CreateShape;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::utils::{Arena, FileLoc, ParameterDictionary}; use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::Result; use anyhow::Result;
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::shapes::DiskShape; use shared::shapes::DiskShape;
use shared::utils::Transform; use shared::{Ptr, Transform};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use shared::Ptr;
impl CreateShape for DiskShape { impl CreateShape for DiskShape {
fn create( fn create(

View file

@ -1,16 +1,14 @@
// use crate::Arena;
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, Vector3f, VectorLike};
use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh}; use shared::shapes::mesh::{BilinearPatchMesh, TriangleMesh};
use shared::utils::{Ptr, Transform}; use shared::{Ptr, Transform};
use std::fs::File; use std::fs::File;
use std::ops::Deref;
use std::path::Path; use std::path::Path;
/// Intermediate mesh from PLY
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct TriQuadMesh { pub struct TriQuadMesh {
pub p: Vec<Point3f>, pub p: Vec<Point3f>,
@ -21,235 +19,7 @@ pub struct TriQuadMesh {
pub quad_indices: Vec<i32>, pub quad_indices: Vec<i32>,
} }
#[derive(DeviceRepr)]
#[device(name = "DeviceTriangleMesh")]
pub struct TriangleMeshStorage {
pub vertex_indices: Vec<i32>, // → Ptr<i32> + len (always present)
pub p: Vec<Point3f>, // → Ptr<Point3f> + len (always present)
pub n: Vec<Normal3f>, // → Ptr<Normal3f> + len (empty → null Ptr, len 0)
pub s: Vec<Vector3f>, // → Ptr<Vector3f> + len
pub uv: Vec<Point2f>, // → Ptr<Point2f> + len
pub face_indices: Vec<i32>, // → Ptr<i32> + len
}
#[derive(Debug)]
pub(crate) struct BilinearMeshStorage {
pub vertex_indices: Vec<i32>,
pub p: Vec<Point3f>,
pub n: Vec<Normal3f>,
pub uv: Vec<Point2f>,
pub image_distribution: Option<PiecewiseConstant2D>,
}
#[derive(Debug)]
pub struct TriangleMesh {
pub storage: Box<TriangleMeshStorage>,
pub device: DeviceTriangleMesh,
}
#[derive(Debug)]
pub struct BilinearPatchMesh {
pub storage: Box<BilinearMeshStorage>,
pub device: DeviceBilinearPatchMesh,
}
impl Deref for TriangleMesh {
type Target = DeviceTriangleMesh;
#[inline]
fn deref(&self) -> &Self::Target {
&self.device
}
}
impl Deref for BilinearPatchMesh {
type Target = DeviceBilinearPatchMesh;
#[inline]
fn deref(&self) -> &Self::Target {
&self.device
}
}
impl TriangleMesh {
pub fn new(
render_from_object: &Transform,
reverse_orientation: bool,
vertex_indices: Vec<i32>,
mut p: Vec<Point3f>,
mut n: Vec<Normal3f>,
mut s: Vec<Vector3f>,
uv: Vec<Point2f>,
face_indices: Vec<i32>,
) -> Self {
let n_triangles = vertex_indices.len() / 3;
let n_vertices = p.len();
// Transform positions to render space
for pt in p.iter_mut() {
*pt = render_from_object.apply_to_point(*pt);
}
// Transform and optionally flip normals
if !n.is_empty() {
assert_eq!(n_vertices, n.len(), "Normal count mismatch");
for nn in n.iter_mut() {
*nn = render_from_object.apply_to_normal(*nn);
if reverse_orientation {
*nn = -*nn;
}
}
}
// Transform tangents
if !s.is_empty() {
assert_eq!(n_vertices, s.len(), "Tangent count mismatch");
for ss in s.iter_mut() {
*ss = render_from_object.apply_to_vector(*ss);
}
}
// Validate UVs
if !uv.is_empty() {
assert_eq!(n_vertices, uv.len(), "UV count mismatch");
}
// Validate face indices
if !face_indices.is_empty() {
assert_eq!(n_triangles, face_indices.len(), "Face index count mismatch");
}
let transform_swaps_handedness = render_from_object.swaps_handedness();
let storage = Box::new(TriangleMeshStorage {
vertex_indices,
p,
n,
s,
uv,
face_indices,
});
// Build device struct with pointers into storage
let device = DeviceTriangleMesh {
n_triangles: n_triangles as u32,
n_vertices: n_vertices as u32,
vertex_indices: storage.vertex_indices.as_ptr().into(),
p: storage.p.as_ptr().into(),
n: if storage.n.is_empty() {
Ptr::null()
} else {
storage.n.as_ptr().into()
},
s: if storage.s.is_empty() {
Ptr::null()
} else {
storage.s.as_ptr().into()
},
uv: if storage.uv.is_empty() {
Ptr::null()
} else {
storage.uv.as_ptr().into()
},
face_indices: if storage.face_indices.is_empty() {
Ptr::null()
} else {
storage.face_indices.as_ptr().into()
},
reverse_orientation,
transform_swaps_handedness,
};
Self { storage, device }
}
pub fn positions(&self) -> &[Point3f] {
&self.storage.p
}
pub fn indices(&self) -> &[i32] {
&self.storage.vertex_indices
}
pub fn normals(&self) -> &[Normal3f] {
&self.storage.n
}
pub fn uvs(&self) -> &[Point2f] {
&self.storage.uv
}
}
impl BilinearPatchMesh {
pub fn new(
render_from_object: &Transform,
reverse_orientation: bool,
vertex_indices: Vec<i32>,
mut p: Vec<Point3f>,
mut n: Vec<Normal3f>,
uv: Vec<Point2f>,
image_distribution: Option<PiecewiseConstant2D>,
) -> Self {
let n_patches = vertex_indices.len() / 4;
let n_vertices = p.len();
for pt in p.iter_mut() {
*pt = render_from_object.apply_to_point(*pt);
}
if !n.is_empty() {
assert_eq!(n_vertices, n.len(), "Normal count mismatch");
for nn in n.iter_mut() {
*nn = render_from_object.apply_to_normal(*nn);
if reverse_orientation {
*nn = -*nn;
}
}
}
if !uv.is_empty() {
assert_eq!(n_vertices, uv.len(), "UV count mismatch");
}
let transform_swaps_handedness = render_from_object.swaps_handedness();
let storage = Box::new(BilinearMeshStorage {
vertex_indices,
p,
n,
uv,
image_distribution,
});
let device = DeviceBilinearPatchMesh {
n_patches: n_patches as u32,
n_vertices: n_vertices as u32,
vertex_indices: storage.vertex_indices.as_ptr().into(),
p: storage.p.as_ptr().into(),
n: if storage.n.is_empty() {
Ptr::null()
} else {
storage.n.as_ptr().into()
},
uv: if storage.uv.is_empty() {
Ptr::null()
} else {
storage.uv.as_ptr().into()
},
reverse_orientation,
transform_swaps_handedness,
image_distribution: storage
.image_distribution
.as_ref()
.map(|d| Ptr::from(&d.device))
.unwrap_or(Ptr::null()),
};
Self { storage, device }
}
}
// PLY Helper Functions // PLY Helper Functions
fn get_float(elem: &DefaultElement, key: &str) -> AnyResult<f32> { fn get_float(elem: &DefaultElement, key: &str) -> AnyResult<f32> {
match elem.get(key) { match elem.get(key) {
Some(Property::Float(v)) => Ok(*v), Some(Property::Float(v)) => Ok(*v),
@ -290,14 +60,12 @@ fn get_list_uint(elem: &DefaultElement, key: &str) -> AnyResult<Vec<u32>> {
} }
} }
// TriQuadMesh Implementation
impl TriQuadMesh { impl TriQuadMesh {
pub fn read_ply<P: AsRef<Path>>(filename: P) -> AnyResult<Self> { pub fn read_ply<P: AsRef<Path>>(filename: P) -> AnyResult<Self> {
let path = filename.as_ref(); let path = filename.as_ref();
let filename_display = path.display().to_string(); let filename_display = path.display().to_string();
let mut f = File::open(path) let f = File::open(path)
.with_context(|| format!("Couldn't open PLY file \"{}\"", filename_display))?; .with_context(|| format!("Couldn't open PLY file \"{}\"", filename_display))?;
let p = Parser::<DefaultElement>::new(); let p = Parser::<DefaultElement>::new();
@ -313,7 +81,6 @@ impl TriQuadMesh {
let mut mesh = TriQuadMesh::default(); let mut mesh = TriQuadMesh::default();
// Parse vertices
if let Some(vertices) = ply.payload.get("vertex") { if let Some(vertices) = ply.payload.get("vertex") {
if vertices.is_empty() { if vertices.is_empty() {
bail!("{}: PLY file has no vertices", filename_display); bail!("{}: PLY file has no vertices", filename_display);
@ -360,7 +127,6 @@ impl TriQuadMesh {
bail!("{}: PLY file has no vertex elements", filename_display); bail!("{}: PLY file has no vertex elements", filename_display);
} }
// Parse faces
if let Some(faces) = ply.payload.get("face") { if let Some(faces) = ply.payload.get("face") {
mesh.tri_indices.reserve(faces.len() * 3); mesh.tri_indices.reserve(faces.len() * 3);
mesh.quad_indices.reserve(faces.len() * 4); mesh.quad_indices.reserve(faces.len() * 4);
@ -390,7 +156,6 @@ impl TriQuadMesh {
bail!("{}: PLY file has no face elements", filename_display); bail!("{}: PLY file has no face elements", filename_display);
} }
// Validate indices
let vertex_count = mesh.p.len() as i32; let vertex_count = mesh.p.len() as i32;
for &idx in &mesh.tri_indices { for &idx in &mesh.tri_indices {
if idx >= vertex_count { if idx >= vertex_count {
@ -421,18 +186,13 @@ impl TriQuadMesh {
return; return;
} }
// Each quad becomes 2 triangles
self.tri_indices.reserve(self.quad_indices.len() / 4 * 6); self.tri_indices.reserve(self.quad_indices.len() / 4 * 6);
for quad in self.quad_indices.chunks_exact(4) { for quad in self.quad_indices.chunks_exact(4) {
let (v0, v1, v2, v3) = (quad[0], quad[1], quad[2], quad[3]); let (v0, v1, v2, v3) = (quad[0], quad[1], quad[2], quad[3]);
// Triangle 1: v0, v1, v2
self.tri_indices.push(v0); self.tri_indices.push(v0);
self.tri_indices.push(v1); self.tri_indices.push(v1);
self.tri_indices.push(v2); self.tri_indices.push(v2);
// Triangle 2: v0, v2, v3
self.tri_indices.push(v0); self.tri_indices.push(v0);
self.tri_indices.push(v2); self.tri_indices.push(v2);
self.tri_indices.push(v3); self.tri_indices.push(v3);
@ -442,10 +202,8 @@ impl TriQuadMesh {
} }
pub fn compute_normals(&mut self) { pub fn compute_normals(&mut self) {
// Initialize normals to zero
self.n = vec![Normal3f::new(0.0, 0.0, 0.0); self.p.len()]; self.n = vec![Normal3f::new(0.0, 0.0, 0.0); self.p.len()];
// Accumulate face normals for triangles
for tri in self.tri_indices.chunks_exact(3) { for tri in self.tri_indices.chunks_exact(3) {
let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize); let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
@ -457,13 +215,11 @@ impl TriQuadMesh {
let v20 = p2 - p0; let v20 = p2 - p0;
let face_normal = v10.cross(v20); let face_normal = v10.cross(v20);
// Accumulate (will normalize later)
self.n[i0] = self.n[i0] + Normal3f::from(face_normal); self.n[i0] = self.n[i0] + Normal3f::from(face_normal);
self.n[i1] = self.n[i1] + Normal3f::from(face_normal); self.n[i1] = self.n[i1] + Normal3f::from(face_normal);
self.n[i2] = self.n[i2] + Normal3f::from(face_normal); self.n[i2] = self.n[i2] + Normal3f::from(face_normal);
} }
// Accumulate face normals for quads
for quad in self.quad_indices.chunks_exact(4) { for quad in self.quad_indices.chunks_exact(4) {
let indices: Vec<usize> = quad.iter().map(|&i| i as usize).collect(); let indices: Vec<usize> = quad.iter().map(|&i| i as usize).collect();
@ -480,7 +236,6 @@ impl TriQuadMesh {
} }
} }
// Normalize all normals
for normal in &mut self.n { for normal in &mut self.n {
let len_sq = normal.norm_squared(); let len_sq = normal.norm_squared();
if len_sq > 0.0 { if len_sq > 0.0 {
@ -489,7 +244,6 @@ impl TriQuadMesh {
} }
} }
/// Convert to a TriangleMesh, consuming self
pub fn into_triangle_mesh( pub fn into_triangle_mesh(
mut self, mut self,
render_from_object: &Transform, render_from_object: &Transform,
@ -507,15 +261,13 @@ impl TriQuadMesh {
self.tri_indices, self.tri_indices,
self.p, self.p,
self.n, self.n,
Vec::new(), // s (tangents) Vec::new(),
self.uv, self.uv,
self.face_indices, self.face_indices,
) )
} }
} }
// Create from PLY directly
impl TriangleMesh { impl TriangleMesh {
pub fn from_ply<P: AsRef<Path>>( pub fn from_ply<P: AsRef<Path>>(
filename: P, filename: P,

View file

@ -1,13 +1,12 @@
use crate::core::shape::CreateShape; use crate::core::shape::CreateShape;
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::utils::{Arena, FileLoc, ParameterDictionary}; use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::Result; use anyhow::Result;
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::shapes::SphereShape; use shared::shapes::SphereShape;
use shared::utils::Transform; use shared::{Ptr, Transform};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use shared::Ptr;
impl CreateShape for SphereShape { impl CreateShape for SphereShape {
fn create( fn create(

View file

@ -1,12 +1,12 @@
use crate::core::shape::{ALL_TRIANGLE_MESHES, CreateShape}; use crate::core::shape::{CreateShape, ALL_TRIANGLE_MESHES};
use crate::core::texture::FloatTexture; use crate::core::texture::FloatTexture;
use crate::shapes::mesh::TriangleMesh; use crate::shapes::mesh::TriangleMesh;
use crate::utils::{Arena, FileLoc, ParameterDictionary}; use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{Result, bail}; use anyhow::{bail, Result};
use log::warn; use log::warn;
use shared::core::shape::Shape; use shared::core::shape::Shape;
use shared::shapes::TriangleShape; use shared::shapes::TriangleShape;
use shared::utils::{Ptr, Transform}; use shared::{Ptr, Transform};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;

View file

@ -1,41 +1,39 @@
use crate::spectra::get_spectra_context; use crate::spectra::get_spectra_context;
use shared::core::color::{RGBToSpectrumTable, RGB, XYZ};
use super::DenselySampledSpectrumBuffer;
use shared::core::color::{RGB, RGBToSpectrumTable, XYZ};
use shared::core::geometry::Point2f; use shared::core::geometry::Point2f;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::spectra::RGBColorSpace; use shared::spectra::{DenselySampledSpectrum, RGBColorSpace};
use shared::utils::math::SquareMatrix; use shared::utils::math::SquareMatrix;
use shared::utils::ptr::Ptr; use shared::Ptr;
use std::sync::Arc; use std::sync::Arc;
#[derive(Clone, Debug)] pub trait CreateRGBColorSpace {
pub struct RGBColorSpaceData { fn new(
illuminant: Arc<DenselySampledSpectrumBuffer>,
pub view: RGBColorSpace,
}
impl std::ops::Deref for RGBColorSpaceData {
type Target = RGBColorSpace;
fn deref(&self) -> &Self::Target {
&self.view
}
}
impl RGBColorSpaceData {
pub fn new(
r: Point2f, r: Point2f,
g: Point2f, g: Point2f,
b: Point2f, b: Point2f,
illuminant: Arc<DenselySampledSpectrumBuffer>, illuminant: Arc<DenselySampledSpectrum>,
rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
) -> Self;
}
impl CreateRGBColorSpace for RGBColorSpace {
fn new(
r: Point2f,
g: Point2f,
b: Point2f,
illuminant: Arc<DenselySampledSpectrum>,
rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>, rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
) -> Self { ) -> Self {
let stdspec = get_spectra_context(); let stdspec = get_spectra_context();
let w_xyz: XYZ = Spectrum::Dense(illuminant.device()).to_xyz(&stdspec); let illum_spectrum = Spectrum::Dense(illuminant.as_ref().clone());
let w_xyz: XYZ = illum_spectrum.to_xyz(&stdspec);
let w = w_xyz.xy(); let w = w_xyz.xy();
let r_xyz = XYZ::from_xyy(r, Some(1.0)); let r_xyz = XYZ::from_xyy(r, Some(1.0));
let g_xyz = XYZ::from_xyy(g, Some(1.0)); let g_xyz = XYZ::from_xyy(g, Some(1.0));
let b_xyz = XYZ::from_xyy(b, Some(1.0)); let b_xyz = XYZ::from_xyy(b, Some(1.0));
let rgb_values = [ let rgb_values = [
[r_xyz.x(), g_xyz.x(), b_xyz.x()], [r_xyz.x(), g_xyz.x(), b_xyz.x()],
[r_xyz.y(), g_xyz.y(), b_xyz.y()], [r_xyz.y(), g_xyz.y(), b_xyz.y()],
@ -44,23 +42,17 @@ impl RGBColorSpaceData {
let rgb = SquareMatrix::new(rgb_values); let rgb = SquareMatrix::new(rgb_values);
let c: RGB = rgb.inverse().unwrap() * w_xyz; let c: RGB = rgb.inverse().unwrap() * w_xyz;
let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]); let xyz_from_rgb = rgb * SquareMatrix::diag(&[c.r, c.g, c.b]);
let rgb_from_xyz = xyz_from_rgb let rgb_from_xyz = xyz_from_rgb.inverse().expect("singular");
.inverse()
.expect("XYZ from RGB matrix is singular"); RGBColorSpace {
let view = RGBColorSpace {
r, r,
g, g,
b, b,
w, w,
illuminant: illuminant.device(), illuminant: Ptr::from(illuminant.as_ref()),
rgb_to_spectrum_table,
xyz_from_rgb, xyz_from_rgb,
rgb_from_xyz, rgb_from_xyz,
rgb_to_spectrum_table,
};
Self {
illuminant: illuminant.into(),
view,
} }
} }
} }

View file

@ -1,11 +1,11 @@
use crate::spectra::{DenselySampledSpectrumBuffer, piecewise::PiecewiseLinearSpectrumBuffer};
use shared::Float; use shared::Float;
use shared::core::spectrum::Spectrum; use shared::core::spectrum::Spectrum;
use shared::spectra::cie::*; use shared::spectra::cie::*;
use shared::spectra::{PiecewiseLinearSpectrum, DenselySampledSpectrum};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::LazyLock; use std::sync::LazyLock;
pub fn create_cie_buffer(data: &[Float]) -> DenselySampledSpectrumBuffer { pub fn create_cie(data: &[Float]) -> DenselySampledSpectrum {
let (start_lambda, step) = match data.len() { let (start_lambda, step) = match data.len() {
471 => (360.0, 1.0), 471 => (360.0, 1.0),
95 => (300.0, 5.0), 95 => (300.0, 5.0),
@ -16,9 +16,9 @@ pub fn create_cie_buffer(data: &[Float]) -> DenselySampledSpectrumBuffer {
.map(|i| start_lambda + i as Float * step) .map(|i| start_lambda + i as Float * step)
.collect(); .collect();
let buffer = PiecewiseLinearSpectrumBuffer::new(lambdas, data.to_vec()); let buffer = PiecewiseLinearSpectrum::new(lambdas, data.to_vec());
let spec = Spectrum::Piecewise(buffer.device); let spec = Spectrum::Piecewise(buffer);
DenselySampledSpectrumBuffer::from_spectrum(&spec) DenselySampledSpectrum::from_spectrum(&spec)
} }
pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(|| { pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(|| {
@ -26,8 +26,8 @@ pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(||
macro_rules! add { macro_rules! add {
($name:expr, $data:expr, $norm:expr) => { ($name:expr, $data:expr, $norm:expr) => {
let buffer = PiecewiseLinearSpectrumBuffer::from_interleaved($data, $norm); let buffer = PiecewiseLinearSpectrum::from_interleaved($data, $norm);
let spectrum = Spectrum::Piecewise(*buffer); let spectrum = Spectrum::Piecewise(buffer);
m.insert($name.to_string(), spectrum); m.insert($name.to_string(), spectrum);
}; };
} }

Some files were not shown because too many files have changed in this diff Show more