Implementing arena based memory allocation

This commit is contained in:
pingu 2026-01-10 00:16:24 +00:00
parent f7c47be077
commit 0ef563d1a5
9 changed files with 1036 additions and 179 deletions

View file

@ -1,55 +1,34 @@
use shared::Float;
use shared::core::ligh::{Light, LightBase, LightType};
use shared::core::medium::MediumInterface;
use shared::core::shape::Shape;
use shared::core::spectrum::{Spectrum, SpectrumTrait};
use shared::core::texture::SpectrumType;
use shared::lights::DiffuseAreaLight;
use shared::spectra::RGBColorSpace;
use std::sync::Arc;
use crate::core::image::{Image, ImageIO};
use crate::core::light::{CreateLight, lookup_spectrum};
use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::{ use crate::core::texture::{
FloatTexture, FloatTextureTrait, TextureEvalContext, TextureEvaluator, FloatTexture, FloatTextureTrait, TextureEvalContext, TextureEvaluator,
UniversalTextureEvaluator, UniversalTextureEvaluator,
}; };
use crate::shapes::{Shape, ShapeSample, ShapeSampleContext, ShapeTrait}; use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename};
use crate::utils::hash::hash_float;
use shared::core::color::RGB;
use shared::core::geometry::{
Bounds3f, Normal3f, Point2f, Point2fi, Point2i, Point3f, Point3fi, Ray, Vector3f, VectorLike,
};
use shared::core::interaction::{
Interaction, InteractionTrait, SimpleInteraction, SurfaceInteraction,
};
use shared::core::light::{
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
};
use shared::core::medium::MediumInterface;
use shared::core::spectrum::{Spectrum, SpectrumTrait};
use shared::images::Image;
use shared::spectra::{
DenselySampledSpectrum, RGBColorSpace, RGBIlluminantSpectrum, SampledSpectrum,
SampledWavelengths,
};
use shared::{Float, PI};
use std::sync::Arc;
#[derive(Clone, Debug)] impl CreateLight for DiffuseAreaLight {
pub struct DiffuseAreaLightStorage { fn new(
shape: Arc<Shape>, render_from_light: shared::utils::Transform,
alpha: Option<Arc<GPUFloatTexture>>,
image: Option<Arc<Image>>,
image_color_space: Option<Arc<RGBColorSpace>>,
}
#[derive(Clone, Debug)]
pub struct DiffuseAreaLightHost {
pub view: DiffuseAreaLight,
_storage: DiffuseAreaLightStorage,
}
impl DiffuseAreaLightHost {
#[allow(clippy::too_many_arguments)]
pub fn new(
render_from_light: Transform,
medium_interface: MediumInterface, medium_interface: MediumInterface,
le: Spectrum, le: Spectrum,
scale: Float, scale: Float,
shape: Shape, shape: Option<shared::utils::Ptr<Shape>>,
alpha: FloatTexture, alpha: Option<shared::utils::Ptr<FloatTexture>>,
image: Option<Image>, image: Option<shared::utils::Ptr<Image>>,
image_color_space: Option<Arc<RGBColorSpace>>, image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
two_sided: bool, two_sided: Option<bool>,
fov: Option<Float>,
) -> Self { ) -> Self {
let is_constant_zero = match &alpha { let is_constant_zero = match &alpha {
FloatTexture::FloatConstant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0, FloatTexture::FloatConstant(tex) => tex.evaluate(&TextureEvalContext::default()) == 0.0,
@ -85,28 +64,117 @@ impl DiffuseAreaLightHost {
if render_from_light.has_scale(None) && !is_triangle_or_bilinear { if render_from_light.has_scale(None) && !is_triangle_or_bilinear {
println!( println!(
"Scaling detected in rendering to light space transformation! \ "Scaling detected in rendering to light space transformation! \
The system has numerous assumptions, implicit and explicit, \ The system has numerous assumptions, implicit and explicit, \
that this transform will have no scale factors in it. \ that this transform will have no scale factors in it. \
Proceed at your own risk; your image may have errors." Proceed at your own risk; your image may have errors."
); );
} }
let storage = DiffuseAreaLightStorage { Self {
shape,
alpha: stored_alpha,
image,
image_color_space,
};
let view = DiffuseAreaLight {
base, base,
area: shape.area(), area: shape.area(),
image,
image_color_space,
shape: Ptr::from(&*storage.shape), shape: Ptr::from(&*storage.shape),
}; alpha: stored_alpha,
lemit,
Self { two_sided,
view, scale,
_storage: storage,
} }
} }
fn create(
arena: &mut Arena,
render_from_light: Transform,
medium: Medium,
params: &ParameterDictionary,
loc: &FileLoc,
shape: &Shape,
alpha_text: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
) -> Light {
let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant);
let mut scale = params.get_one_float("scale", 1.);
let two_sided = params.get_one_bool("twosided", false);
let filename = resolve_filename(params.get_one_string("filename", ""));
let mut image_color_space = colorspace.clone();
if !filename.is_empty() {
if l.is_some() {
panic!(
"{}: Both \"L\" and \"filename\" specified for DiffuseAreaLight.",
loc
);
}
let im = Image::read(filename, None);
let image: Image = im.image;
if image.has_any_infinite_pixels() {
panic!(
"{:?}: Image '{}' has NaN pixels. Not suitable for light.",
loc, filename
);
}
let channel_desc = image.get_channel_desc(&["R", "G", "B"]).expect(&format!(
"{:?}: Image '{}' must have R, G, B channels",
loc, filename
));
let selected_image = image.select_channels(channel_desc);
image_color_space = im.metadata.colorspace;
} else if l.is_none() {
l = Some(colorpace.unwrap().Illuminant.clone());
}
let l_for_scale = l.as_ref().unwrap_or(&colorspace.illuminant);
let lemit_data = lookup_spectrum(l_for_scale);
scale /= spectrum_to_photometric(lemit_data);
let phi_v = parameters.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_host {
// Get the appropriate luminance vector from the image colour space
let lum_vec = image_color_space.luminance_vector();
let mut sum_k_e = 0.0;
let res = img.resolution;
for y in 0..res.y {
for x in 0..res.x {
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];
}
}
k_e = sum_k_e / (res.x * res.y) as Float;
}
let side_factor = if two_sided { 2.0 } else { 1.0 };
k_e *= side_factor * shape_data.area() * PI;
// now multiply up scale to hit the target power
scale *= phi_v / k_e;
}
let specific = DiffuseAreaLight::new(
render_from_light,
medium.into(),
l,
scale,
shape.upload(arena),
alpha.upload(arena),
image.upload(arena),
image_color_space.upload(arena),
Some(true),
shape.area(),
);
Light::DiffuseArea(specific)
}
} }

108
src/lights/distant.rs Normal file
View file

@ -0,0 +1,108 @@
use crate::core::light::{CreateLight, lookup_spectrum};
use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture;
use crate::utils::{Arena, FileLoc, ParameterDictionary};
use shared::core::geometry::{Vector3f, VectorLike};
use shared::core::light::{Light, LightBase};
use shared::core::medium::{Medium, MediumInterface};
use shared::core::shape::Shape;
use shared::lights::DistantLight;
use shared::spectra::RGBColorSpace;
use shared::utils::Transform;
impl CreateLight for DistantLight {
fn new(
render_from_light: Transform,
medium_interface: shared::core::medium::MediumInterface,
le: shared::core::spectrum::Spectrum,
scale: shared::Float,
shape: Option<shared::utils::Ptr<Shape>>,
alpha: Option<shared::utils::Ptr<FloatTexture>>,
image: Option<shared::utils::Ptr<Image>>,
image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
two_sided: Option<bool>,
fov: Option<shared::Float>,
) -> Self {
let base = LightBase::new(
LightType::DeltaDirection,
render_from_light,
MediumInterface::empty(),
);
let lemit = lookup_spectrum(le);
Self {
base,
lemit,
scale,
scene_center: Vector3f::default(),
scene_radius: 0.,
}
}
fn create(
arena: &mut Arena,
render_from_light: Transform,
medium: Medium,
parameters: &ParameterDictionary,
loc: &FileLoc,
shape: &Shape,
alpha_text: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
) -> Light {
let l = parameters
.get_one_spectrum(
"L",
colorspace.unwrap().illuminant,
SpectrumType::Illuminant,
)
.unwrap();
let lemit = lookup_spectrum(l);
let mut scale = parameters.get_one_float("scale", 1);
let from = parameters.get_one_point3f("from", Point3f::new(0., 0., 0.));
let to = parameters.get_one_point3f("to", Point3f(0., 0., 1.));
let w = (from - to).normalize();
let (v1, v2) = w.coordinate_system();
let m: [Float; 16] = [
v1.x(),
v2.x(),
w.x(),
0.,
v1.y(),
v2.y(),
w.y(),
0.,
v1.z(),
v2.z(),
w.z(),
0.,
0.,
0.,
0.,
1.,
];
let t = Transform::from_flat(m);
let final_render = render_from_light * t;
scale /= spectrum_to_photometric(l.unwrap());
// Adjust scale to meet target illuminance value
// Like for IBLs we measure illuminance as incident on an upward-facing
// patch.
let e_v = parameters.get_one_float("illuminance", -1.);
if e_v > 0. {
sc *= e_v;
}
let specific = DistantLight::new(
final_render,
medium.into(),
le,
scale,
None,
None,
None,
None,
None,
None,
);
Light::Distant(specific)
}
}

View file

@ -1,12 +1,31 @@
use crate::core::light::{LightBaseTrait, LightFactory}; use crate::core::image::{Image, ImageIO, ImageMetadata};
use crate::core::light::{CreateLight, lookup_spectrum};
use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture;
use crate::utils::sampling::PiecewiseConstant2D;
use crate::utils::{Arena, FileLoc, ParameterDictionary, resolve_filename};
use shared::core::image::ImageBase;
use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::{Medium, MediumInterface};
use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType;
use shared::lights::GoniometricLight;
use shared::spectra::RGBColorSpace;
use shared::utils::Transform;
use shared::utils::containers::Array2D;
impl LightFactory for GoniometricLight { impl CreateLight for GoniometricLight {
fn new( fn new(
render_from_light: Transform, render_from_light: Transform,
medium_interface: MediumInterface, medium_interface: MediumInterface,
scale: Float, le: Spectrum,
iemit: &Spectrum, scale: shared::Float,
image: &Image, shape: Option<shared::utils::Ptr<Shape>>,
alpha: Option<shared::utils::Ptr<FloatTexture>>,
image: Option<shared::utils::Ptr<Image>>,
image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
two_sided: Option<bool>,
fov: Option<shared::Float>,
) -> Self { ) -> Self {
let base = LightBase::new( let base = LightBase::new(
LightType::DeltaPosition, LightType::DeltaPosition,
@ -14,15 +33,142 @@ impl LightFactory for GoniometricLight {
medium_interface, medium_interface,
); );
let i_interned = LightBase::lookup_spectrum(&iemit); let iemit = lookup_spectrum(le);
let d = image.get_sampling_distribution_uniform(); let d = image.unwrap().get_sampling_distribution_uniform();
let distrib = PiecewiseConstant2D::new_with_data(&d); let distrib = PiecewiseConstant2D::new_with_data(d);
Self { Self {
base, base,
iemit: i_interned, iemit,
scale, scale,
image: Ptr::from(image), image,
distrib, distrib,
} }
} }
fn create(
arena: &mut Arena,
render_from_light: Transform,
medium: Medium,
params: &ParameterDictionary,
loc: &FileLoc,
shape: &Shape,
alpha_text: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
) -> Light {
let i = params.get_one_spectrum(
"I",
colorspace.unwrap().illuminant,
SpectrumType::Illuminant,
);
let lemit_data = lookup_spectrum(&i_spectrum_def);
let sc = params.get_one_float("scale", 1.);
let filename = resolve_filename(params.get_one_string("filename", ""));
let mut image: Option<Image> = None;
if filename.is_empty() {
println!(
"{}: Both \"L\" and \"filename\" specified for DiffuseAreaLight.",
loc
)
} else {
let im = Image::read(filename, None).expect("Could not load image");
let loaded_img: Image = im.image;
let res = loaded_img.resolution();
let metadata: ImageMetadata = im.metadata;
if loaded_img.has_any_infinite_pixels() {
panic!(
"{:?}: Image '{}' has NaN pixels. Not suitable for light.",
loc, filename
);
}
if res.x() != res.y() {
panic!(
"{:?}: Image resolution ({}, {}) is non square. Unlikely that this is an equal area map.",
loc,
res.x(),
res.y(),
y()
);
}
let rgb_desc = loaded_img.get_channel_desc(&["R", "G", "B"]);
let y_desc = loaded_img.get_channel_desc(&["Y"]);
if let Ok(rgb) = rgb_desc {
if y_desc.is_ok() {
panic!(
"{:?}: Image '{}' has both RGB and Y channels. Ambiguous.",
loc, filename
);
}
let mut y_pixels = Vec::with_capacity((res.x * res.y) as usize);
for y in 0..res.y {
for x in 0..res.x {
let r = loaded_img.get_channel(Point2i::new(x, y), 0);
let g = loaded_img.get_channel(Point2i::new(x, y), 1);
let b = loaded_img.get_channel(Point2i::new(x, y), 2);
y_pixels.push((r + g + b) / 3.0);
}
}
loaded_img = Some(Image::new(
PixelFormat::F32,
res,
&["Y"],
ColorEncoding::Linear,
));
} else if y_desc.is_ok() {
image = Some(loaded_img);
} else {
panic!(
"{:?}: Image '{}' has neither RGB nor Y channels.",
loc, filename
);
}
}
scale /= spectrum_to_photometric(&lemit_data);
let phi_v = params.get_one_float("power", -1.0);
if phi_v > 0.0 {
if let Some(ref img) = image {
let mut sum_y = 0.0;
let res = img.resolution();
for y in 0..res.y {
for x in 0..res.x {
sum_y += img.get_channel(Point2i::new(x, y), 0);
}
}
let k_e = 4.0 * PI * sum_y / (res.x * res.y) as Float;
scale *= phi_v / k_e;
}
}
let swap_yz: [Float; 16] = [
1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1.,
];
let t = Transform::from_flat(swap_yz);
let final_render_from_light = render_from_light * t;
let d: Array2D<Float> = image.get_sampling_distribution();
distrib = PiecewiseConstant2D::new_with_data(d);
let specific = GoniometricLight::new(
render_from_light,
medium_interface,
le,
scale,
None,
None,
Some(image),
None,
None,
None,
);
Light::Goniometric(specific)
}
} }

View file

@ -1,44 +1,27 @@
use shared::Float; use shared::Float;
use shared::core::geometry::{Bounds3f, Point2f}; use shared::core::geometry::{Bounds3f, Point2f};
use shared::core::light::{LightBase, LightType}; use shared::core::light::{CreateLight, Light, LightBase, LightType};
use shared::core::medium::MediumInterface; use shared::core::medium::MediumInterface;
use shared::lights::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight}; use shared::lights::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::utils::Transform; use shared::utils::Transform;
use shared::utils::sampling::PiecewiseConstant2D; use shared::utils::sampling::DevicePiecewiseConstant2D;
use std::sync::Arc; use std::sync::Arc;
use crate::core::light::{LightBaseTrait, LightFactory}; use crate::core::light::{LightBaseTrait, lookup_spectrum};
#[derive(Debug)] impl CreateLight for InfiniteImageLight {
struct InfiniteImageLightStorage { fn new(
image: Image,
distrib: PiecewiseConstant2D,
compensated_distrib: PiecewiseConstant2D,
image_color_space: RGBColorSpace,
}
#[derive(Clone, Debug)]
pub struct InfiniteImageLightHost {
pub view: InfiniteImageLight,
pub filename: String, // Kept on Host only
_storage: Arc<InfiniteImageLightStorage>,
}
impl std::ops::Deref for InfiniteImageLightHost {
type Target = InfiniteImageLight;
fn deref(&self) -> &Self::Target {
&self.view
}
}
impl InfiniteImageLightHost {
pub fn new(
render_from_light: Transform, render_from_light: Transform,
image: Image, medium_interface: MediumInterface,
image_color_space: RGBColorSpace, le: shared::core::spectrum::Spectrum,
scale: Float, scale: Float,
filename: String, shape: Option<shared::utils::Ptr<Shape>>,
alpha: Option<shared::utils::Ptr<FloatTexture>>,
image: Option<shared::utils::Ptr<Image>>,
image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
two_sided: Option<bool>,
fov: Option<Float>,
) -> Self { ) -> Self {
let base = LightBase::new( let base = LightBase::new(
LightType::Infinite, LightType::Infinite,
@ -53,6 +36,7 @@ impl InfiniteImageLightHost {
assert_eq!(3, desc.size()); assert_eq!(3, desc.size());
assert!(desc.is_identity()); assert!(desc.is_identity());
if image.resolution().x() != image.resolution().y() { if image.resolution().x() != image.resolution().y() {
hash(hashee, into);
panic!( panic!(
"{}: image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.", "{}: image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.",
filename, filename,
@ -62,7 +46,7 @@ impl InfiniteImageLightHost {
} }
let mut d = image.get_sampling_distribution_uniform(); let mut d = image.get_sampling_distribution_uniform();
let domain = Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.)); let domain = Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.));
let distrib = PiecewiseConstant2D::new_with_bounds(&d, domain); let distrib = DevicePiecewiseConstant2D::new_with_bounds(&d, domain);
let slice = &mut d.values; // or d.as_slice_mut() let slice = &mut d.values; // or d.as_slice_mut()
let count = slice.len() as Float; let count = slice.len() as Float;
let sum: Float = slice.iter().sum(); let sum: Float = slice.iter().sum();
@ -79,31 +63,31 @@ impl InfiniteImageLightHost {
} }
} }
let compensated_distrib = PiecewiseConstant2D::new_with_bounds(&d, domain); let compensated_distrib = DevicePiecewiseConstant2D::new_with_bounds(&d, domain);
let storage = Arc::new(InfiniteImageLightStorage { InfiniteImageLight {
image,
distrib,
compensated_distrib,
image_color_space,
});
let view = InfiniteImageLight {
base, base,
image: &storage.image, image: &image,
image_color_space: &storage.image_color_space, image_color_space: &storage.image_color_space,
scene_center: Point3f::default(), scene_center: Point3f::default(),
scene_radius: 0., scene_radius: 0.,
scale, scale,
distrib: &storage.distrib, distrib: &distrib,
compensated_distrib: &storage.compensated_distrib, compensated_distrib: &compensated_distrib,
}; };
}
Self { fn create(
view, arena: &mut crate::utils::Arena,
filename, render_from_light: Transform,
_storage: storage, medium: Medium,
} parameters: &crate::utils::ParameterDictionary,
loc: &FileLoc,
shape: &Shape,
alpha_tex: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
) -> shared::core::light::Light {
todo!()
} }
} }
@ -121,21 +105,18 @@ pub struct InfinitePortalLightHost {
_storage: Arc<InfinitePortalLightStorage>, _storage: Arc<InfinitePortalLightStorage>,
} }
impl std::ops::Deref for InfinitePortalLightHost { impl CreateLight for InfinitePortalLightHost {
type Target = InfinitePortalLight; fn new(
fn deref(&self) -> &Self::Target {
&self.view
}
}
impl InfinitePortalLightHost {
pub fn new(
render_from_light: Transform, render_from_light: Transform,
equal_area_image: &Image, medium_interface: MediumInterface,
image_color_space: RGBColorSpace, le: shared::core::spectrum::Spectrum,
scale: Float, scale: Float,
filename: String, shape: Option<shared::utils::Ptr<Shape>>,
points: Vec<Point3f>, alpha: Option<shared::utils::Ptr<FloatTexture>>,
image: Option<shared::utils::Ptr<Image>>,
image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
two_sided: Option<bool>,
fov: Option<Float>,
) -> Self { ) -> Self {
let base = LightBase::new( let base = LightBase::new(
LightType::Infinite, LightType::Infinite,
@ -250,34 +231,54 @@ impl InfinitePortalLightHost {
image_color_space, image_color_space,
}); });
let view = InfinitePortalLight { InfinitePortalLight {
base, base,
image: &storage.image, image,
image_color_space: &storage.image_color_space, image_color_space: &storage.image_color_space,
scale, scale,
scene_center: Point3f::default(), scene_center: Point3f::default(),
scene_radius: 0., scene_radius: 0.,
portal, portal,
portal_frame, portal_frame,
distribution: &storage.distribution, distribution,
};
Self {
view,
filename,
_storage: storage,
} }
} }
fn create(
arena: &mut crate::utils::Arena,
render_from_light: Transform,
medium: Medium,
parameters: &crate::utils::ParameterDictionary,
loc: &FileLoc,
shape: &Shape,
alpha_tex: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
) -> Light {
todo!()
}
} }
impl LightFactory for InfiniteUniformLight { impl CreateLight for InfiniteUniformLight {
fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self { fn new(
render_from_light: Transform,
medium_interface: MediumInterface,
le: shared::core::spectrum::Spectrum,
scale: Float,
shape: Option<shared::utils::Ptr<Shape>>,
alpha: Option<shared::utils::Ptr<FloatTexture>>,
image: Option<shared::utils::Ptr<Image>>,
image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
two_sided: Option<bool>,
fov: Option<Float>,
cos_fallof_start: Option<Float>,
total_width: Option<Float>,
) -> Self {
let base = LightBase::new( let base = LightBase::new(
LightType::Infinite, LightType::Infinite,
render_from_light, &render_from_light,
MediumInterface::default(), &MediumInterface::default(),
); );
let lemit = LightBase::lookup_spectrum(&le); let lemit = lookup_spectrum(le);
Self { Self {
base, base,
lemit, lemit,
@ -286,4 +287,17 @@ impl LightFactory for InfiniteUniformLight {
scene_radius: 0., scene_radius: 0.,
} }
} }
fn create(
arena: &mut crate::utils::Arena,
render_from_light: Transform,
medium: Medium,
parameters: &crate::utils::ParameterDictionary,
loc: &FileLoc,
shape: &Shape,
alpha_tex: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
) -> Light {
todo!()
}
} }

View file

@ -1,5 +1,8 @@
pub mod diffuse; pub mod diffuse;
pub mod distant;
pub mod goniometric; pub mod goniometric;
pub mod infinite; pub mod infinite;
pub mod point;
pub mod projection; pub mod projection;
pub mod sampler; pub mod sampler;
pub mod spot;

74
src/lights/point.rs Normal file
View file

@ -0,0 +1,74 @@
use crate::core::light::{CreateLight, LightBaseTrait, lookup_spectrum};
use crate::core::spectrum::spectrum_to_photometric;
use crate::core::texture::FloatTexture;
use crate::utils::{Arena, FileLoc, ParameterDictionary};
use shared::PI;
use shared::core::geometry::VectorLike;
use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::Medium;
use shared::core::shape::Shape;
use shared::lights::PointLight;
use shared::spectra::RGBColorSpace;
use shared::utils::Transform;
impl CreateLight for PointLight {
fn new(
render_from_light: Transform,
medium_interface: shared::core::medium::MediumInterface,
le: shared::core::spectrum::Spectrum,
scale: shared::Float,
_shape: Option<shared::utils::Ptr<Shape>>,
_alpha: Option<shared::utils::Ptr<FloatTexture>>,
_image: Option<shared::utils::Ptr<Image>>,
_image_color_space: Option<shared::utils::Ptr<RGBColorSpace>>,
_two_sided: Option<bool>,
_fov: Option<shared::Float>,
) -> Self {
let base = LightBase::new(
LightType::DeltaPosition,
render_from_light,
medium_interface,
);
let i = lookup_spectrum(le);
Self { base, scale, i }
}
fn create(
arena: &mut Arena,
render_from_light: Transform,
medium: Medium,
parameters: &ParameterDictionary,
loc: &FileLoc,
shape: &Shape,
alpha_text: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
) -> Light {
let l = parameters
.get_one_spectrum(
"L",
colorspace.unwrap().illuminant,
SpectrumType::Illuminant,
)
.unwrap();
let lemit = lookup_spectrum(l);
let mut scale = parameters.get_one_float("scale", 1);
scale /= spectrum_to_photometric(l.unwrap());
let phi_v = parameters.get_one_float("power", 1.);
if phi_v > 0. {
let k_e = 4. * PI;
sc *= phi_v / k_e;
}
let from = parameters.get_one_point3f("from", Point3f::zero());
let tf = Transform::translate(from.into());
let final_render = render_from_light * tf;
let base = LightBase::new(LightType::DeltaPosition, render_from_light, medium.into());
let specific = PointLight {
base,
scale,
i: arena.alloc(lemit),
};
Light::Point(specific)
}
}

View file

@ -1,26 +1,30 @@
use crate::core::image::ImageBuffer; use crate::core::image::{Image, ImageIO, ImageMetadata};
use shared::core::light::LightBase; use crate::core::light::CreateLight;
use crate::core::spectrum::spectrum_to_photometric;
use crate::spectra::colorspace::new;
use crate::utils::{Ptr, Upload, resolve_filename};
use shared::core::geometry::{Bounds2f, VectorLike};
use shared::core::image::ImageAccess;
use shared::core::light::{Light, LightBase};
use shared::core::medium::MediumInterface;
use shared::core::spectrum::Spectrum;
use shared::lights::ProjectionLight; use shared::lights::ProjectionLight;
use shared::spectra::RGBColorSpace;
use shared::utils::math::{radians, square};
use shared::utils::{Ptr, Transform};
pub struct ProjectionLightStorage { impl CreateLight for ProjectionLight {
image: *const Image, fn new(
distrib: *const PiecewiseConstant2D,
image_color_space: *const RGBColorSpace,
}
pub struct ProjectionLightHost {
pub view: ProjectionLight,
_storage: ProjectionLightStorage,
}
impl ProjectionLightHost {
pub fn new(
render_from_light: Transform, render_from_light: Transform,
medium_interface: MediumInterface, medium_interface: MediumInterface,
image: ImageBuffer, le: Spectrum,
image_color_space: RGBColorSpace, scale: shared::Float,
scale: Float, shape: Option<Ptr<Shape>>,
fov: Float, alpha: Option<Ptr<FloatTexture>>,
image: Option<Ptr<Image>>,
image_color_space: Option<Ptr<RGBColorSpace>>,
two_sided: Option<bool>,
fov: Option<shared::Float>,
) -> Self { ) -> Self {
let base = LightBase::new( let base = LightBase::new(
LightType::DeltaPosition, LightType::DeltaPosition,
@ -38,9 +42,9 @@ impl ProjectionLightHost {
}; };
let hither = 1e-3; let hither = 1e-3;
let screen_from_light = Transform::perspective(fov, hither, 1e30).unwrap(); let screen_from_light = Transform::perspective(fov.unwrap(), hither, 1e30).unwrap();
let light_from_screen = screen_from_light.inverse(); let light_from_screen = screen_from_light.inverse();
let opposite = (radians(fov) / 2.).tan(); let opposite = (radians(fov.unwrap()) / 2.).tan();
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect }; let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
let a = 4. * square(opposite) * aspect_ratio; let a = 4. * square(opposite) * aspect_ratio;
let dwda = |p: Point2f| { let dwda = |p: Point2f| {
@ -52,28 +56,109 @@ impl ProjectionLightHost {
let d = image.get_sampling_distribution(dwda, screen_bounds); let d = image.get_sampling_distribution(dwda, screen_bounds);
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds); let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds);
let storage = ProjectionLightStorage { Self {
base,
image, image,
image_color_space, image_color_space,
distrib, distrib,
};
let view = ProjectionLight {
base,
image: storage.image,
image_color_space: storage.image_color_space,
screen_bounds, screen_bounds,
screen_from_light, screen_from_light,
light_from_screen, light_from_screen,
scale, scale,
hither, hither,
a, a,
distrib: storage.distrib,
};
Self {
view,
_storage: storage,
} }
} }
fn create(
arena: &mut crate::utils::Arena,
render_from_light: shared::utils::Transform,
medium: Medium,
parameters: &crate::utils::ParameterDictionary,
loc: &FileLoc,
_shape: &Shape,
_alpha_text: &FloatTexture,
_colorspace: Option<&shared::spectra::RGBColorSpace>,
) -> Light {
let mut scale = parameters.get_one_float("scale", 1.);
let power = parameters.get_one_float("power", -1.);
let fov = parameters.get_one_float("fov", 90.);
let filename = resolve_filename(parameters.get_one_string("filename", ""));
if filename.is_empty() {
panic!(
"{}: Must provide filename for projection light source.",
loc
);
}
let im = Image::read(filename, None).unwrap();
let image: Image = im.image;
let metadata: ImageMetadata = im.metadata;
if image.has_any_infinite_pixels() {
panic!(
"{:?}: Image '{}' has NaN pixels. Not suitable for light.",
loc, filename
);
}
let colorspace: RGBColorSpace = metadata.colorspace.unwrap();
let channel_desc = image
.get_channel_desc(&["R", "G", "B"])
.unwrap_or_else(|_| {
panic!(
"{:?}: Image '{}' must have R, G and B channels.",
loc, filename
)
});
let image = image.select_channels(channel_desc);
scale /= spectrum_to_photometric(colorspace.illuminant);
if power > 0. {
let hither = 1e-3;
let aspect = image.resolution().x() as Float / image.resolution().y() as Float;
let screen_bounds = if aspect > 1. {
Bounds2f::from_poins(Point2f::new(-aspect, -1.), Point2f::new(aspect, 1.))
} else {
Bounds2f::from_points(
Point2f::new(-1., -1. / aspect),
Point2f::new(1., 1. / aspect),
)
};
let screen_from_light = Transform::perspective(fov, hither, 1e30);
let light_from_screen = screen_from_light.inverse();
let opposite = (radians(fov) / 2.).tan();
let aspect_factor = if aspect > 1. { aspect } else { 1. / aspect };
let a = 4. * square(aspect_factor);
let mut sum = 0;
let luminance = colorspace.luminance_vector();
let res = image.resolution();
for y in 0..res.y() {
for x in 0..res.x() {
let lerp_factor = Point2f::new((x + 0.5) / res.x(), (y + 0.5) / res.y());
let ps = screen_bounds.lerp(lerp_factor);
let w_point =
light_from_screen.apply_to_point(Point3f::new(ps.x(), ps.y(), 0.));
let w = Vector3f::from(w_point).normalize();
let dwda = w.z().powi(3);
for c in 0..3 {
sum += image.get_channel(Point2f::new(x, y), c) * luminance[c] * dwda;
}
}
}
scale *= power / (a * sum / (res.x() + res.y()));
}
let flip = Transform::scale(1., -1., 1.);
let render_from_light_flip = render_from_light * flip;
let specific = Self::new(
render_from_light,
medium_interface,
le,
scale,
None,
None,
image.upload(arena),
None,
None,
None,
);
Light::Projection(specific)
}
} }

100
src/lights/spot.rs Normal file
View file

@ -0,0 +1,100 @@
use crate::core::image::{Image, ImageIO, ImageMetadata};
use crate::core::light::CreateLight;
use crate::core::spectrum::spectrum_to_photometric;
use crate::spectra::colorspace::new;
use crate::utils::{Ptr, Upload, resolve_filename};
use shared::PI;
use shared::core::geometry::{Bounds2f, Frame, VectorLike};
use shared::core::image::ImageAccess;
use shared::core::light::{Light, LightBase, LightType};
use shared::core::medium::MediumInterface;
use shared::core::spectrum::Spectrum;
use shared::core::texture::SpectrumType;
use shared::lights::{ProjectionLight, SpotLight};
use shared::spectra::RGBColorSpace;
use shared::utils::math::{radians, square};
use shared::utils::{Ptr, Transform};
impl CreateLight for SpotLight {
fn new(
render_from_light: Transform,
medium_interface: MediumInterface,
le: Spectrum,
scale: shared::Float,
shape: Option<Ptr<Shape>>,
alpha: Option<Ptr<FloatTexture>>,
image: Option<Ptr<Image>>,
image_color_space: Option<Ptr<RGBColorSpace>>,
two_sided: Option<bool>,
fov: Option<shared::Float>,
cos_falloff_start: Option<shared::Float>,
total_width: Option<shared::Float>,
) -> Self {
let base = LightBase::new(
LightType::DeltaPosition,
render_from_light,
MediumInterface::empty(),
);
let iemit = lookup_spectrum(le);
Self {
base,
iemit,
scale,
cos_falloff_end: radians(total_width.unwrap().cos()),
cos_falloff_start: radians(cos_falloff_start.unwrap().cos()),
}
}
fn create(
arena: &mut crate::utils::Arena,
render_from_light: Transform,
medium: Medium,
parameters: &crate::utils::ParameterDictionary,
loc: &FileLoc,
shape: &Shape,
alpha_tex: &FloatTexture,
colorspace: Option<&RGBColorSpace>,
) -> Light {
let i = parameters
.get_one_spectrum(
"I",
colorspace.unwrap().illuminant,
SpectrumType::Illuminant,
)
.expect("No spectrum");
let mut scale = parameters.get_one_float("scale", 1.);
let coneangle = parameters.get_one_float("coneangle", def);
let conedelta = parameters.get_one_float("conedelta", def);
let from = parameters.get_one_point3f("from", Point3f::zero());
let to = parameters.get_one_point3f("to", Point3f::new(0., 0., 1.));
let dir_to_z = Transform::from(Frame::from_z((to - from).normalize()));
let t = Transform::translate(from.into()) * dir_to_z.inverse();
let final_render = render_from_light * t;
scale /= spectrum_to_photometric(i);
let phi_v = parameters.get_one_float("power", -1.);
if phi_v > 0. {
let cos_falloff_end = radians(coneangle).cos();
let cos_falloff_start = radians(coneangle - conedelta).cos();
let k_e =
2. * PI * ((1. - cos_falloff_start) + (cos_falloff_start - cos_falloff_end) / 2);
scale *= phi_v / k_e;
}
let specific = SpotLight::new(
final_render,
medium.into(),
le,
scale,
None,
None,
None,
None,
None,
None,
Some(coneangle),
Some(coneangle - conedelta),
);
}
}

259
src/utils/arena.rs Normal file
View file

@ -0,0 +1,259 @@
use crate::core::image::Image;
use crate::core::texture::FloatTexture;
use crate::utils::sampling::PiecewiseConstant2D;
use core::alloc::Layout;
use shared::Float;
use shared::core::color::RGBToSpectrumTable;
use shared::core::image::DeviceImage;
use shared::core::shape::Shape;
use shared::core::texture::GPUFloatTexture;
use shared::spectra::{RGBColorSpace, StandardColorSpaces};
use shared::textures::*;
use shared::utils::Ptr;
use shared::utils::sampling::DevicePiecewiseConstant2D;
pub struct Arena {
buffer: Vec<u8>,
}
impl Arena {
pub fn new() -> Self {
Self {
buffer: Vec::with_capacity(64 * 1024 * 1024),
}
}
pub fn alloc<T: Copy>(&mut self, value: T) -> Ptr<T> {
let layout = Layout::new::<T>();
let offset = self.alloc_raw(layout);
unsafe {
let ptr = self.buffer.as_mut_ptr().add(offset) as *mut T;
std::ptr::write(ptr, value);
}
Ptr {
offset: offset as i32,
_marker: std::marker::PhantomData,
}
}
pub fn alloc_slice<T: Copy>(&mut self, values: &[T]) -> Ptr<T> {
let layout = Layout::array::<T>(values.len()).unwrap();
let offset = self.alloc_raw(layout);
unsafe {
let ptr = self.buffer.as_mut_ptr().add(offset) as *mut T;
std::ptr::copy_nonoverlapping(values.as_ptr(), ptr, values.len());
}
Ptr {
offset: offset as i32,
_marker: std::marker::PhantomData,
}
}
fn alloc_raw(&mut self, layout: Layout) -> usize {
let len = self.buffer.len();
let align_offset = (len + layout.align() - 1) & !(layout.align() - 1);
let new_len = align_offset + layout.size();
if new_len > self.buffer.capacity() {
// Growth strategy: Double capacity to reduce frequency of resizing
let new_cap = std::cmp::max(self.buffer.capacity() * 2, new_len);
self.buffer.reserve(new_cap - self.buffer.len());
}
self.buffer.resize(new_len, 0);
align_offset
}
pub fn raw_data(&self) -> &[u8] {
&self.buffer
}
}
pub trait Upload {
type Target: Copy;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target>;
}
impl Upload for Shape {
type Target = Shape;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
arena.alloc(self.clone())
}
}
impl Upload for Image {
type Target = DeviceImage;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
let pixels_ptr = arena.alloc_slice(&self.storage_as_slice());
let device_img = DeviceImage {
base: self.base,
pixels: pixels_ptr,
};
arena.alloc(device_img)
}
}
impl Upload for FloatTexture {
type Target = GPUFloatTexture;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
let gpu_variant = match self {
FloatTexture::Constant(tex) => GPUFloatTexture::Constant(tex.clone()),
FloatTexture::Checkerboard(tex) => GPUFloatTexture::Checkerboard(tex.clone()),
FloatTexture::Dots(tex) => GPUFloatTexture::Dots(tex.clone()),
FloatTexture::FBm(tex) => GPUFloatTexture::FBm(tex.clone()),
FloatTexture::Windy(tex) => GPUFloatTexture::Windy(tex.clone()),
FloatTexture::Wrinkled(tex) => GPUFloatTexture::Wrinkled(tex.clone()),
FloatTexture::Constant(val) => GPUFloatTexture::Constant(*val),
FloatTexture::Scaled(tex) => {
let child_ptr = tex.texture.upload(arena);
let gpu_scaled = GPUFloatScaledTexture {
tex: child_ptr,
scale: tex.scale.upload(arena),
};
GPUFloatTexture::Scaled(gpu_scaled)
}
FloatTexture::Mix(tex) => {
let tex1_ptr = tex.tex1.upload(arena);
let tex2_ptr = tex.tex2.upload(arena);
let amount_ptr = tex.amount.upload(arena);
let gpu_mix = GPUFloatMixTexture {
tex1: tex1_ptr,
tex2: tex2_ptr,
amount: amount_ptr,
};
GPUFloatTexture::Mix(gpu_mix)
}
FloatTexture::DirectionMix(tex) => {
let tex1_ptr = tex.tex1.upload(arena);
let tex2_ptr = tex.tex2.upload(arena);
let gpu_dmix = shared::textures::GPUFloatDirectionMixTexture {
tex1: tex1_ptr,
tex2: tex2_ptr,
dir: tex.dir,
};
GPUFloatTexture::DirectionMix(gpu_dmix)
}
FloatTexture::Image(tex) => {
let image_ptr = tex.image.upload(arena);
let gpu_image_tex = GPUFloatImageTexture {
mapping: tex.mapping,
tex_obj: image_ptr.offset as u64,
scale: tex.scale,
invert: tex.invert,
mapping: tex.mapping,
};
GPUFloatTexture::Image(gpu_image_tex)
}
FloatTexture::Ptex(tex) => {
todo!("Implement Ptex buffer upload")
}
FloatTexture::Bilerp(tex) => GPUFloatTexture::Bilerp(tex.clone()),
};
arena.alloc(gpu_variant)
}
}
impl Upload for RGBToSpectrumTable {
type Target = RGBToSpectrumTable;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
let z_ptr = arena.alloc_slice(&self.z_nodes);
let c_ptr = arena.alloc_slice(&self.coeffs);
let shared_table = RGBToSpectrumTable {
z_nodes: z_ptr,
coeffs: c_ptr,
};
arena.alloc(shared_table)
}
}
impl Upload for RGBColorSpace {
type Target = RGBColorSpace;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
let table_ptr = self.rgb_to_spectrum_table.upload(arena);
let shared_space = RGBColorSpace {
r: self.r,
g: self.g,
b: self.b,
w: self.w,
illuminant: self.illuminant.clone(),
rgb_to_spectrum_table: table_ptr,
xyz_from_rgb: self.xyz_from_rgb,
rgb_from_xyz: self.rgb_from_xyz,
};
arena.alloc(shared_space)
}
}
impl Upload for StandardColorSpaces {
type Target = StandardColorSpaces;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
let srgb_ptr = self.srgb.upload(arena);
let dci_ptr = self.dci_p3.upload(arena);
let rec_ptr = self.rec2020.upload(arena);
let aces_ptr = self.aces2065_1.upload(arena);
let registry = StandardColorSpaces {
srgb: srgb_ptr,
dci_p3: dci_ptr,
rec2020: rec_ptr,
aces2065_1: aces_ptr,
};
arena.alloc(registry)
}
}
impl Upload for PiecewiseConstant2D {
type Target = DevicePiecewiseConstant2D;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
let marginal_shared = self.p_marginal.to_shared(arena);
let conditionals_shared: Vec<DevicePiecewiseConstant1D> = self
.p_conditionals
.iter()
.map(|c| c.to_shared(arena))
.collect();
let conditionals_ptr = arena.alloc_slice(&conditionals_shared);
let shared_2d = DevicePiecewiseConstant2D {
domain: self.domain,
p_marginal: marginal_shared,
n_conditionals: self.p_conditionals.len(),
p_conditional_v: conditionals_ptr,
};
arena.alloc(shared_2d)
}
}
impl<T: Upload> Upload for Option<T> {
type Target = T::Target;
fn upload(&self, arena: &mut Arena) -> Ptr<Self::Target> {
match self {
Some(val) => val.upload(arena),
None => Ptr::null(),
}
}
}