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::{
FloatTexture, FloatTextureTrait, TextureEvalContext, TextureEvaluator,
UniversalTextureEvaluator,
};
use crate::shapes::{Shape, ShapeSample, ShapeSampleContext, ShapeTrait};
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;
use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename};
#[derive(Clone, Debug)]
pub struct DiffuseAreaLightStorage {
shape: Arc<Shape>,
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,
impl CreateLight for DiffuseAreaLight {
fn new(
render_from_light: shared::utils::Transform,
medium_interface: MediumInterface,
le: Spectrum,
scale: Float,
shape: Shape,
alpha: FloatTexture,
image: Option<Image>,
image_color_space: Option<Arc<RGBColorSpace>>,
two_sided: bool,
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 {
let is_constant_zero = match &alpha {
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 {
println!(
"Scaling detected in rendering to light space transformation! \
The system has numerous assumptions, implicit and explicit, \
that this transform will have no scale factors in it. \
Proceed at your own risk; your image may have errors."
The system has numerous assumptions, implicit and explicit, \
that this transform will have no scale factors in it. \
Proceed at your own risk; your image may have errors."
);
}
let storage = DiffuseAreaLightStorage {
shape,
alpha: stored_alpha,
image,
image_color_space,
};
let view = DiffuseAreaLight {
Self {
base,
area: shape.area(),
image,
image_color_space,
shape: Ptr::from(&*storage.shape),
};
Self {
view,
_storage: storage,
alpha: stored_alpha,
lemit,
two_sided,
scale,
}
}
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(
render_from_light: Transform,
medium_interface: MediumInterface,
scale: Float,
iemit: &Spectrum,
image: &Image,
le: 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,
@ -14,15 +33,142 @@ impl LightFactory for GoniometricLight {
medium_interface,
);
let i_interned = LightBase::lookup_spectrum(&iemit);
let d = image.get_sampling_distribution_uniform();
let distrib = PiecewiseConstant2D::new_with_data(&d);
let iemit = lookup_spectrum(le);
let d = image.unwrap().get_sampling_distribution_uniform();
let distrib = PiecewiseConstant2D::new_with_data(d);
Self {
base,
iemit: i_interned,
iemit,
scale,
image: Ptr::from(image),
image,
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::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::lights::{InfiniteImageLight, InfinitePortalLight, InfiniteUniformLight};
use shared::spectra::RGBColorSpace;
use shared::utils::Transform;
use shared::utils::sampling::PiecewiseConstant2D;
use shared::utils::sampling::DevicePiecewiseConstant2D;
use std::sync::Arc;
use crate::core::light::{LightBaseTrait, LightFactory};
use crate::core::light::{LightBaseTrait, lookup_spectrum};
#[derive(Debug)]
struct InfiniteImageLightStorage {
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(
impl CreateLight for InfiniteImageLight {
fn new(
render_from_light: Transform,
image: Image,
image_color_space: RGBColorSpace,
medium_interface: MediumInterface,
le: shared::core::spectrum::Spectrum,
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 {
let base = LightBase::new(
LightType::Infinite,
@ -53,6 +36,7 @@ impl InfiniteImageLightHost {
assert_eq!(3, desc.size());
assert!(desc.is_identity());
if image.resolution().x() != image.resolution().y() {
hash(hashee, into);
panic!(
"{}: image resolution ({}, {}) is non-square. It's unlikely this is an equal area environment map.",
filename,
@ -62,7 +46,7 @@ impl InfiniteImageLightHost {
}
let mut d = image.get_sampling_distribution_uniform();
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 count = slice.len() as Float;
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 {
image,
distrib,
compensated_distrib,
image_color_space,
});
let view = InfiniteImageLight {
InfiniteImageLight {
base,
image: &storage.image,
image: &image,
image_color_space: &storage.image_color_space,
scene_center: Point3f::default(),
scene_radius: 0.,
scale,
distrib: &storage.distrib,
compensated_distrib: &storage.compensated_distrib,
distrib: &distrib,
compensated_distrib: &compensated_distrib,
};
}
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>,
) -> shared::core::light::Light {
todo!()
}
}
@ -121,21 +105,18 @@ pub struct InfinitePortalLightHost {
_storage: Arc<InfinitePortalLightStorage>,
}
impl std::ops::Deref for InfinitePortalLightHost {
type Target = InfinitePortalLight;
fn deref(&self) -> &Self::Target {
&self.view
}
}
impl InfinitePortalLightHost {
pub fn new(
impl CreateLight for InfinitePortalLightHost {
fn new(
render_from_light: Transform,
equal_area_image: &Image,
image_color_space: RGBColorSpace,
medium_interface: MediumInterface,
le: shared::core::spectrum::Spectrum,
scale: Float,
filename: String,
points: Vec<Point3f>,
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 {
let base = LightBase::new(
LightType::Infinite,
@ -250,34 +231,54 @@ impl InfinitePortalLightHost {
image_color_space,
});
let view = InfinitePortalLight {
InfinitePortalLight {
base,
image: &storage.image,
image,
image_color_space: &storage.image_color_space,
scale,
scene_center: Point3f::default(),
scene_radius: 0.,
portal,
portal_frame,
distribution: &storage.distribution,
};
Self {
view,
filename,
_storage: storage,
distribution,
}
}
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 {
fn new(render_from_light: Transform, le: Spectrum, scale: Float) -> Self {
impl CreateLight for InfiniteUniformLight {
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(
LightType::Infinite,
render_from_light,
MediumInterface::default(),
&render_from_light,
&MediumInterface::default(),
);
let lemit = LightBase::lookup_spectrum(&le);
let lemit = lookup_spectrum(le);
Self {
base,
lemit,
@ -286,4 +287,17 @@ impl LightFactory for InfiniteUniformLight {
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 distant;
pub mod goniometric;
pub mod infinite;
pub mod point;
pub mod projection;
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 shared::core::light::LightBase;
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::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::spectra::RGBColorSpace;
use shared::utils::math::{radians, square};
use shared::utils::{Ptr, Transform};
pub struct ProjectionLightStorage {
image: *const Image,
distrib: *const PiecewiseConstant2D,
image_color_space: *const RGBColorSpace,
}
pub struct ProjectionLightHost {
pub view: ProjectionLight,
_storage: ProjectionLightStorage,
}
impl ProjectionLightHost {
pub fn new(
impl CreateLight for ProjectionLight {
fn new(
render_from_light: Transform,
medium_interface: MediumInterface,
image: ImageBuffer,
image_color_space: RGBColorSpace,
scale: Float,
fov: Float,
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>,
) -> Self {
let base = LightBase::new(
LightType::DeltaPosition,
@ -38,9 +42,9 @@ impl ProjectionLightHost {
};
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 opposite = (radians(fov) / 2.).tan();
let opposite = (radians(fov.unwrap()) / 2.).tan();
let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect };
let a = 4. * square(opposite) * aspect_ratio;
let dwda = |p: Point2f| {
@ -52,28 +56,109 @@ impl ProjectionLightHost {
let d = image.get_sampling_distribution(dwda, screen_bounds);
let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds);
let storage = ProjectionLightStorage {
Self {
base,
image,
image_color_space,
distrib,
};
let view = ProjectionLight {
base,
image: storage.image,
image_color_space: storage.image_color_space,
screen_bounds,
screen_from_light,
light_from_screen,
scale,
hither,
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(),
}
}
}