From a14960562c5984322db3abac318f2e41afb21863 Mon Sep 17 00:00:00 2001 From: Wito Wiala Date: Wed, 20 May 2026 16:16:57 +0100 Subject: [PATCH] =?UTF-8?q?This=20commit=20is=20way=20too=20large.=20So,?= =?UTF-8?q?=20added=20Upload=20trait=20back=20again,=20but=20only=20for=20?= =?UTF-8?q?Textures,=20which=20are=20the=20most=20complex=20types.=20Fixin?= =?UTF-8?q?g=20Medium=20and=20PixelSensor=20creation=20on=20host=20side.?= =?UTF-8?q?=20Can=C2=B4t=20really=20find=20a=20satisfying=20way=20of=20kee?= =?UTF-8?q?ping=20construction=20and=20creation=20separate,=20so=20for=20n?= =?UTF-8?q?ow,=20regrettably,=20putting=20them=20in=20the=20same=20place.?= =?UTF-8?q?=20Added=20some=20types=20to=20parameter=20parsing.=20Continuin?= =?UTF-8?q?g=20fixup=20caused=20by=20creation=20of=20GVec=20and=20GBox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shared/src/materials/coated.rs | 8 +- src/core/color.rs | 2 +- src/core/image/mod.rs | 16 + src/core/light.rs | 20 +- src/core/medium.rs | 728 ++++++++++++++++++++++++++------- src/core/scene/builder.rs | 2 +- src/core/scene/scene.rs | 189 +++++---- src/core/shape.rs | 12 +- src/core/spectrum.rs | 2 +- src/core/texture.rs | 25 +- src/globals.rs | 8 +- src/integrators/pipeline.rs | 8 +- src/lib.rs | 2 +- src/lights/diffuse.rs | 106 ++--- src/lights/distant.rs | 2 +- src/lights/goniometric.rs | 6 +- src/lights/infinite.rs | 16 +- src/lights/point.rs | 2 +- src/lights/spot.rs | 2 +- src/materials/coated.rs | 46 +-- src/materials/complex.rs | 25 +- src/samplers/halton.rs | 2 +- src/shapes/mesh.rs | 10 +- src/spectra/colorspace.rs | 2 +- src/spectra/mod.rs | 14 +- src/textures/image.rs | 1 - src/utils/mod.rs | 2 +- src/utils/parameters.rs | 26 ++ src/utils/upload.rs | 160 ++++++++ 29 files changed, 1038 insertions(+), 406 deletions(-) create mode 100644 src/utils/upload.rs diff --git a/shared/src/materials/coated.rs b/shared/src/materials/coated.rs index 8b21457..a3813ba 100644 --- a/shared/src/materials/coated.rs +++ b/shared/src/materials/coated.rs @@ -40,8 +40,8 @@ impl CoatedDiffuseMaterial { thickness: Ptr, albedo: Ptr, g: Ptr, - eta: Ptr, displacement: Ptr, + eta: Ptr, normal_map: Ptr, 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, displacement: Ptr, interface_uroughness: Ptr, interface_vroughness: Ptr, @@ -166,6 +165,7 @@ pub struct CoatedConductorMaterial { conductor_eta: Ptr, k: Ptr, reflectance: Ptr, + normal_map: Ptr, 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, displacement: Ptr, interface_uroughness: Ptr, interface_vroughness: Ptr, thickness: Ptr, - interface_eta: Ptr, g: Ptr, albedo: Ptr, conductor_uroughness: Ptr, @@ -188,6 +186,8 @@ impl CoatedConductorMaterial { conductor_eta: Ptr, k: Ptr, reflectance: Ptr, + normal_map: Ptr, + interface_eta: Ptr, max_depth: u32, n_samples: u32, remap_roughness: bool, diff --git a/src/core/color.rs b/src/core/color.rs index fef7e3c..48880f1 100644 --- a/src/core/color.rs +++ b/src/core/color.rs @@ -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 where Self: Sized; } diff --git a/src/core/image/mod.rs b/src/core/image/mod.rs index 5a9b509..b256593 100644 --- a/src/core/image/mod.rs +++ b/src/core/image/mod.rs @@ -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() + } } diff --git a/src/core/light.rs b/src/core/light.rs index 1494914..a5a213c 100644 --- a/src/core/light.rs +++ b/src/core/light.rs @@ -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, @@ -119,15 +118,10 @@ pub fn create_area_light( alpha_tex: &FloatTexture, colorspace: Option<&RGBColorSpace>, arena: &mut Arena, -) -> Result { - crate::lights::diffuse::create( - render_from_light, - medium, - parameters, - loc, - shape, - alpha_tex, - colorspace, - arena, - ) +) -> Result> { + let light = crate::lights::diffuse::create( + render_from_light, medium, parameters, loc, + shape, alpha_tex, colorspace, arena, + )?; + Ok(arena.alloc(light)) } diff --git a/src/core/medium.rs b/src/core/medium.rs index 4297436..9d2d6b4 100644 --- a/src/core/medium.rs +++ b/src/core/medium.rs @@ -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, - sigma_s_grid: SampledGrid, - sigma_scale: Float, - le_grid: SampledGrid, - 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, - sigma_s_grid: SampledGrid, - sigma_scale: Float, - le_grid: SampledGrid, - 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, - temperature_grid: Option>, - le: &Spectrum, - le_scale: SampledGrid, - ) -> 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, - temperature_grid: Option>, - le: &Spectrum, - le_scale: SampledGrid, - ) -> 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> { + 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 { + 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 { + 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 = 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 { + 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 = 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 = 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 = 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), + })) } diff --git a/src/core/scene/builder.rs b/src/core/scene/builder.rs index b8c44b6..030fa67 100644 --- a/src/core/scene/builder.rs +++ b/src/core/scene/builder.rs @@ -556,7 +556,7 @@ impl ParserTarget for BasicSceneBuilder { self.current_accelerator .take() .expect("Accelerator not set before WorldBegin"), - arena, + &arena, ); Ok(()) } diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index eb2c0b3..970dfcb 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -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_entities: &[ShapeSceneEntity], textures: &NamedTextures, - arena: &mut Arena, + arena: &Arena, ) -> HashMap> { let light_state = self.light_state.lock(); let mut shape_lights: HashMap> = HashMap::new(); @@ -672,6 +672,53 @@ impl BasicScene { (primitives, area_lights) } + fn build_primitives_inner( + shapes: Vec>, + mtl: Material, + alpha_tex: &Option>, + mi: MediumInterface, + al_params: Option<&AreaLightEntity>, + render_from_light: Transform, + film_cs: Option<&RGBColorSpace>, + arena: &mut Arena, + area_lights: &mut Vec>, + ) -> Vec<(Ptr, Ptr, Ptr)> { + 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), )) }; diff --git a/src/core/shape.rs b/src/core/shape.rs index 6d9ce32..2a2c4cc 100644 --- a/src/core/shape.rs +++ b/src/core/shape.rs @@ -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> = (0..n_tris) .map(|i| { let tri_shape = Shape::Triangle(TriangleShape { diff --git a/src/core/spectrum.rs b/src/core/spectrum.rs index 70cb74e..0623932 100644 --- a/src/core/spectrum.rs +++ b/src/core/spectrum.rs @@ -19,7 +19,7 @@ pub fn get_spectrum_cache() -> &'static InternCache { 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()) diff --git a/src/core/texture.rs b/src/core/texture.rs index 252e120..a39065b 100644 --- a/src/core/texture.rs +++ b/src/core/texture.rs @@ -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,7 +40,7 @@ pub enum FloatTexture { Mix(FloatMixTexture), DirectionMix(FloatDirectionMixTexture), // #[device(custom = "upload_image", variant_type = "GPUFloatImageTexture")] - Scaled(FloatScaledTexture) + Scaled(FloatScaledTexture), Image(FloatImageTexture), Bilerp(FloatBilerpTexture), } @@ -107,14 +107,11 @@ 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), @@ -135,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, } } @@ -266,3 +262,4 @@ pub struct TexInfo { pub wrap_mode: WrapMode, pub encoding: ColorEncoding, } + diff --git a/src/globals.rs b/src/globals.rs index 2ac7618..899504c 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -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 = - Lazy::new(|| RGBToSpectrumTableData::new(SRGB_SCALE, SRGB_COEFFS)); + Lazy::new(|| RGBToSpectrumTable::new(&SRGB_SCALE, &SRGB_COEFFS)); pub static DCI_P3_TABLE: Lazy = - 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 = - 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 = - Lazy::new(|| RGBToSpectrumTableData::new(ACES_SCALE.to_vec(), ACES_COEFFS.to_vec())); + Lazy::new(|| RGBToSpectrumTable::new(&ACES_SCALE, &ACES_COEFFS)); diff --git a/src/integrators/pipeline.rs b/src/integrators/pipeline.rs index 492c04c..67f61de 100644 --- a/src/integrators/pipeline.rs +++ b/src/integrators/pipeline.rs @@ -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( 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 { diff --git a/src/lib.rs b/src/lib.rs index 6ea9678..b9f6278 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; diff --git a/src/lights/diffuse.rs b/src/lights/diffuse.rs index ab6069e..c75673c 100644 --- a/src/lights/diffuse.rs +++ b/src/lights/diffuse.rs @@ -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(¶ms.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, Option) = + if !filename.is_empty() { + if l.is_some() { + return Err(anyhow!("{}: both \"L\" and \"filename\" specified", loc)); + } - let im = Image::read(Path::new(&filename), None)?; + let im = 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, }; diff --git a/src/lights/distant.rs b/src/lights/distant.rs index 272498e..dcd88ac 100644 --- a/src/lights/distant.rs +++ b/src/lights/distant.rs @@ -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., diff --git a/src/lights/goniometric.rs b/src/lights/goniometric.rs index 2ba7633..376c613 100644 --- a/src/lights/goniometric.rs +++ b/src/lights/goniometric.rs @@ -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, diff --git a/src/lights/infinite.rs b/src/lights/infinite.rs index b53ec15..b225893 100644 --- a/src/lights/infinite.rs +++ b/src/lights/infinite.rs @@ -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)) diff --git a/src/lights/point.rs b/src/lights/point.rs index 4a5b35b..ed7833e 100644 --- a/src/lights/point.rs +++ b/src/lights/point.rs @@ -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)) } diff --git a/src/lights/spot.rs b/src/lights/spot.rs index 6ebecc5..5914ff0 100644 --- a/src/lights/spot.rs +++ b/src/lights/spot.rs @@ -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, diff --git a/src/materials/coated.rs b/src/materials/coated.rs index 2db4367..fc25d29 100644 --- a/src/materials/coated.rs +++ b/src/materials/coated.rs @@ -4,7 +4,7 @@ use crate::core::texture::SpectrumTexture; use crate::globals::get_options; use crate::spectra::data::get_named_spectrum; use crate::utils::TextureParameterDictionary; -use crate::{Arena, FileLoc}; +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, diff --git a/src/materials/complex.rs b/src/materials/complex.rs index c0bc02d..5058062 100644 --- a/src/materials/complex.rs +++ b/src/materials/complex.rs @@ -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, diff --git a/src/samplers/halton.rs b/src/samplers/halton.rs index 85da4fb..9e60d30 100644 --- a/src/samplers/halton.rs +++ b/src/samplers/halton.rs @@ -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]; diff --git a/src/shapes/mesh.rs b/src/shapes/mesh.rs index d43d5b1..b4a9a0c 100644 --- a/src/shapes/mesh.rs +++ b/src/shapes/mesh.rs @@ -268,11 +268,19 @@ impl TriQuadMesh { } } -impl TriangleMesh { +pub trait ReadTriangleMesh { pub fn from_ply>( filename: P, render_from_object: &Transform, reverse_orientation: bool, + ) -> AnyResult; +} + +impl ReadTriangleMesh for TriangleMesh { + fn from_ply>( + filename: P, + render_from_object: &Transform, + reverse_orientation: bool, ) -> AnyResult { let mesh = TriQuadMesh::read_ply(filename)?; Ok(mesh.into_triangle_mesh(render_from_object, reverse_orientation)) diff --git a/src/spectra/colorspace.rs b/src/spectra/colorspace.rs index bf8efb9..e289966 100644 --- a/src/spectra/colorspace.rs +++ b/src/spectra/colorspace.rs @@ -27,7 +27,7 @@ impl CreateRGBColorSpace for RGBColorSpace { ) -> Self { let stdspec = get_spectra_context(); let illum_ptr = Ptr::from(illuminant); - let illum_spectrum = Spectrum::Dense(illuminant); + let illum_spectrum = Spectrum::Dense(illum_ptr); let w_xyz: XYZ = illum_spectrum.to_xyz(&stdspec); let w = w_xyz.xy(); diff --git a/src/spectra/mod.rs b/src/spectra/mod.rs index 4782c93..ed353e5 100644 --- a/src/spectra/mod.rs +++ b/src/spectra/mod.rs @@ -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 = @@ -57,7 +56,7 @@ pub static SRGB: LazyLock> = 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> = LazyLock::new(|| { @@ -66,7 +65,7 @@ pub static DCI_P3: LazyLock> = 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> = LazyLock::new(|| { @@ -75,7 +74,7 @@ pub static REC2020: LazyLock> = 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> = LazyLock::new(|| { @@ -84,7 +83,7 @@ pub static ACES: LazyLock> = LazyLock::new(|| { let g = Point2f::new(0.0000, 1.0000); let b = Point2f::new(0.0001, -0.0770); let table_ptr = Ptr::from(&ACES_TABLE); - Arc::new(RGBColorSpace::new(r, g, b, illum, table_ptr)) + Arc::new(RGBColorSpace::new(r, g, b, &illum, &table_ptr)) }); #[derive(Debug, Clone)] @@ -134,6 +133,11 @@ pub fn default_colorspace_arc() -> Arc { Arc::new(default_colorspace()) } +pub fn default_colorspace_ref() -> &'static RGBColorSpace { + static CS: OnceLock = OnceLock::new(); + CS.get_or_init(|| stdcs.srgb) +} + pub fn default_illuminant() -> Spectrum { Spectrum::Dense(default_colorspace().illuminant) } diff --git a/src/textures/image.rs b/src/textures/image.rs index a817d04..d2b6106 100644 --- a/src/textures/image.rs +++ b/src/textures/image.rs @@ -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 { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 06c6280..8051eb9 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -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; diff --git a/src/utils/parameters.rs b/src/utils/parameters.rs index dcc1e17..eb0df57 100644 --- a/src/utils/parameters.rs +++ b/src/utils/parameters.rs @@ -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] { + ¶m.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 { + self.lookup_single(name, def) + } + pub fn get_float_array(&self, name: &str) -> Result> { self.lookup_array(name) } @@ -476,6 +498,10 @@ impl ParameterDictionary { self.lookup_array(name) } + pub fn get_rgb_array(&self, name: &str) -> Result> { + self.lookup_array(name) + } + pub fn get_one_spectrum( &self, name: &str, diff --git a/src/utils/upload.rs b/src/utils/upload.rs new file mode 100644 index 0000000..ca89112 --- /dev/null +++ b/src/utils/upload.rs @@ -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 { + type Target = Ptr; + fn upload(self, arena: &Arena) -> Self::Target { + arena.alloc(convert_float(&self, arena)) + } +} + +impl Upload for Arc { + type Target = Ptr; + fn upload(self, arena: &Arena) -> Self::Target { + arena.alloc(convert_spectrum(&self, arena)) + } +} + +impl Upload for &FloatTexture { + type Target = Ptr; + fn upload(self, arena: &Arena) -> Self::Target { + arena.alloc(convert_float(&self, arena)) + } +} + +impl Upload for &SpectrumTexture { + type Target = Ptr; + fn upload(self, arena: &Arena) -> Self::Target { + arena.alloc(convert_spectrum(&self, arena)) + } +} +impl Upload for Option> { + type Target = Ptr; + fn upload(self, arena: &Arena) -> Self::Target { + arena.alloc_opt(self.map(|v| convert_float(&v, arena))) + } +} + +impl Upload for Option> { + type Target = Ptr; + fn upload(self, arena: &Arena) -> Self::Target { + arena.alloc_opt(self.map(|v| convert_spectrum(&v, arena))) + } +} + +pub trait ArenaUpload { + fn upload(&self, value: T) -> T::Target; +} + +impl ArenaUpload for Arena { + fn upload(&self, value: T) -> T::Target { + value.upload(self) + } +}