Compare commits

..

2 commits

36 changed files with 1123 additions and 530 deletions

View file

@ -1045,7 +1045,7 @@ const SRGB_TO_LINEAR_LUT: [Float; 256] = [
pub const RES: u32 = 64;
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Coeffs {
pub c0: Float,
pub c1: Float,
@ -1099,10 +1099,10 @@ impl Mul<Float> for Coeffs {
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct RGBToSpectrumTable {
pub z_nodes: Ptr<Float>,
pub coeffs: Ptr<Coeffs>,
pub z_nodes: GVec<Float>,
pub coeffs: GVec<Coeffs>,
pub n_nodes: u32,
}
@ -1113,7 +1113,7 @@ impl RGBToSpectrumTable {
#[inline(always)]
fn get_coeffs(&self, bucket: u32, z: u32, y: u32, x: u32) -> Coeffs {
let offset = bucket * (RES * RES * RES) + z * (RES * RES) + y * (RES) + x;
unsafe { *self.coeffs.add(offset as usize) }
unsafe { *self.coeffs.as_ptr().add(offset as usize) }
}
pub fn evaluate(&self, rgb: RGB) -> RGBSigmoidPolynomial {
@ -1153,7 +1153,7 @@ impl RGBToSpectrumTable {
let x = coord_a / z;
let y = coord_b / z;
let z_nodes = unsafe { core::slice::from_raw_parts(self.z_nodes.as_raw(), RES as usize) };
let z_nodes = &self.z_nodes;
let zi = find_interval(RES, |i| z_nodes[i as usize] < z) as usize;
let dz = (z - z_nodes[zi]) / (z_nodes[zi + 1] - z_nodes[zi]);
let x_float = x * (RES - 1) as Float;

View file

@ -18,5 +18,5 @@ pub mod textures;
pub mod utils;
pub use core::pbrt::*;
pub use utils::alloc::{gbox, gvec, gvec_from_slice, gvec_with_capacity, GVec, GBox};
pub use utils::alloc::{gbox, gvec, gvec_from_slice, gvec_with_capacity, leak, GBox, GVec};
pub use utils::{Array2D, PBRTOptions, Ptr, Transform};

View file

@ -40,8 +40,8 @@ impl CoatedDiffuseMaterial {
thickness: Ptr<GPUFloatTexture>,
albedo: Ptr<GPUSpectrumTexture>,
g: Ptr<GPUFloatTexture>,
eta: Ptr<Spectrum>,
displacement: Ptr<GPUFloatTexture>,
eta: Ptr<Spectrum>,
normal_map: Ptr<Image>,
remap_roughness: bool,
max_depth: u32,
@ -153,7 +153,6 @@ impl MaterialTrait for CoatedDiffuseMaterial {
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct CoatedConductorMaterial {
normal_map: Ptr<Image>,
displacement: Ptr<GPUFloatTexture>,
interface_uroughness: Ptr<GPUFloatTexture>,
interface_vroughness: Ptr<GPUFloatTexture>,
@ -166,6 +165,7 @@ pub struct CoatedConductorMaterial {
conductor_eta: Ptr<GPUSpectrumTexture>,
k: Ptr<GPUSpectrumTexture>,
reflectance: Ptr<GPUSpectrumTexture>,
normal_map: Ptr<Image>,
max_depth: u32,
n_samples: u32,
remap_roughness: bool,
@ -175,12 +175,10 @@ pub struct CoatedConductorMaterial {
impl CoatedConductorMaterial {
#[allow(clippy::too_many_arguments)]
pub fn new(
normal_map: Ptr<Image>,
displacement: Ptr<GPUFloatTexture>,
interface_uroughness: Ptr<GPUFloatTexture>,
interface_vroughness: Ptr<GPUFloatTexture>,
thickness: Ptr<GPUFloatTexture>,
interface_eta: Ptr<Spectrum>,
g: Ptr<GPUFloatTexture>,
albedo: Ptr<GPUSpectrumTexture>,
conductor_uroughness: Ptr<GPUFloatTexture>,
@ -188,6 +186,8 @@ impl CoatedConductorMaterial {
conductor_eta: Ptr<GPUSpectrumTexture>,
k: Ptr<GPUSpectrumTexture>,
reflectance: Ptr<GPUSpectrumTexture>,
normal_map: Ptr<Image>,
interface_eta: Ptr<Spectrum>,
max_depth: u32,
n_samples: u32,
remap_roughness: bool,

View file

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

View file

@ -1,8 +1,9 @@
extern crate alloc;
use crate::utils::ptr::Ptr;
use alloc::alloc::Global;
use alloc::vec::Vec;
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::alloc::{AllocError, Allocator, Layout};
use core::ptr::NonNull;
@ -200,3 +201,9 @@ pub fn gvec_from_slice<T: Clone>(slice: &[T]) -> GVec<T> {
pub fn gbox<T>(value: T) -> GBox<T> {
Box::new_in(value, GpuAlloc::default())
}
pub fn leak<T: 'static>(val: T) -> Ptr<T> {
let b = gbox(val);
let leaked: &'static T = Box::leak(b);
Ptr::from(leaked)
}

View file

@ -827,6 +827,12 @@ impl DigitPermutation {
}
}
pub fn compute_radical_inverse_permutations(seed: u64) -> GVec<DigitPermutation> {
let mut result = gvec();
result.extend(PRIMES.iter().map(|&base| DigitPermutation::new(base as i32, seed)));
result
}
pub fn scrambled_radical_inverse(base_index: u32, mut a: u64, perm: &DigitPermutation) -> Float {
let base = PRIMES[base_index as usize] as u64;

View file

@ -6,7 +6,7 @@ use std::ops::Deref;
use std::path::Path;
pub trait CreateRGBToSpectrumTable {
fn from_data(z_nodes: &[Float], coeffs: &[Float]) -> Self;
fn new(z_nodes: &[Float], coeffs: &[Float]) -> Self;
fn load(base_dir: &Path, name: &str) -> Result<Self> where Self: Sized;
}

View file

@ -349,4 +349,20 @@ impl HostImage {
pub fn into_image(self) -> Image {
self.inner
}
pub fn resolution(&self) -> Point2i {
self.inner.resolution()
}
pub fn n_channels(&self) -> i32 {
self.inner.n_channels()
}
pub fn encoding(&self) -> ColorEncoding {
self.inner.encoding
}
pub fn format(&self) -> PixelFormat {
self.inner.format()
}
}

View file

@ -102,14 +102,13 @@ pub fn create_light(
arena,
),
"diffuse" => Err(anyhow!(
"{}: \"diffuse\" is an area light; use create_area_light with a shape",
"{}: \"diffuse\" is an area light. Use create_area_light with a shape",
loc
)),
_ => Err(anyhow!("{}: unknown light type \"{}\"", loc, name)),
}
}
/// Create a diffuse area light bound to a specific shape
pub fn create_area_light(
render_from_light: Transform,
medium: Option<Medium>,
@ -119,15 +118,10 @@ pub fn create_area_light(
alpha_tex: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
arena: &mut Arena,
) -> Result<Light> {
crate::lights::diffuse::create(
render_from_light,
medium,
parameters,
loc,
shape,
alpha_tex,
colorspace,
arena,
)
) -> Result<Ptr<Light>> {
let light = crate::lights::diffuse::create(
render_from_light, medium, parameters, loc,
shape, alpha_tex, colorspace, arena,
)?;
Ok(arena.alloc(light))
}

View file

@ -1,164 +1,610 @@
use shared::core::geometry::{Bounds3f, Point3i};
use shared::core::medium::{GridMedium, HGPhaseFunction, HomogeneousMedium, RGBGridMedium};
use crate::core::spectrum::spectrum_to_photometric;
use crate::spectra::SRGB;
use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{bail, Result};
use shared::core::geometry::{Bounds3f, Point3f, Point3i};
use shared::core::medium::{
GridMedium, HGPhaseFunction, HomogeneousMedium, MajorantGrid, Medium, RGBGridMedium,
};
use shared::core::spectrum::{Spectrum, SpectrumTrait};
use shared::spectra::{RGBIlluminantSpectrum, RGBUnboundedSpectrum, DenselySampledSpectrum};
use shared::core::texture::SpectrumType;
use shared::spectra::{
ConstantSpectrum, DenselySampledSpectrum, RGBIlluminantSpectrum, RGBUnboundedSpectrum,
};
use shared::utils::containers::SampledGrid;
use shared::{Float, Transform, core::medium::MajorantGrid};
use shared::{Float, Ptr, Transform};
pub trait RGBGridMediumCreator {
fn new(
bounds: &Bounds3f,
render_from_medium: &Transform,
g: Float,
sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_scale: Float,
le_grid: SampledGrid<RGBIlluminantSpectrum>,
le_scale: Float,
) -> Self;
struct MeasuredSS {
name: &'static str,
sigma_prime_s: [Float; 3],
sigma_a: [Float; 3],
}
impl RGBGridMediumCreator for RGBGridMedium {
#[allow(clippy::too_many_arguments)]
fn new(
bounds: &Bounds3f,
render_from_medium: &Transform,
g: Float,
sigma_a_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_s_grid: SampledGrid<RGBUnboundedSpectrum>,
sigma_scale: Float,
le_grid: SampledGrid<RGBIlluminantSpectrum>,
le_scale: Float,
) -> Self {
let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16));
for z in 0..majorant_grid.res.x() {
for y in 0..majorant_grid.res.y() {
for x in 0..majorant_grid.res.x() {
let bounds = majorant_grid.voxel_bounds(x, y, z);
let convert = |s: &RGBUnboundedSpectrum| s.max_value();
let max_sigma_t = sigma_a_grid.max_value_convert(bounds, convert)
+ sigma_s_grid.max_value_convert(bounds, convert);
majorant_grid.set(x, y, z, sigma_scale * max_sigma_t);
}
}
}
// From "A Practical Model for Subsurface Light Transport"
// Jensen, Marschner, Levoy, Hanrahan — SIGGRAPH 2001
// and "Acquiring Scattering Properties of Participating Media by Dilution"
// Narasimhan, Gupta, Donner, Ramamoorthi, Nayar, Jensen — SIGGRAPH 2006
static SUBSURFACE_TABLE: &[MeasuredSS] = &[
MeasuredSS {
name: "Apple",
sigma_prime_s: [2.29, 2.39, 1.97],
sigma_a: [0.0030, 0.0034, 0.046],
},
MeasuredSS {
name: "Chicken1",
sigma_prime_s: [0.15, 0.21, 0.38],
sigma_a: [0.015, 0.077, 0.19],
},
MeasuredSS {
name: "Chicken2",
sigma_prime_s: [0.19, 0.25, 0.32],
sigma_a: [0.018, 0.088, 0.20],
},
MeasuredSS {
name: "Cream",
sigma_prime_s: [7.38, 5.47, 3.15],
sigma_a: [0.0002, 0.0028, 0.0163],
},
MeasuredSS {
name: "Ketchup",
sigma_prime_s: [0.18, 0.07, 0.03],
sigma_a: [0.061, 0.97, 1.45],
},
MeasuredSS {
name: "Marble",
sigma_prime_s: [2.19, 2.62, 3.00],
sigma_a: [0.0021, 0.0041, 0.0071],
},
MeasuredSS {
name: "Potato",
sigma_prime_s: [0.68, 0.70, 0.55],
sigma_a: [0.0024, 0.0090, 0.12],
},
MeasuredSS {
name: "Skimmilk",
sigma_prime_s: [0.70, 1.22, 1.90],
sigma_a: [0.0014, 0.0025, 0.0142],
},
MeasuredSS {
name: "Skin1",
sigma_prime_s: [0.74, 0.88, 1.01],
sigma_a: [0.032, 0.17, 0.48],
},
MeasuredSS {
name: "Skin2",
sigma_prime_s: [1.09, 1.59, 1.79],
sigma_a: [0.013, 0.070, 0.145],
},
MeasuredSS {
name: "Spectralon",
sigma_prime_s: [11.6, 20.4, 14.9],
sigma_a: [0.00, 0.00, 0.00],
},
MeasuredSS {
name: "Wholemilk",
sigma_prime_s: [2.55, 3.21, 3.77],
sigma_a: [0.0011, 0.0024, 0.014],
},
MeasuredSS {
name: "Lowfat Milk",
sigma_prime_s: [0.89187, 1.5136, 2.532],
sigma_a: [0.002875, 0.00575, 0.0115],
},
MeasuredSS {
name: "Reduced Milk",
sigma_prime_s: [2.4858, 3.1669, 4.5214],
sigma_a: [0.0025556, 0.0051111, 0.012778],
},
MeasuredSS {
name: "Regular Milk",
sigma_prime_s: [4.5513, 5.8294, 7.136],
sigma_a: [0.0015333, 0.0046, 0.019933],
},
MeasuredSS {
name: "Espresso",
sigma_prime_s: [0.72378, 0.84557, 1.0247],
sigma_a: [4.7984, 6.5751, 8.8493],
},
MeasuredSS {
name: "Mint Mocha Coffee",
sigma_prime_s: [0.31602, 0.38538, 0.48131],
sigma_a: [3.772, 5.8228, 7.82],
},
MeasuredSS {
name: "Lowfat Soy Milk",
sigma_prime_s: [0.30576, 0.34233, 0.61664],
sigma_a: [0.0014375, 0.0071875, 0.035937],
},
MeasuredSS {
name: "Regular Soy Milk",
sigma_prime_s: [0.59223, 0.73866, 1.4693],
sigma_a: [0.0019167, 0.0095833, 0.065167],
},
MeasuredSS {
name: "Lowfat Chocolate Milk",
sigma_prime_s: [0.64925, 0.83916, 1.1057],
sigma_a: [0.0115, 0.0368, 0.1564],
},
MeasuredSS {
name: "Regular Chocolate Milk",
sigma_prime_s: [1.4585, 2.1289, 2.9527],
sigma_a: [0.010063, 0.043125, 0.14375],
},
MeasuredSS {
name: "Coke",
sigma_prime_s: [8.9053e-05, 8.372e-05, 0.0],
sigma_a: [0.10014, 0.16503, 0.2468],
},
MeasuredSS {
name: "Pepsi",
sigma_prime_s: [6.1697e-05, 4.2564e-05, 0.0],
sigma_a: [0.091641, 0.14158, 0.20729],
},
MeasuredSS {
name: "Sprite",
sigma_prime_s: [6.0306e-06, 6.4139e-06, 6.5504e-06],
sigma_a: [0.001886, 0.0018308, 0.0020025],
},
MeasuredSS {
name: "Gatorade",
sigma_prime_s: [0.0024574, 0.003007, 0.0037325],
sigma_a: [0.024794, 0.019289, 0.008878],
},
MeasuredSS {
name: "Chardonnay",
sigma_prime_s: [1.7982e-05, 1.3758e-05, 1.2023e-05],
sigma_a: [0.010782, 0.011855, 0.023997],
},
MeasuredSS {
name: "White Zinfandel",
sigma_prime_s: [1.7501e-05, 1.9069e-05, 1.288e-05],
sigma_a: [0.012072, 0.016184, 0.019843],
},
MeasuredSS {
name: "Merlot",
sigma_prime_s: [2.1129e-05, 0.0, 0.0],
sigma_a: [0.11632, 0.25191, 0.29434],
},
MeasuredSS {
name: "Budweiser Beer",
sigma_prime_s: [2.4356e-05, 2.4079e-05, 1.0564e-05],
sigma_a: [0.011492, 0.024911, 0.057786],
},
MeasuredSS {
name: "Coors Light Beer",
sigma_prime_s: [5.0922e-05, 4.301e-05, 0.0],
sigma_a: [0.006164, 0.013984, 0.034983],
},
MeasuredSS {
name: "Clorox",
sigma_prime_s: [0.0024035, 0.0031373, 0.003991],
sigma_a: [0.0033542, 0.014892, 0.026297],
},
MeasuredSS {
name: "Apple Juice",
sigma_prime_s: [0.00013612, 0.00015836, 0.000227],
sigma_a: [0.012957, 0.023741, 0.052184],
},
MeasuredSS {
name: "Cranberry Juice",
sigma_prime_s: [0.00010402, 0.00011646, 7.8139e-05],
sigma_a: [0.039437, 0.094223, 0.12426],
},
MeasuredSS {
name: "Grape Juice",
sigma_prime_s: [5.382e-05, 0.0, 0.0],
sigma_a: [0.10404, 0.23958, 0.29325],
},
MeasuredSS {
name: "Ruby Grapefruit Juice",
sigma_prime_s: [0.011002, 0.010927, 0.011036],
sigma_a: [0.085867, 0.18314, 0.25262],
},
MeasuredSS {
name: "White Grapefruit Juice",
sigma_prime_s: [0.22826, 0.23998, 0.32748],
sigma_a: [0.0138, 0.018831, 0.056781],
},
MeasuredSS {
name: "Shampoo",
sigma_prime_s: [0.0007176, 0.0008303, 0.0009016],
sigma_a: [0.014107, 0.045693, 0.061717],
},
MeasuredSS {
name: "Strawberry Shampoo",
sigma_prime_s: [0.00015671, 0.00015947, 1.518e-05],
sigma_a: [0.01449, 0.05796, 0.075823],
},
MeasuredSS {
name: "Head & Shoulders Shampoo",
sigma_prime_s: [0.023805, 0.028804, 0.034306],
sigma_a: [0.084621, 0.15688, 0.20365],
},
MeasuredSS {
name: "Lemon Tea Powder",
sigma_prime_s: [0.040224, 0.045264, 0.051081],
sigma_a: [2.4288, 4.5757, 7.2127],
},
MeasuredSS {
name: "Orange Powder",
sigma_prime_s: [0.00015617, 0.00017482, 0.0001762],
sigma_a: [0.001449, 0.003441, 0.007863],
},
MeasuredSS {
name: "Pink Lemonade Powder",
sigma_prime_s: [0.00012103, 0.00013073, 0.00012528],
sigma_a: [0.001165, 0.002366, 0.003195],
},
MeasuredSS {
name: "Cappuccino Powder",
sigma_prime_s: [1.8436, 2.5851, 2.1662],
sigma_a: [35.844, 49.547, 61.084],
},
MeasuredSS {
name: "Salt Powder",
sigma_prime_s: [0.027333, 0.032451, 0.031979],
sigma_a: [0.28415, 0.3257, 0.34148],
},
MeasuredSS {
name: "Sugar Powder",
sigma_prime_s: [0.00022272, 0.00025513, 0.000271],
sigma_a: [0.012638, 0.031051, 0.050124],
},
MeasuredSS {
name: "Suisse Mocha Powder",
sigma_prime_s: [2.7979, 3.5452, 4.3365],
sigma_a: [17.502, 27.004, 35.433],
},
MeasuredSS {
name: "Pacific Ocean Surface Water",
sigma_prime_s: [0.0001764, 0.00032095, 0.00019617],
sigma_a: [0.031845, 0.031324, 0.030147],
},
];
Self {
bounds: *bounds,
render_from_medium: *render_from_medium,
le_grid,
le_scale,
phase: HGPhaseFunction::new(g),
sigma_a_grid,
sigma_s_grid,
sigma_scale,
majorant_grid,
}
}
fn get_medium_scattering_properties(name: &str) -> Option<(Spectrum, Spectrum)> {
SUBSURFACE_TABLE.iter().find(|m| m.name == name).map(|m| {
let sigma_a = Spectrum::RGBUnbounded(RGBUnboundedSpectrum::new(&SRGB, m.sigma_a.into()));
let sigma_s =
Spectrum::RGBUnbounded(RGBUnboundedSpectrum::new(&SRGB, m.sigma_prime_s.into()));
(sigma_a, sigma_s)
})
}
pub trait GridMediumCreator {
fn new(
bounds: &Bounds3f,
render_from_medium: &Transform,
sigma_a: &Spectrum,
sigma_s: &Spectrum,
sigma_scale: Float,
g: Float,
density_grid: SampledGrid<Float>,
temperature_grid: Option<SampledGrid<Float>>,
le: &Spectrum,
le_scale: SampledGrid<Float>,
) -> Self;
}
impl GridMediumCreator for GridMedium {
#[allow(clippy::too_many_arguments)]
fn new(
bounds: &Bounds3f,
render_from_medium: &Transform,
sigma_a: &Spectrum,
sigma_s: &Spectrum,
sigma_scale: Float,
g: Float,
density_grid: SampledGrid<Float>,
temperature_grid: Option<SampledGrid<Float>>,
le: &Spectrum,
le_scale: SampledGrid<Float>,
) -> Self {
let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(sigma_a);
let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(sigma_s);
sigma_a_spec.scale(sigma_scale);
sigma_s_spec.scale(sigma_scale);
let le_spec = DenselySampledSpectrum::from_spectrum(le);
let mut majorant_grid = MajorantGrid::new(*bounds, Point3i::new(16, 16, 16)).device;
let is_emissive = if temperature_grid.is_some() {
true
} else {
Spectrum::Dense(le_spec.device()).max_value() > 0.
pub trait CreateMedium {
fn create(
name: &str,
parameters: &ParameterDictionary,
render_from_medium: Transform,
loc: &FileLoc,
arena: &Arena,
) -> Result<Ptr<Medium>> {
let medium = match name {
"homogeneous" => create_homogeneous(parameters, loc, arena)?,
"uniformgrid" => create_grid(parameters, render_from_medium, loc, arena)?,
"rgbgrid" => create_rgb_grid(parameters, render_from_medium, loc, arena)?,
// "cloud" => create_cloud(parameters, render_from_medium, loc, arena)?,
// "nanovdb" => create_nanovdb(parameters, render_from_medium, loc, arena)?,
_ => bail!("{}: unknown medium \"{}\"", loc, name),
};
Ok(arena.alloc(medium))
}
}
for z in 0..majorant_grid.res.z() {
for y in 0..majorant_grid.res.y() {
for x in 0..majorant_grid.res.x() {
let bounds = majorant_grid.voxel_bounds(x, y, z);
majorant_grid.set(x, y, z, density_grid.max_value(bounds));
}
fn create_homogeneous(
parameters: &ParameterDictionary,
loc: &FileLoc,
arena: &Arena,
) -> Result<Medium> {
let preset = parameters.get_one_string("preset", "")?;
let (mut sigma_a, mut sigma_s) = if !preset.is_empty() {
match get_medium_scattering_properties(&preset) {
Some(props) => (Some(props.0), Some(props.1)),
None => {
log::warn!("{}: material preset \"{}\" not found", loc, preset);
(None, None)
}
}
} else {
(None, None)
};
Self {
bounds: *bounds,
render_from_medium: *render_from_medium,
sigma_a_spec: sigma_a_spec.device(),
sigma_s_spec: sigma_s_spec.device(),
density_grid,
phase: HGPhaseFunction::new(g),
temperature_grid,
le_spec: le_spec.device(),
le_scale,
is_emissive,
majorant_grid,
if sigma_a.is_none() {
sigma_a = parameters
.get_one_spectrum("sigma_a", None, SpectrumType::Unbounded)
.or(Some(Spectrum::Constant(ConstantSpectrum::new(1.0))));
}
if sigma_s.is_none() {
sigma_s = parameters
.get_one_spectrum("sigma_s", None, SpectrumType::Unbounded)
.or(Some(Spectrum::Constant(ConstantSpectrum::new(1.0))));
}
let sigma_a = sigma_a.unwrap();
let sigma_s = sigma_s.unwrap();
let le = parameters
.get_one_spectrum("Le", None, SpectrumType::Illuminant)
.filter(|s| s.max_value() > 0.0);
let mut le_scale = parameters.get_one_float("Lescale", 1.0)?;
let le = match le {
Some(s) => {
le_scale /= spectrum_to_photometric(s);
s
}
None => Spectrum::Constant(ConstantSpectrum::new(0.0)),
};
let sigma_scale = parameters.get_one_float("scale", 1.0)?;
let g = parameters.get_one_float("g", 0.0)?;
let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(&sigma_a);
let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(&sigma_s);
let mut le_spec = DenselySampledSpectrum::from_spectrum(&le);
sigma_a_spec.scale(sigma_scale);
sigma_s_spec.scale(sigma_scale);
le_spec.scale(le_scale);
Ok(Medium::Homogeneous(HomogeneousMedium {
sigma_a_spec: arena.alloc(sigma_a_spec),
sigma_s_spec: arena.alloc(sigma_s_spec),
le_spec: arena.alloc(le_spec),
phase: HGPhaseFunction::new(g),
}))
}
fn create_grid(
parameters: &ParameterDictionary,
render_from_medium: shared::Transform,
loc: &FileLoc,
arena: &Arena,
) -> Result<Medium> {
let density = parameters.get_float_array("density")?;
if density.is_empty() {
bail!("{}: no \"density\" value provided for grid medium", loc);
}
let temperature = parameters.get_float_array("temperature")?;
if !temperature.is_empty() && temperature.len() != density.len() {
bail!(
"{}: different number of samples ({} vs {}) for \"density\" and \"temperature\"",
loc,
density.len(),
temperature.len()
);
}
let nx = parameters.get_one_int("nx", 1)?;
let ny = parameters.get_one_int("ny", 1)?;
let nz = parameters.get_one_int("nz", 1)?;
if density.len() as i32 != nx * ny * nz {
bail!(
"{}: grid medium has {} density values; expected nx*ny*nz = {}",
loc,
density.len(),
nx * ny * nz
);
}
let density_grid = SampledGrid::new(&density, nx, ny, nz);
let temperature_grid = if !temperature.is_empty() {
Some(SampledGrid::new(&temperature, nx, ny, nz))
} else {
None
};
let le = parameters.get_one_spectrum("Le", None, SpectrumType::Illuminant);
if le.is_some() && !temperature.is_empty() {
bail!(
"{}: both \"Le\" and \"temperature\" values were provided",
loc
);
}
let (le, le_norm) = match le.filter(|s| s.max_value() > 0.0) {
Some(s) => (s, 1.0 / spectrum_to_photometric(s)),
None => (Spectrum::Constant(ConstantSpectrum::new(0.0)), 1.0),
};
let le_scale_values = parameters.get_float_array("Lescale")?;
let le_grid = if le_scale_values.is_empty() {
SampledGrid::new(&[le_norm], 1, 1, 1)
} else {
if le_scale_values.len() as i32 != nx * ny * nz {
bail!(
"{}: expected {} values for \"Lescale\" but got {}",
loc,
nx * ny * nz,
le_scale_values.len()
);
}
let scaled: Vec<Float> = le_scale_values.iter().map(|v| v * le_norm).collect();
SampledGrid::new(&scaled, nx, ny, nz)
};
let p0 = parameters.get_one_point3f("p0", Point3f::new(0.0, 0.0, 0.0))?;
let p1 = parameters.get_one_point3f("p1", Point3f::new(1.0, 1.0, 1.0))?;
let bounds = Bounds3f::from_points(p0, p1);
let g = parameters.get_one_float("g", 0.0)?;
let sigma_a = parameters
.get_one_spectrum("sigma_a", None, SpectrumType::Unbounded)
.unwrap_or(Spectrum::Constant(ConstantSpectrum::new(1.0)));
let sigma_s = parameters
.get_one_spectrum("sigma_s", None, SpectrumType::Unbounded)
.unwrap_or(Spectrum::Constant(ConstantSpectrum::new(1.0)));
let sigma_scale = parameters.get_one_float("scale", 1.0)?;
let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(&sigma_a);
let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(&sigma_s);
sigma_a_spec.scale(sigma_scale);
sigma_s_spec.scale(sigma_scale);
let le_spec = DenselySampledSpectrum::from_spectrum(&le);
let is_emissive = if temperature_grid.is_some() {
true
} else {
le.max_value() > 0.0
};
let mut majorant_grid = MajorantGrid::new(bounds, Point3i::new(16, 16, 16));
for z in 0..majorant_grid.res.z() {
for y in 0..majorant_grid.res.y() {
for x in 0..majorant_grid.res.x() {
let voxel_bounds = majorant_grid.voxel_bounds(x, y, z);
majorant_grid.set(x, y, z, density_grid.max_value(voxel_bounds));
}
}
}
Ok(Medium::Grid(GridMedium {
bounds,
render_from_medium,
sigma_a_spec: arena.alloc(sigma_a_spec),
sigma_s_spec: arena.alloc(sigma_s_spec),
density_grid: arena.alloc(density_grid),
phase: HGPhaseFunction::new(g),
temperature_grid: arena.alloc_opt(temperature_grid),
le_spec: arena.alloc(le_spec),
le_scale: arena.alloc(le_grid),
is_emissive,
majorant_grid: arena.alloc(majorant_grid),
}))
}
pub trait HomogeneousMediumCreator {
fn new(
sigma_a: Spectrum,
sigma_s: Spectrum,
sigma_scale: Float,
le: Spectrum,
le_scale: Float,
g: Float,
) -> Self;
}
fn create_rgb_grid(
parameters: &ParameterDictionary,
render_from_medium: shared::Transform,
loc: &FileLoc,
arena: &Arena,
) -> Result<Medium> {
let sigma_a_rgb = parameters.get_rgb_array("sigma_a")?;
let sigma_s_rgb = parameters.get_rgb_array("sigma_s")?;
impl HomogeneousMediumCreator for HomogeneousMedium {
fn new(
sigma_a: Spectrum,
sigma_s: Spectrum,
sigma_scale: Float,
le: Spectrum,
le_scale: Float,
g: Float,
) -> Self {
let mut sigma_a_spec = DenselySampledSpectrum::from_spectrum(&sigma_a);
let mut sigma_s_spec = DenselySampledSpectrum::from_spectrum(&sigma_s);
let mut le_spec = DenselySampledSpectrum::from_spectrum(&le);
if sigma_a_rgb.is_empty() && sigma_s_rgb.is_empty() {
bail!("{}: RGB grid requires \"sigma_a\" and/or \"sigma_s\"", loc);
}
sigma_a_spec.scale(sigma_scale);
sigma_s_spec.scale(sigma_scale);
le_spec.scale(le_scale);
let n_density = if !sigma_a_rgb.is_empty() {
if !sigma_s_rgb.is_empty() && sigma_a_rgb.len() != sigma_s_rgb.len() {
bail!(
"{}: different number of samples ({} vs {}) for \"sigma_a\" and \"sigma_s\"",
loc,
sigma_a_rgb.len(),
sigma_s_rgb.len()
);
}
sigma_a_rgb.len()
} else {
sigma_s_rgb.len()
};
Self {
sigma_a_spec: sigma_a_spec.device(),
sigma_s_spec: sigma_s_spec.device(),
le_spec: le_spec.device(),
phase: HGPhaseFunction::new(g),
let le_rgb = parameters.get_rgb_array("Le")?;
if !le_rgb.is_empty() && sigma_a_rgb.is_empty() {
bail!(
"{}: RGB grid requires \"sigma_a\" if \"Le\" is provided",
loc
);
}
if !le_rgb.is_empty() && le_rgb.len() != n_density {
bail!(
"{}: expected {} values for \"Le\" but got {}",
loc,
n_density,
le_rgb.len()
);
}
let nx = parameters.get_one_int("nx", 1)?;
let ny = parameters.get_one_int("ny", 1)?;
let nz = parameters.get_one_int("nz", 1)?;
if n_density as i32 != nx * ny * nz {
bail!(
"{}: RGB grid medium has {} values; expected nx*ny*nz = {}",
loc,
n_density,
nx * ny * nz
);
}
let cs = parameters.colorspace();
let sigma_a_grid = if !sigma_a_rgb.is_empty() {
let spectra: Vec<RGBUnboundedSpectrum> = sigma_a_rgb
.iter()
.map(|rgb| RGBUnboundedSpectrum::new(cs, *rgb))
.collect();
Some(SampledGrid::new(&spectra, nx, ny, nz))
} else {
None
};
let sigma_s_grid = if !sigma_s_rgb.is_empty() {
let spectra: Vec<RGBUnboundedSpectrum> = sigma_s_rgb
.iter()
.map(|rgb| RGBUnboundedSpectrum::new(cs, *rgb))
.collect();
Some(SampledGrid::new(&spectra, nx, ny, nz))
} else {
None
};
let le_grid = if !le_rgb.is_empty() {
let spectra: Vec<RGBIlluminantSpectrum> = le_rgb
.iter()
.map(|rgb| RGBIlluminantSpectrum::new(cs, *rgb))
.collect();
Some(SampledGrid::new(&spectra, nx, ny, nz))
} else {
None
};
let p0 = parameters.get_one_point3f("p0", Point3f::new(0.0, 0.0, 0.0))?;
let p1 = parameters.get_one_point3f("p1", Point3f::new(1.0, 1.0, 1.0))?;
let bounds = Bounds3f::from_points(p0, p1);
let le_scale = parameters.get_one_float("Lescale", 1.0)?;
let g = parameters.get_one_float("g", 0.0)?;
let sigma_scale = parameters.get_one_float("scale", 1.0)?;
// Build majorant grid from combined sigma_a + sigma_s maxima
let mut majorant_grid = MajorantGrid::new(bounds, Point3i::new(16, 16, 16));
let convert = |s: &RGBUnboundedSpectrum| s.max_value();
for z in 0..majorant_grid.res.z() {
for y in 0..majorant_grid.res.y() {
for x in 0..majorant_grid.res.x() {
let voxel_bounds = majorant_grid.voxel_bounds(x, y, z);
let max_a = sigma_a_grid
.as_ref()
.map(|g| g.max_value_convert(voxel_bounds, convert))
.unwrap_or(1.0);
let max_s = sigma_s_grid
.as_ref()
.map(|g| g.max_value_convert(voxel_bounds, convert))
.unwrap_or(1.0);
majorant_grid.set(x, y, z, sigma_scale * (max_a + max_s));
}
}
}
Ok(Medium::RGBGrid(RGBGridMedium {
bounds,
render_from_medium,
phase: HGPhaseFunction::new(g),
le_scale,
sigma_scale,
sigma_a_grid: arena.alloc_opt(sigma_a_grid),
sigma_s_grid: arena.alloc_opt(sigma_s_grid),
le_grid: arena.alloc_opt(le_grid),
majorant_grid: arena.alloc(majorant_grid),
}))
}

View file

@ -556,7 +556,7 @@ impl ParserTarget for BasicSceneBuilder {
self.current_accelerator
.take()
.expect("Accelerator not set before WorldBegin"),
arena,
&arena,
);
Ok(())
}

View file

@ -12,7 +12,7 @@ use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::utils::parallel::{run_async, AsyncJob};
use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
use crate::utils::resolve_filename;
use crate::{Arena, FileLoc};
use crate::{Arena, ArenaUpload, FileLoc, Upload};
use anyhow::{anyhow, Result};
use parking_lot::Mutex;
use shared::core::camera::{Camera, CameraTransform};
@ -556,7 +556,7 @@ impl BasicScene {
loaded_shapes: &[Vec<Shape>],
shape_entities: &[ShapeSceneEntity],
textures: &NamedTextures,
arena: &mut Arena,
arena: &Arena,
) -> HashMap<usize, Vec<Light>> {
let light_state = self.light_state.lock();
let mut shape_lights: HashMap<usize, Vec<Light>> = HashMap::new();
@ -672,6 +672,53 @@ impl BasicScene {
(primitives, area_lights)
}
fn build_primitives_inner(
shapes: Vec<Ptr<Shape>>,
mtl: Material,
alpha_tex: &Option<Arc<FloatTexture>>,
mi: MediumInterface,
al_params: Option<&AreaLightEntity>,
render_from_light: Transform,
film_cs: Option<&RGBColorSpace>,
arena: &mut Arena,
area_lights: &mut Vec<Arc<Light>>,
) -> Vec<(Ptr<Shape>, Ptr<Light>, Ptr<GPUFloatTexture>)> {
shapes
.into_iter()
.map(|shape| {
let area_light_ptr = al_params
.and_then(|al_entity| {
let cs = al_entity.parameters.color_space.as_deref().or(film_cs);
let default_alpha = Arc::new(FloatTexture::default());
let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha);
crate::core::light::create_area_light(
render_from_light,
None,
&al_entity.parameters,
&al_entity.loc,
&shape,
alpha_ref,
cs,
arena,
)
.ok()
})
.unwrap_or(Ptr::null());
if !area_light_ptr.is_null() {
area_lights.push(Arc::new(unsafe { &*area_light_ptr.as_raw() }.clone()));
}
let alpha_ptr = alpha_tex
.as_ref()
.map(|t| arena.upload(t.as_ref()))
.unwrap_or(Ptr::null());
(shape, area_light_ptr, alpha_ptr)
})
.collect()
}
fn build_primitives_for_entity(
entity: &ShapeSceneEntity,
textures: &NamedTextures,
@ -727,52 +774,30 @@ impl BasicScene {
let al_params = entity.light_index.map(|idx| &light_state.area_lights[idx]);
for shape in shapes {
let area_light = al_params.and_then(|al_entity| {
let colorspace_ref = al_entity.parameters.color_space.as_deref().or(film_cs);
let default_alpha = Arc::new(FloatTexture::default());
let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha);
crate::core::light::create_area_light(
*entity.render_from_object,
None,
&al_entity.parameters,
&al_entity.loc,
&shape,
alpha_ref,
colorspace_ref,
arena,
)
.ok()
});
let uploaded_light = area_light
.as_ref()
.map(|l| l.upload(arena))
.unwrap_or(Ptr::null());
if let Some(ref light) = area_light {
area_lights.push(Arc::new(light.clone()));
}
let shape_ptr = shape.upload(arena);
let prim =
if uploaded_light.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() {
Primitive::Simple(SimplePrimitive::new(shape_ptr, Ptr::from(&mtl)))
} else {
Primitive::Geometric(GeometricPrimitive::new(
shape_ptr,
mtl.upload(arena),
uploaded_light,
mi.clone(),
alpha_tex
.as_ref()
.map(|t| t.upload(arena))
.unwrap_or(Ptr::null()),
))
};
let built = Self::build_primitives_inner(
shapes,
mtl,
&alpha_tex,
mi.clone(),
al_params,
*entity.render_from_object,
film_cs,
arena,
area_lights,
);
for (shape, light_ptr, alpha_ptr) in built {
let prim = if light_ptr.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() {
Primitive::Simple(SimplePrimitive::new(shape, Ptr::from(&mtl)))
} else {
Primitive::Geometric(GeometricPrimitive::new(
shape,
arena.alloc(mtl),
light_ptr,
mi.clone(),
alpha_ptr,
))
};
primitives.push(prim);
}
}
@ -839,56 +864,33 @@ impl BasicScene {
let al_params = entity.light_index.map(|idx| &light_state.area_lights[idx]);
for shape in shapes {
let area_light = al_params.and_then(|al_entity| {
let colorspace_ref = al_entity.parameters.color_space.as_deref().or(film_cs);
let default_alpha = Arc::new(FloatTexture::default());
let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha);
crate::core::light::create_area_light(
entity.transformed_base.render_from_object.start_transform,
None,
&al_entity.parameters,
&al_entity.loc,
&shape,
alpha_ref,
colorspace_ref,
arena,
)
.ok()
});
let uploaded_light = area_light
.as_ref()
.map(|l| l.upload(arena))
.unwrap_or(Ptr::null());
if let Some(ref light) = area_light {
area_lights.push(Arc::new(light.clone()));
}
let shape_ptr = shape.upload(arena);
let built = Self::build_primitives_inner(
shapes,
mtl,
&alpha_tex,
mi.clone(),
al_params,
entity.transformed_base.render_from_object.start_transform,
film_cs,
arena,
area_lights,
);
for (shape, light_ptr, alpha_ptr) in built {
let base_prim =
if uploaded_light.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() {
Primitive::Simple(SimplePrimitive::new(shape_ptr, Ptr::from(&mtl)))
if light_ptr.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() {
Primitive::Simple(SimplePrimitive::new(shape, Ptr::from(&mtl)))
} else {
Primitive::Geometric(GeometricPrimitive::new(
shape_ptr,
mtl.upload(arena),
uploaded_light,
shape,
arena.alloc(mtl),
light_ptr,
mi.clone(),
alpha_tex
.as_ref()
.map(|t| t.upload(arena))
.unwrap_or(Ptr::null()),
alpha_ptr,
))
};
let base_ptr = arena.alloc(base_prim);
primitives.push(Primitive::Animated(AnimatedPrimitive {
primitive: base_ptr,
primitive: arena.alloc(base_prim),
render_from_primitive: arena.alloc(entity.transformed_base.render_from_object),
}));
}
@ -937,10 +939,7 @@ impl BasicScene {
.get(&shape_ctx.entity_index)
.and_then(|lights| lights.get(shape_ctx.shape_index));
let area_light = shape_lights_opt
.map(|l| l.upload(arena))
.unwrap_or(Ptr::null());
let light_ptr = arena.alloc_opt(shape_lights_opt);
let shape_ptr = shape_ctx.shape;
let prim = if area_light.is_null() && !mi.is_medium_transition() && alpha_tex.is_none()
{
@ -948,10 +947,10 @@ impl BasicScene {
} else {
Primitive::Geometric(GeometricPrimitive::new(
shape_ptr,
mtl.upload(arena),
arena.alloc(mtl),
area_light,
mi.clone(),
alpha_tex.upload(arena),
arena.upload(alpha_tex),
))
};

View file

@ -1,12 +1,12 @@
use crate::core::texture::FloatTexture;
use crate::shapes::{BilinearPatchMesh, TriangleMesh};
use crate::utils::{Arena, FileLoc, ParameterDictionary, resolve_filename};
use crate::shapes::*;
use crate::utils::resolve_filename;
use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{anyhow, bail, Result};
use parking_lot::Mutex;
use shared::core::shape::*;
use shared::Ptr;
use shared::shapes::*;
use shared::utils::Transform;
use shared::{Transform, Ptr};
use std::collections::HashMap;
use std::sync::Arc;
@ -116,8 +116,8 @@ impl ShapeFactory for Shape {
global_store.push(host_arc.clone());
drop(global_store);
let n_tris = host_arc.device.n_triangles;
let mesh_ptr = Ptr::from(&host_arc.device);
let n_tris = host_arc..n_triangles;
let mesh_ptr = Ptr::from(&host_arc);
let shapes: Vec<Ptr<Shape>> = (0..n_tris)
.map(|i| {
let tri_shape = Shape::Triangle(TriangleShape {

View file

@ -19,7 +19,7 @@ pub fn get_spectrum_cache() -> &'static InternCache<DenselySampledSpectrum> {
pub fn spectrum_to_photometric(s: Spectrum) -> Float {
let effective_spectrum = match s {
Spectrum::RGBIlluminant(ill) => &Spectrum::Dense(*ill.illuminant),
Spectrum::RGBIlluminant(ill) => &Spectrum::Dense(ill.illuminant),
_ => &s,
};
effective_spectrum.inner_product(&cie_y())

View file

@ -1,5 +1,4 @@
use crate::textures::*;
use crate::utils::mipmap::{MIPMapFilterOptions, MIPMap};
use crate::utils::TextureParameterDictionary;
use crate::{Arena, FileLoc};
use anyhow::{anyhow, Result};
@ -9,8 +8,8 @@ use shared::core::geometry::Vector3f;
use shared::core::image::WrapMode;
use shared::core::texture::SpectrumType;
use shared::core::texture::{
CylindricalMapping, PlanarMapping, SphericalMapping, TextureEvalContext, TextureMapping2D,
UVMapping, GPUFloatTexture, GPUSpectrumTexture
CylindricalMapping, GPUFloatTexture, GPUSpectrumTexture, PlanarMapping, SphericalMapping,
TextureEvalContext, TextureMapping2D, UVMapping,
};
use shared::spectra::{SampledSpectrum, SampledWavelengths};
use shared::textures::*;
@ -30,6 +29,7 @@ pub trait SpectrumTextureTrait {
}
#[derive(Clone, Debug)]
#[enum_dispatch(FloatTextureTrait)]
pub enum FloatTexture {
Constant(FloatConstantTexture),
Checkerboard(FloatCheckerboardTexture),
@ -40,6 +40,7 @@ pub enum FloatTexture {
Mix(FloatMixTexture),
DirectionMix(FloatDirectionMixTexture),
// #[device(custom = "upload_image", variant_type = "GPUFloatImageTexture")]
Scaled(FloatScaledTexture),
Image(FloatImageTexture),
Bilerp(FloatBilerpTexture),
}
@ -105,14 +106,12 @@ impl FloatTexture {
}
}
#[derive(Clone, Debug)]
#[enum_dispatch(SpectrumTextureTrait)]
pub enum SpectrumTexture {
Constant(SpectrumConstantTexture),
Checkerboard(SpectrumCheckerboardTexture),
Dots(SpectrumDotsTexture),
// #[device(
// custom = "upload_spectrum_image",
// variant_type = "GPUSpectrumImageTexture"
// )]
Image(SpectrumImageTexture),
Bilerp(SpectrumBilerpTexture),
Scaled(SpectrumScaledTexture),
@ -133,12 +132,11 @@ impl SpectrumTexture {
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),
color_space: arena.alloc(
inner.base.mipmap.color_space
.clone()
.unwrap_or_else(crate::spectra::default_colorspace),
),
spectrum_type: inner.spectrum_type,
}
}
@ -264,3 +262,4 @@ pub struct TexInfo {
pub wrap_mode: WrapMode,
pub encoding: ColorEncoding,
}

View file

@ -65,10 +65,10 @@ pub static REC2020_COEFFS: Lazy<&[Float]> =
Lazy::new(|| strip_to_len(REC2020_COEFFS_BYTES, COEFFS_LEN));
pub static SRGB_TABLE: Lazy<RGBToSpectrumTable> =
Lazy::new(|| RGBToSpectrumTableData::new(SRGB_SCALE, SRGB_COEFFS));
Lazy::new(|| RGBToSpectrumTable::new(&SRGB_SCALE, &SRGB_COEFFS));
pub static DCI_P3_TABLE: Lazy<RGBToSpectrumTable> =
Lazy::new(|| RGBToSpectrumTableData::new(DCI_P3_SCALE.to_vec(), DCI_P3_COEFFS.to_vec()));
Lazy::new(|| RGBToSpectrumTable::new(&DCI_P3_SCALE, &DCI_P3_COEFFS));
pub static REC2020_TABLE: Lazy<RGBToSpectrumTable> =
Lazy::new(|| RGBToSpectrumTableData::new(REC2020_SCALE.to_vec(), REC2020_COEFFS.to_vec()));
Lazy::new(|| RGBToSpectrumTable::new(&REC2020_SCALE, &REC2020_COEFFS));
pub static ACES_TABLE: Lazy<RGBToSpectrumTable> =
Lazy::new(|| RGBToSpectrumTableData::new(ACES_SCALE.to_vec(), ACES_COEFFS.to_vec()));
Lazy::new(|| RGBToSpectrumTable::new(&ACES_SCALE, &ACES_COEFFS));

View file

@ -1,19 +1,19 @@
use super::RayIntegratorTrait;
use super::base::IntegratorBase;
use crate::Arena;
use super::RayIntegratorTrait;
use crate::core::camera::InitMetadata;
use crate::core::film::FilmTrait;
use crate::core::image::{Image, ImageIO, ImageMetadata};
use crate::globals::get_options;
use crate::spectra::get_spectra_context;
use crate::Arena;
use indicatif::{ProgressBar, ProgressStyle};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use shared::Float;
use shared::core::camera::{Camera, CameraTrait};
use shared::core::geometry::{Bounds2i, Point2i, VectorLike};
use shared::core::sampler::get_camera_sample;
use shared::core::sampler::{Sampler, SamplerTrait};
use shared::spectra::SampledSpectrum;
use shared::Float;
use std::io::Write;
use std::path::Path;
use std::sync::Arc;
@ -213,7 +213,7 @@ pub fn render<T>(
let film_image = film.get_image(&film_metadata, splat_scale);
let (mse_values, _mse_debug_img) =
film_image.mse(film_image.all_channels_desc(), ref_img, false);
film_image.mse(&film_image.all_channels_desc(), ref_img, false);
let mse_avg = mse_values.average();
if let Some(file) = &mut mse_out_file {

View file

@ -15,4 +15,4 @@ pub mod utils;
#[cfg(feature = "cuda")]
pub mod gpu;
pub use utils::{Arena, FileLoc, ParameterDictionary};
pub use utils::{Arena, FileLoc, ParameterDictionary, Upload, ArenaUpload};

View file

@ -5,6 +5,7 @@ use crate::core::light::lookup_spectrum;
use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture;
use crate::utils::resolve_filename;
use crate::utils::upload::ArenaUpload;
use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{anyhow, Result};
use shared::core::geometry::Point2i;
@ -12,11 +13,10 @@ use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::{Medium, MediumInterface};
use shared::core::shape::{Shape, ShapeTrait};
use shared::core::spectrum::Spectrum;
use shared::core::texture::GPUFloatTexture;
use shared::core::texture::{SpectrumType, TextureEvalContext};
use shared::lights::DiffuseAreaLight;
use shared::spectra::RGBColorSpace;
use shared::utils::{Ptr, Transform};
use shared::utils::Transform;
use shared::{Float, PI};
pub fn create(
@ -37,51 +37,49 @@ pub fn create(
let two_sided = params.get_one_bool("twosided", false)?;
let filename = resolve_filename(&params.get_one_string("filename", "")?);
let (image, image_color_space) = if !filename.is_empty() {
if l.is_some() {
return Err(anyhow!("{}: both \"L\" and \"filename\" specified", loc));
}
let (image, image_color_space): (Option<Image>, Option<RGBColorSpace>) =
if !filename.is_empty() {
if l.is_some() {
return Err(anyhow!("{}: both \"L\" and \"filename\" specified", loc));
}
let im = Image::read(Path::new(&filename), None)?;
let im = Image::read(Path::new(&filename), None)?;
if im.image.has_any_infinite_pixels() {
return Err(anyhow!("{}: image has infinite pixel values", loc));
}
if im.image.has_any_nan_pixels() {
return Err(anyhow!("{}: image has NaN pixel values", loc));
}
if im.image.has_any_infinite_pixels() {
return Err(anyhow!("{}: image has infinite pixel values", loc));
}
if im.image.has_any_nan_pixels() {
return Err(anyhow!("{}: image has NaN pixel values", loc));
}
let channel_desc = im
.image
.get_channel_desc(&["R", "G", "B"])
.map_err(|_| anyhow!("{}: image must have R, G, B channels", loc))?;
let channel_desc = im
.image
.get_channel_desc(&["R", "G", "B"])
.map_err(|_| anyhow!("{}: image must have R, G, B channels", loc))?;
let image = im.image.select_channels(&channel_desc);
let cs = im.metadata.get_colorspace();
let image = im.image.select_channels(&channel_desc);
let cs = im.metadata.get_colorspace();
(Some(image), cs)
} else {
if l.is_none() {
l = Some(illum_spec);
}
(None, None)
};
(Some(image), cs)
} else {
if l.is_none() {
l = Some(illum_spec);
}
(None, None)
};
let l_for_scale = l.as_ref().unwrap_or(&illum_spec);
scale /= spectrum_to_photometric(*l_for_scale);
let phi_v = params.get_one_float("power", -1.0)?;
if phi_v > 0.0 {
// k_e is the emissive power of the light as defined by the spectral
// distribution and texture and is used to normalize the emitted
// radiance such that the user-defined power will be the actual power
// emitted by the light.
let mut k_e: Float = 1.0;
if let Some(ref img) = image {
// Get the appropriate luminance vector from the image colour space
let lum_vec = image_color_space.unwrap().luminance_vector();
let lum_vec = image_color_space
.as_ref()
.expect("image present but no color space")
.luminance_vector();
let mut sum_k_e = 0.0;
let res = img.resolution();
@ -91,7 +89,6 @@ pub fn create(
let r = img.get_channel(Point2i::new(x, y), 0);
let g = img.get_channel(Point2i::new(x, y), 1);
let b = img.get_channel(Point2i::new(x, y), 2);
sum_k_e += r * lum_vec[0] + g * lum_vec[1] + b * lum_vec[2];
}
}
@ -100,21 +97,15 @@ pub fn create(
let side_factor = if two_sided { 2.0 } else { 1.0 };
k_e *= side_factor * shape.area() * PI;
// now multiply up scale to hit the target power
scale *= phi_v / k_e;
}
let alpha_ptr = alpha.upload(arena);
let is_constant_zero = match &*alpha_ptr {
GPUFloatTexture::Constant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0,
_ => false,
};
let (light_type, _) = if is_constant_zero {
(LightType::DeltaPosition, None)
// Upload alpha texture to GPU and check for constant-zero
let alpha_ptr = arena.upload(alpha);
let light_type = if alpha_ptr.is_constant_zero() {
LightType::DeltaPosition
} else {
(LightType::Area, Some(alpha))
LightType::Area
};
let mi = match medium {
@ -127,6 +118,7 @@ pub fn create(
}
None => MediumInterface::default(),
};
let base = LightBase::new(light_type, render_from_light, mi);
if let Some(ref img) = image {
@ -143,30 +135,20 @@ pub fn create(
let is_triangle_or_bilinear = matches!(*shape, Shape::Triangle(_) | Shape::BilinearPatch(_));
if render_from_light.has_scale(None) && !is_triangle_or_bilinear {
println!(
"Scaling detected in rendering to light space transformation! \
Proceed at your own risk; your image may have errors."
eprintln!(
"Warning: scaling detected in rendering-to-light transform; \
image may have errors."
);
}
let shape_ptr = shape.upload(arena);
let image_ptr = image
.as_ref()
.map(|img| arena.alloc(*img.device()))
.unwrap_or(Ptr::null());
let colorspace_ptr = image_color_space
.map(|cs| cs.upload(arena))
.unwrap_or(Ptr::null());
let lemit_ptr = arena.alloc(lookup_spectrum(l_for_scale).device());
let specific = DiffuseAreaLight {
base,
area: shape.area(),
shape: shape_ptr,
shape: arena.alloc(*shape),
alpha: alpha_ptr,
image: image_ptr,
colorspace: colorspace_ptr,
lemit: lemit_ptr,
image: arena.alloc(image),
colorspace: arena.alloc_opt(image_color_space),
lemit: arena.alloc((*lookup_spectrum(l_for_scale)).clone()),
two_sided,
scale,
};

View file

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

View file

@ -99,8 +99,8 @@ pub fn create(
let image_ptr = if !image.is_null() {
let distrib = PiecewiseConstant2D::from_image(&image);
let distrib_ptr = distrib.upload(arena);
let img_ptr = image.upload(arena);
let distrib_ptr = arena.alloc(distrib);
let img_ptr = arena.alloc(image);
(img_ptr, distrib_ptr)
} else {
(Ptr::null(), Ptr::null())
@ -108,7 +108,7 @@ pub fn create(
let specific = GoniometricLight {
base,
iemit: arena.alloc(iemit.device()),
iemit: arena.alloc(*iemit),
scale,
image: image_ptr.0,
distrib: image_ptr.1,

View file

@ -4,7 +4,7 @@ use crate::core::spectrum::spectrum_to_photometric;
use crate::spectra::get_spectra_context;
use crate::utils::resolve_filename;
use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D};
use crate::{Arena, FileLoc, ParameterDictionary};
use crate::{Arena, FileLoc, ParameterDictionary, ArenaUpload, Upload};
use anyhow::{anyhow, Result};
use rayon::prelude::*;
use shared::core::camera::CameraTransform;
@ -63,7 +63,7 @@ pub fn create(
}
let lemit = lookup_spectrum(&spectrum);
let light = UniformInfiniteLight::new(render_from_light, scale, lemit.upload(arena));
let light = UniformInfiniteLight::new(render_from_light, scale, arena.alloc(*lemit));
return Ok(Light::InfiniteUniform(light));
}
@ -140,9 +140,9 @@ fn create_image_light(
render_from_light,
scale,
image_ptr,
image_cs.upload(arena),
distrib.upload(arena),
compensated_distrib.upload(arena),
arena.alloc(image_cs),
arena.alloc(distrib),
arena.alloc(compensated_distrib),
);
Ok(Light::InfiniteImage(light))
@ -198,11 +198,11 @@ fn create_portal_light(
let light = PortalInfiniteLight::new(
render_from_light,
scale,
remapped.upload(arena),
image_cs.upload(arena),
arena.alloc(remapped),
arena.alloc(image_cs),
portal,
portal_frame,
distribution.upload(arena),
arena.alloc(distribution),
);
Ok(Light::InfinitePortal(light))

View file

@ -86,6 +86,6 @@ pub fn create(
None => MediumInterface::default(),
};
let specific = PointLight::new(final_render, mi, l, scale);
let specific = PointLight::new(final_render, mi, l, scale, arena);
Ok(Light::Point(specific))
}

View file

@ -42,7 +42,7 @@ impl CreateSpotLight for SpotLight {
);
let i = lookup_spectrum(&le);
let iemit = Ptr::from(&i.device());
let iemit = arena.alloc(i);
Self {
base,
iemit,

View file

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

View file

@ -2,15 +2,15 @@ use crate::core::image::Image;
use crate::core::material::CreateMaterial;
use crate::core::texture::SpectrumTexture;
use crate::spectra::get_colorspace_device;
use crate::{Arena, FileLoc};
use crate::utils::TextureParameterDictionary;
use crate::{Arena, FileLoc, Upload, ArenaUpload};
use anyhow::Result;
use shared::bxdfs::HairBxDF;
use shared::core::material::Material;
use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType;
use shared::materials::complex::*;
use shared::textures::SpectrumConstantTexture;
use anyhow::Result;
use std::collections::HashMap;
use std::sync::Arc;
@ -30,8 +30,7 @@ impl CreateMaterial for HairMaterial {
let pheomelanin = parameters.get_float_texture_or_null("pheomelanin")?;
let has_melanin = eumelanin.is_some() || pheomelanin.is_some();
// Default distribution if nothing is spceified
let sigma_a = if sigma_a.is_none() && !reflectance.is_none() && !has_melanin {
let sigma_a = if sigma_a.is_none() && reflectance.is_none() && !has_melanin {
let stdcs = get_colorspace_device();
let default_rgb = HairBxDF::sigma_a_from_concentration(1.3, 0.0, stdcs);
let spectrum = Spectrum::RGBUnbounded(default_rgb);
@ -45,21 +44,23 @@ impl CreateMaterial for HairMaterial {
let beta_m = parameters.get_float_texture("beta_m", 0.3)?;
let beta_n = parameters.get_float_texture("beta_n", 0.3)?;
let alpha = parameters.get_float_texture("alpha", 2.)?;
let material = HairMaterial::new(
sigma_a.upload(arena),
reflectance.upload(arena),
eumelanin.upload(arena),
pheomelanin.upload(arena),
eta.upload(arena),
beta_m.upload(arena),
beta_n.upload(arena),
alpha.upload(arena),
arena.upload(sigma_a),
arena.upload(reflectance),
arena.upload(eumelanin),
arena.upload(pheomelanin),
arena.upload(eta),
arena.upload(beta_m),
arena.upload(beta_n),
arena.upload(alpha),
);
Ok(Material::Hair(material))
}
}
impl CreateMaterial for SubsurfaceMaterial {
fn create(
_parameters: &TextureParameterDictionary,

View file

@ -1,6 +1,6 @@
use super::*;
use crate::globals::get_options;
use crate::utils::math::compute_radical_inverse_permutations;
use shared::utils::math::compute_radical_inverse_permutations;
use anyhow::{Result, anyhow};
use shared::core::geometry::Point2i;
use shared::core::sampler::{HaltonSampler, MAX_HALTON_RESOLUTION, RandomizeStrategy};
@ -21,7 +21,7 @@ impl CreateHaltonSampler for HaltonSampler {
randomize: RandomizeStrategy,
seed: u64,
) -> Self {
let (_, digit_permutations) = compute_radical_inverse_permutations(seed);
let digit_permutations = compute_radical_inverse_permutations(seed);
let mut base_scales = [0u64; 2];
let mut base_exponents = [0u64; 2];
let bases = [2, 3];

View file

@ -258,21 +258,29 @@ impl TriQuadMesh {
TriangleMesh::new(
render_from_object,
reverse_orientation,
self.tri_indices,
self.p,
self.n,
Vec::new(),
self.uv,
self.face_indices,
&self.tri_indices,
&self.p,
&self.n,
&Vec::new(),
&self.uv,
&self.face_indices,
)
}
}
impl TriangleMesh {
pub trait ReadTriangleMesh {
pub fn from_ply<P: AsRef<Path>>(
filename: P,
render_from_object: &Transform,
reverse_orientation: bool,
) -> AnyResult<Self>;
}
impl ReadTriangleMesh for TriangleMesh {
fn from_ply<P: AsRef<Path>>(
filename: P,
render_from_object: &Transform,
reverse_orientation: bool,
) -> AnyResult<Self> {
let mesh = TriQuadMesh::read_ply(filename)?;
Ok(mesh.into_triangle_mesh(render_from_object, reverse_orientation))

View file

@ -12,8 +12,8 @@ pub trait CreateRGBColorSpace {
r: Point2f,
g: Point2f,
b: Point2f,
illuminant: Arc<DenselySampledSpectrum>,
rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
illuminant: &DenselySampledSpectrum,
rgb_to_spectrum_table: &RGBToSpectrumTable,
) -> Self;
}
@ -22,11 +22,12 @@ impl CreateRGBColorSpace for RGBColorSpace {
r: Point2f,
g: Point2f,
b: Point2f,
illuminant: Arc<DenselySampledSpectrum>,
rgb_to_spectrum_table: Ptr<RGBToSpectrumTable>,
illuminant: &DenselySampledSpectrum,
rgb_to_spectrum_table: &RGBToSpectrumTable,
) -> Self {
let stdspec = get_spectra_context();
let illum_spectrum = Spectrum::Dense(illuminant.as_ref().clone());
let illum_ptr = Ptr::from(illuminant);
let illum_spectrum = Spectrum::Dense(illum_ptr);
let w_xyz: XYZ = illum_spectrum.to_xyz(&stdspec);
let w = w_xyz.xy();
@ -49,8 +50,8 @@ impl CreateRGBColorSpace for RGBColorSpace {
g,
b,
w,
illuminant: Ptr::from(illuminant.as_ref()),
rgb_to_spectrum_table,
illuminant: illum_ptr,
rgb_to_spectrum_table: Ptr::from(rgb_to_spectrum_table),
xyz_from_rgb,
rgb_from_xyz,
}

View file

@ -1,7 +1,7 @@
use shared::Float;
use shared::core::spectrum::Spectrum;
use shared::spectra::cie::*;
use shared::spectra::{PiecewiseLinearSpectrum, DenselySampledSpectrum};
use shared::spectra::{DenselySampledSpectrum, PiecewiseLinearSpectrum};
use shared::{gbox, leak, Float, Ptr};
use std::collections::HashMap;
use std::sync::LazyLock;
@ -17,7 +17,7 @@ pub fn create_cie(data: &[Float]) -> DenselySampledSpectrum {
.collect();
let buffer = PiecewiseLinearSpectrum::new(lambdas, data.to_vec());
let spec = Spectrum::Piecewise(buffer);
let spec = Spectrum::Piecewise(Ptr::from(&*buffer));
DenselySampledSpectrum::from_spectrum(&spec)
}
@ -27,7 +27,7 @@ pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(||
macro_rules! add {
($name:expr, $data:expr, $norm:expr) => {
let buffer = PiecewiseLinearSpectrum::from_interleaved($data, $norm);
let spectrum = Spectrum::Piecewise(buffer);
let spectrum = Spectrum::Piecewise(leak(buffer));
m.insert($name.to_string(), spectrum);
};
}
@ -49,7 +49,6 @@ pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(||
add!("stdillum-F12", &CIE_ILLUM_F12, true);
add!("illum-acesD60", &ACES_ILLUM_D60, true);
// --- Glasses ---
add!("glass-BK7", &GLASS_BK7_ETA, false);
add!("glass-BAF10", &GLASS_BAF10_ETA, false);
add!("glass-FK51A", &GLASS_FK51A_ETA, false);
@ -58,7 +57,6 @@ pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(||
add!("glass-F10", &GLASS_SF10_ETA, false);
add!("glass-F11", &GLASS_SF11_ETA, false);
// --- Metals ---
add!("metal-Ag-eta", &AG_ETA, false);
add!("metal-Ag-k", &AG_K, false);
add!("metal-Al-eta", &AL_ETA, false);
@ -74,87 +72,70 @@ pub static NAMED_SPECTRA: LazyLock<HashMap<String, Spectrum>> = LazyLock::new(||
add!("metal-TiO2-eta", &TIO2_ETA, false);
add!("metal-TiO2-k", &TIO2_K, false);
// --- Canon EOS 100D ---
add!("canon_eos_100d_r", &CANON_EOS_100D_R, false);
add!("canon_eos_100d_g", &CANON_EOS_100D_G, false);
add!("canon_eos_100d_b", &CANON_EOS_100D_B, false);
// --- Canon EOS 1DX MkII ---
add!("canon_eos_1dx_mkii_r", &CANON_EOS_1DX_MKII_R, false);
add!("canon_eos_1dx_mkii_g", &CANON_EOS_1DX_MKII_G, false);
add!("canon_eos_1dx_mkii_b", &CANON_EOS_1DX_MKII_B, false);
// --- Canon EOS 200D ---
add!("canon_eos_200d_r", &CANON_EOS_200D_R, false);
add!("canon_eos_200d_g", &CANON_EOS_200D_G, false);
add!("canon_eos_200d_b", &CANON_EOS_200D_B, false);
// --- Canon EOS 200D MkII ---
add!("canon_eos_200d_mkii_r", &CANON_EOS_200D_MKII_R, false);
add!("canon_eos_200d_mkii_g", &CANON_EOS_200D_MKII_G, false);
add!("canon_eos_200d_mkii_b", &CANON_EOS_200D_MKII_B, false);
// --- Canon EOS 5D ---
add!("canon_eos_5d_r", &CANON_EOS_5D_R, false);
add!("canon_eos_5d_g", &CANON_EOS_5D_G, false);
add!("canon_eos_5d_b", &CANON_EOS_5D_B, false);
// --- Canon EOS 5D MkII ---
add!("canon_eos_5d_mkii_r", &CANON_EOS_5D_MKII_R, false);
add!("canon_eos_5d_mkii_g", &CANON_EOS_5D_MKII_G, false);
add!("canon_eos_5d_mkii_b", &CANON_EOS_5D_MKII_B, false);
// --- Canon EOS 5D MkIII ---
add!("canon_eos_5d_mkiii_r", &CANON_EOS_5D_MKIII_R, false);
add!("canon_eos_5d_mkiii_g", &CANON_EOS_5D_MKIII_G, false);
add!("canon_eos_5d_mkiii_b", &CANON_EOS_5D_MKIII_B, false);
// --- Canon EOS 5D MkIV ---
add!("canon_eos_5d_mkiv_r", &CANON_EOS_5D_MKIV_R, false);
add!("canon_eos_5d_mkiv_g", &CANON_EOS_5D_MKIV_G, false);
add!("canon_eos_5d_mkiv_b", &CANON_EOS_5D_MKIV_B, false);
// --- Canon EOS 5DS ---
add!("canon_eos_5ds_r", &CANON_EOS_5DS_R, false);
add!("canon_eos_5ds_g", &CANON_EOS_5DS_G, false);
add!("canon_eos_5ds_b", &CANON_EOS_5DS_B, false);
// --- Canon EOS M ---
add!("canon_eos_m_r", &CANON_EOS_M_R, false);
add!("canon_eos_m_g", &CANON_EOS_M_G, false);
add!("canon_eos_m_b", &CANON_EOS_M_B, false);
// --- Hasselblad L1D 20C ---
add!("hasselblad_l1d_20c_r", &HASSELBLAD_L1D_20C_R, false);
add!("hasselblad_l1d_20c_g", &HASSELBLAD_L1D_20C_G, false);
add!("hasselblad_l1d_20c_b", &HASSELBLAD_L1D_20C_B, false);
// --- Nikon D810 ---
add!("nikon_d810_r", &NIKON_D810_R, false);
add!("nikon_d810_g", &NIKON_D810_G, false);
add!("nikon_d810_b", &NIKON_D810_B, false);
// --- Nikon D850 ---
add!("nikon_d850_r", &NIKON_D850_R, false);
add!("nikon_d850_g", &NIKON_D850_G, false);
add!("nikon_d850_b", &NIKON_D850_B, false);
// --- Sony ILCE 6400 ---
add!("sony_ilce_6400_r", &SONY_ILCE_6400_R, false);
add!("sony_ilce_6400_g", &SONY_ILCE_6400_G, false);
add!("sony_ilce_6400_b", &SONY_ILCE_6400_B, false);
// --- Sony ILCE 7M3 ---
add!("sony_ilce_7m3_r", &SONY_ILCE_7M3_R, false);
add!("sony_ilce_7m3_g", &SONY_ILCE_7M3_G, false);
add!("sony_ilce_7m3_b", &SONY_ILCE_7M3_B, false);
// --- Sony ILCE 7RM3 ---
add!("sony_ilce_7rm3_r", &SONY_ILCE_7RM3_R, false);
add!("sony_ilce_7rm3_g", &SONY_ILCE_7RM3_G, false);
add!("sony_ilce_7rm3_b", &SONY_ILCE_7RM3_B, false);
// --- Sony ILCE 9 ---
add!("sony_ilce_9_r", &SONY_ILCE_9_R, false);
add!("sony_ilce_9_g", &SONY_ILCE_9_G, false);
add!("sony_ilce_9_b", &SONY_ILCE_9_B, false);

View file

@ -11,7 +11,6 @@ use std::sync::LazyLock;
pub mod colorspace;
pub mod data;
pub mod dense;
pub mod piecewise;
pub static CIE_X_DATA: LazyLock<DenselySampledSpectrum> =
@ -28,19 +27,16 @@ fn get_d65_illuminant_buffer() -> Arc<DenselySampledSpectrum> {
}
pub fn cie_x() -> Spectrum {
Spectrum::Dense(CIE_X_DATA)
Spectrum::Dense(Ptr::from(&*CIE_X_DATA))
}
pub fn cie_y() -> Spectrum {
Spectrum::Dense(CIE_Y_DATA)
Spectrum::Dense(Ptr::from(&*CIE_Y_DATA))
}
pub fn cie_z() -> Spectrum {
Spectrum::Dense(CIE_Z_DATA)
Spectrum::Dense(Ptr::from(&*CIE_Z_DATA))
}
pub fn cie_d65() -> Spectrum {
Spectrum::Dense(CIE_D65_DATA)
Spectrum::Dense(Ptr::from(&*CIE_D65_DATA))
}
pub fn get_spectra_context() -> StandardSpectra {
@ -52,6 +48,7 @@ pub fn get_spectra_context() -> StandardSpectra {
}
}
pub static SRGB: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| {
let illum = get_d65_illuminant_buffer();
let r = Point2f::new(0.64, 0.33);
@ -59,7 +56,7 @@ pub static SRGB: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| {
let b = Point2f::new(0.15, 0.06);
let table_ptr = Ptr::from(&*SRGB_TABLE);
Arc::new(RGBColorSpace::new(r, g, b, illum, table_ptr))
Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr))
});
pub static DCI_P3: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| {
@ -68,7 +65,7 @@ pub static DCI_P3: LazyLock<Arc<RGBColorSpace>> = LazyLock::new(|| {
let g = Point2f::new(0.265, 0.690);
let b = Point2f::new(0.150, 0.060);
let table_ptr = Ptr::from(&*DCI_P3_TABLE);
Arc::new(RGBColorSpace::new(r, g, b, illum, table_ptr))
Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr))
});
pub static REC2020: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
@ -77,7 +74,7 @@ pub static REC2020: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
let g = Point2f::new(0.170, 0.797);
let b = Point2f::new(0.131, 0.046);
let table_ptr = Ptr::from(&*REC2020_TABLE);
Arc::new(RGBColorSpace::new(r, g, b, illum, table_ptr))
Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr))
});
pub static ACES: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
@ -85,8 +82,8 @@ pub static ACES: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
let r = Point2f::new(0.7347, 0.2653);
let g = Point2f::new(0.0000, 1.0000);
let b = Point2f::new(0.0001, -0.0770);
let table_ptr = Ptr::from(&ACES_TABLE.view);
Arc::new(RGBColorSpace::new(r, g, b, illum, table_ptr))
let table_ptr = Ptr::from(&ACES_TABLE);
Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr))
});
#[derive(Debug, Clone)]
@ -120,10 +117,10 @@ pub fn get_colorspace_context() -> StandardColorSpaces {
pub fn get_colorspace_device() -> DeviceStandardColorSpaces {
DeviceStandardColorSpaces {
srgb: Ptr::from(&*SRGB),
dci_p3: Ptr::from(&*DCI_P3),
rec2020: Ptr::from(&*REC2020),
aces2065_1: Ptr::from(&*ACES),
srgb: Ptr::from(&**SRGB),
dci_p3: Ptr::from(&**DCI_P3),
rec2020: Ptr::from(&**REC2020),
aces2065_1: Ptr::from(&**ACES),
}
}
@ -136,6 +133,11 @@ pub fn default_colorspace_arc() -> Arc<RGBColorSpace> {
Arc::new(default_colorspace())
}
pub fn default_colorspace_ref() -> &'static RGBColorSpace {
static CS: OnceLock<RGBColorSpace> = OnceLock::new();
CS.get_or_init(|| stdcs.srgb)
}
pub fn default_illuminant() -> Spectrum {
Spectrum::Dense(default_colorspace().illuminant)
}

View file

@ -21,7 +21,6 @@ use shared::utils::Transform;
use shared::Float;
use std::path::Path;
use std::sync::Arc;
// use crate::utils::{FileLoc, TextureParameterDictionary};
#[derive(Clone, Debug)]
pub struct ImageTextureBase {

View file

@ -1,59 +0,0 @@
use half::f16;
use shared::utils::hash::hash_buffer;
use shared::utils::math::{permutation_element, DigitPermutation, PRIMES};
use shared::Float;
#[inline(always)]
pub fn f16_to_f32(bits: u16) -> f32 {
#[cfg(feature = "cuda")]
{
// Use hardware intrinsic on CUDA
// Cast bits to cuda_f16, then cast to f32
let half_val = unsafe { core::mem::transmute::<u16, cuda_std::f16>(bits) };
half_val.to_f32()
}
#[cfg(feature = "vulkan")]
{
// Use shared logic or spirv-std intrinsics if available.
// Sadly, f16 support in rust-gpu is still maturing.
// A manual bit-conversion function is often safest here.
f16_to_f32_software(bits)
}
#[cfg(not(any(feature = "cuda", feature = "vulkan")))]
{
f16::from_bits(bits).to_f32()
}
}
pub fn compute_radical_inverse_permutations(seed: u64) -> (Vec<u16>, Vec<DigitPermutation>) {
let temp_data: Vec<Vec<u16>> = PRIMES
.iter()
.map(|&base| DigitPermutation::new(base as i32, seed).permutations)
.collect();
let mut storage: Vec<u16> = Vec::with_capacity(temp_data.iter().map(|v| v.len()).sum());
for vec in &temp_data {
storage.extend_from_slice(vec);
}
let mut views = Vec::with_capacity(PRIMES.len());
// let mut current_offset = 0;
// let storage_base_ptr = storage.as_ptr();
for (i, &base) in PRIMES.iter().enumerate() {
let len = temp_data[i].len();
let n_digits = len as u32 / base as u32;
// let ptr_to_data = storage_base_ptr.add(current_offset);
views.push(DigitPermutation::new(base as i32, n_digits as u64));
// current_offset += len;
}
(storage, views)
}

View file

@ -4,7 +4,6 @@ pub mod containers;
pub mod error;
pub mod file;
pub mod io;
pub mod math;
pub mod mipmap;
pub mod parallel;
pub mod parameters;
@ -19,6 +18,7 @@ pub use parameters::{
ParameterDictionary, ParsedParameter, ParsedParameterVector, TextureParameterDictionary,
};
pub use strings::*;
pub use upload::{Upload, ArenaUpload};
#[cfg(feature = "vulkan")]
pub type Arena = arena::Arena<backend::vulkan::VulkanAllocator>;
@ -28,3 +28,28 @@ pub type Arena = arena::Arena<backend::cuda::CudaAllocator>;
#[cfg(not(any(feature = "cuda", feature = "vulkan")))]
pub type Arena = arena::Arena<backend::SystemAllocator>;
use half::f16;
#[inline(always)]
pub fn f16_to_f32(bits: u16) -> f32 {
#[cfg(feature = "cuda")]
{
// Use hardware intrinsic on CUDA
// Cast bits to cuda_f16, then cast to f32
let half_val = unsafe { core::mem::transmute::<u16, cuda_std::f16>(bits) };
half_val.to_f32()
}
#[cfg(feature = "vulkan")]
{
// Use shared logic or spirv-std intrinsics if available.
// Keep an eye on rust-gpu
f16_to_f32_software(bits)
}
#[cfg(not(any(feature = "cuda", feature = "vulkan")))]
{
f16::from_bits(bits).to_f32()
}
}

View file

@ -1,7 +1,7 @@
use crate::core::spectrum::SPECTRUM_FILE_CACHE;
use crate::spectra::piecewise::ReadFromFile;
use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::spectra::data::get_named_spectrum;
use crate::spectra::piecewise::ReadFromFile;
use crate::utils::FileLoc;
use anyhow::{bail, Result};
use shared::core::color::RGB;
@ -136,6 +136,18 @@ impl PBRTParameter for bool {
}
}
impl PBRTParameter for RGB {
type Raw = Float;
const TYPE_NAME: &'static str = "rgb";
const N_PER_ITEM: usize = 3;
fn convert(v: &[Self::Raw]) -> Self {
RGB::new(v[0], v[1], v[2])
}
fn get_values(param: &ParsedParameter) -> &[Self::Raw] {
&param.floats
}
}
impl PBRTParameter for Float {
type Raw = Float;
const TYPE_NAME: &'static str = "float";
@ -281,6 +293,12 @@ impl ParameterDictionary {
Ok(dict)
}
pub fn colorspace(&self) -> &RGBColorSpace {
self.color_space
.as_deref()
.unwrap_or_else(|| crate::spectra::default_colorspace_ref())
}
fn check_parameter_types(&self) -> Result<()> {
for p in &self.params {
match p.type_name.as_str() {
@ -444,6 +462,10 @@ impl ParameterDictionary {
self.lookup_single(name, def)
}
pub fn get_one_rgb(&self, name: &str, def: RGB) -> Result<RGB> {
self.lookup_single(name, def)
}
pub fn get_float_array(&self, name: &str) -> Result<Vec<Float>> {
self.lookup_array(name)
}
@ -476,6 +498,10 @@ impl ParameterDictionary {
self.lookup_array(name)
}
pub fn get_rgb_array(&self, name: &str) -> Result<Vec<RGB>> {
self.lookup_array(name)
}
pub fn get_one_spectrum(
&self,
name: &str,
@ -655,11 +681,11 @@ impl ParameterDictionary {
})
.unzip();
vec![Spectrum::Piecewise(PiecewiseLinearSpectrum {
lambdas: lambdas.as_ptr().into(),
values: values.as_ptr().into(),
vec![Spectrum::Piecewise(leak(PiecewiseLinearSpectrum {
lambdas: gvec_from_slice(lambdas),
values: gvec_from_slice(values),
count: lambdas.len() as u32,
})]
}))]
}
fn extract_file_spectrum(&self, param: &ParsedParameter) -> Vec<Spectrum> {
@ -681,18 +707,18 @@ fn read_spectrum_from_file(filename: &str) -> Result<Spectrum, String> {
{
let cache = SPECTRUM_FILE_CACHE.lock();
if let Some(s) = cache.get(&fn_key) {
return Ok(s.clone());
return Ok(*s); // Spectrum is Copy, so just copy it
}
}
let pls = PiecewiseLinearSpectrum::read(&fn_key)
.ok_or_else(|| format!("unable to read or parse spectrum file '{}'", fn_key))?;
let spectrum = Spectrum::Piecewise(*pls);
let spectrum = Spectrum::Piecewise(leak(pls));
{
let mut cache = SPECTRUM_FILE_CACHE.lock();
cache.insert(fn_key, spectrum.clone());
cache.insert(fn_key, spectrum);
}
Ok(spectrum)

160
src/utils/upload.rs Normal file
View file

@ -0,0 +1,160 @@
use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::Arena;
use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture};
use shared::textures::*;
use shared::Ptr;
use std::sync::Arc;
pub trait Upload {
type Target;
fn upload(self, arena: &Arena) -> Self::Target;
}
fn convert_float(tex: &FloatTexture, arena: &Arena) -> GPUFloatTexture {
match tex {
FloatTexture::Constant(t) => GPUFloatTexture::Constant(*t),
FloatTexture::Bilerp(t) => GPUFloatTexture::Bilerp(*t),
FloatTexture::Checkerboard(t) => GPUFloatTexture::Checkerboard(*t),
FloatTexture::Dots(t) => GPUFloatTexture::Dots(*t),
FloatTexture::FBm(t) => GPUFloatTexture::FBm(*t),
FloatTexture::Windy(t) => GPUFloatTexture::Windy(*t),
FloatTexture::Wrinkled(t) => GPUFloatTexture::Wrinkled(*t),
FloatTexture::Scaled(t) => {
let tex = arena.alloc(convert_float(&t.tex, arena));
let scale = arena.alloc(convert_float(&t.scale, arena));
GPUFloatTexture::Scaled(GPUFloatScaledTexture { tex, scale })
}
FloatTexture::Mix(t) => {
let tex1 = arena.alloc(convert_float(&t.tex1, arena));
let tex2 = arena.alloc(convert_float(&t.tex2, arena));
let amount = arena.alloc(convert_float(&t.amount, arena));
GPUFloatTexture::Mix(GPUFloatMixTexture { tex1, tex2, amount })
}
FloatTexture::DirectionMix(t) => {
let tex1 = arena.alloc(convert_float(&t.tex1, arena));
let tex2 = arena.alloc(convert_float(&t.tex2, arena));
GPUFloatTexture::DirectionMix(GPUFloatDirectionMixTexture {
tex1,
tex2,
dir: t.dir,
})
}
FloatTexture::Image(t) => {
let tex_obj = arena.get_texture_object(&t.base.mipmap);
GPUFloatTexture::Image(GPUFloatImageTexture {
mapping: t.base.mapping,
tex_obj,
scale: t.base.scale,
invert: t.base.invert,
})
}
}
}
fn convert_spectrum(tex: &SpectrumTexture, arena: &Arena) -> GPUSpectrumTexture {
match tex {
SpectrumTexture::Constant(t) => GPUSpectrumTexture::Constant(*t),
SpectrumTexture::Bilerp(t) => GPUSpectrumTexture::Bilerp(*t),
SpectrumTexture::Checkerboard(t) => GPUSpectrumTexture::Checkerboard(*t),
SpectrumTexture::Dots(t) => GPUSpectrumTexture::Dots(*t),
SpectrumTexture::Marble(t) => GPUSpectrumTexture::Marble(*t),
SpectrumTexture::Scaled(t) => {
let tex = arena.alloc(convert_spectrum(&t.tex, arena));
let scale = arena.alloc(convert_float(&t.scale, arena));
GPUSpectrumTexture::Scaled(GPUSpectrumScaledTexture { tex, scale })
}
SpectrumTexture::Mix(t) => {
let tex1 = arena.alloc(convert_spectrum(&t.tex1, arena));
let tex2 = arena.alloc(convert_spectrum(&t.tex2, arena));
let amount = arena.alloc(convert_float(&t.amount, arena));
GPUSpectrumTexture::Mix(GPUSpectrumMixTexture { tex1, tex2, amount })
}
SpectrumTexture::DirectionMix(t) => {
let tex1 = arena.alloc(convert_spectrum(&t.tex1, arena));
let tex2 = arena.alloc(convert_spectrum(&t.tex2, arena));
GPUSpectrumTexture::DirectionMix(GPUSpectrumDirectionMixTexture {
tex1,
tex2,
dir: t.dir,
})
}
SpectrumTexture::Image(t) => {
let tex_obj = arena.get_texture_object(&t.base.mipmap);
GPUSpectrumTexture::Image(GPUSpectrumImageTexture {
mapping: t.base.mapping,
tex_obj,
scale: t.base.scale,
invert: t.base.invert,
is_single_channel: t.base.mipmap.is_single_channel(),
spectrum_type: t.spectrum_type,
color_space: arena.alloc(
t.base
.mipmap
.color_space
.clone()
.unwrap_or_else(crate::spectra::default_colorspace),
),
})
}
}
}
impl Upload for Arc<FloatTexture> {
type Target = Ptr<GPUFloatTexture>;
fn upload(self, arena: &Arena) -> Self::Target {
arena.alloc(convert_float(&self, arena))
}
}
impl Upload for Arc<SpectrumTexture> {
type Target = Ptr<GPUSpectrumTexture>;
fn upload(self, arena: &Arena) -> Self::Target {
arena.alloc(convert_spectrum(&self, arena))
}
}
impl Upload for &FloatTexture {
type Target = Ptr<GPUFloatTexture>;
fn upload(self, arena: &Arena) -> Self::Target {
arena.alloc(convert_float(&self, arena))
}
}
impl Upload for &SpectrumTexture {
type Target = Ptr<GPUSpectrumTexture>;
fn upload(self, arena: &Arena) -> Self::Target {
arena.alloc(convert_spectrum(&self, arena))
}
}
impl Upload for Option<Arc<FloatTexture>> {
type Target = Ptr<GPUFloatTexture>;
fn upload(self, arena: &Arena) -> Self::Target {
arena.alloc_opt(self.map(|v| convert_float(&v, arena)))
}
}
impl Upload for Option<Arc<SpectrumTexture>> {
type Target = Ptr<GPUSpectrumTexture>;
fn upload(self, arena: &Arena) -> Self::Target {
arena.alloc_opt(self.map(|v| convert_spectrum(&v, arena)))
}
}
pub trait ArenaUpload {
fn upload<T: Upload>(&self, value: T) -> T::Target;
}
impl ArenaUpload for Arena {
fn upload<T: Upload>(&self, value: T) -> T::Target {
value.upload(self)
}
}