Refactoring

This commit is contained in:
Wito Wiala 2026-05-22 14:34:53 +01:00
parent 226ff88874
commit 3226e9c965
10 changed files with 316 additions and 95 deletions

View file

@ -8,6 +8,7 @@ use core::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
}; };
use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero}; use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero};
use core::fmt;
pub trait MulAdd<M = Self, A = Self> { pub trait MulAdd<M = Self, A = Self> {
type Output; type Output;
@ -35,6 +36,45 @@ pub struct Point<T, const N: usize>(pub [T; N]);
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Normal<T, const N: usize>(pub [T; N]); pub struct Normal<T, const N: usize>(pub [T; N]);
impl<T: fmt::Display, const N: usize> fmt::Display for Vector<T, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Vector(")?;
for (i, item) in (&self.0).into_iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", item)?;
}
write!(f, ")")
}
}
impl<T: fmt::Display, const N: usize> fmt::Display for Point<T, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Point(")?;
for (i, item) in (&self.0).into_iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", item)?;
}
write!(f, ")")
}
}
impl<T: fmt::Display, const N: usize> fmt::Display for Normal<T, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Normal(")?;
for (i, item) in (&self.0).into_iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", item)?;
}
write!(f, ")")
}
}
#[macro_export] #[macro_export]
macro_rules! impl_tuple_core { macro_rules! impl_tuple_core {
($Struct:ident) => { ($Struct:ident) => {

View file

@ -194,3 +194,10 @@ pub enum Material {
Mix(MixMaterial), Mix(MixMaterial),
} }
// TODO: THIS IS A HACK JUST FOR TESTING
impl PartialEq for Material {
fn eq(&self, other: &Self) -> bool {
core::mem::discriminant(self) == core::mem::discriminant(other)
}
}

View file

@ -1,13 +1,22 @@
use crate::core::scene::BasicScene; use crate::core::scene::BasicScene;
use crate::globals::get_options;
use crate::integrators::pipeline::render;
use crate::Arena; use crate::Arena;
use anyhow::Result; use anyhow::{bail, Result};
use log::warn; use log::warn;
use shared::core::camera::CameraTrait; use shared::core::camera::CameraTrait;
use shared::core::geometry::{Point2f, Vector2f};
use shared::core::interaction::InteractionTrait;
use shared::core::primitive::PrimitiveTrait;
use shared::core::sampler::CameraSample;
use shared::spectra::{SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN};
use shared::Float;
use std::sync::Arc;
fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> {
let media = scene.create_media(); let media = scene.create_media();
let textures = scene.create_textures(arena); let textures = scene.create_textures(arena);
let lights = scene.create_lights() let (lights, _) = scene.create_lights(&textures, arena);
let (named_materials, materials) = scene.create_materials(&textures, arena)?; let (named_materials, materials) = scene.create_materials(&textures, arena)?;
let (aggregate, area_lights) = let (aggregate, area_lights) =
scene.create_aggregate(&textures, &named_materials, &materials, arena); scene.create_aggregate(&textures, &named_materials, &materials, arena);
@ -15,6 +24,90 @@ fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> {
let film = camera.get_film(); let film = camera.get_film();
warn!("Creating integrator"); warn!("Creating integrator");
let sampler = scene.get_sampler()?; let sampler = scene.get_sampler()?;
let integrator = scene.create_integrator(camera, sampler, aggregate, lights, arena); let integrator = scene.create_integrator(camera.clone(), sampler.clone(), aggregate.clone(), lights, arena);
let mut have_scattering = false;
for sh in scene.shapes.lock().iter() {
if !sh.inside_medium.is_empty() || !sh.outside_medium.is_empty() {
have_scattering = true;
}
}
for sh in scene.animated_shapes.lock().iter() {
if !sh.inside_medium.is_empty() || !sh.outside_medium.is_empty() {
have_scattering = true;
}
}
if get_options().pixel_material.is_some() {
let lambda =
SampledWavelengths::sample_uniform(0.5, LAMBDA_MIN as Float, LAMBDA_MAX as Float);
let cs = CameraSample {
p_film: Point2f::from(get_options().pixel_material.unwrap()) + Vector2f::new(0.5, 0.5),
time: 0.5,
p_lens: Point2f::new(0.5, 0.5),
filter_weight: 1.,
};
let Some(cr) = camera.generate_ray_differential(cs, &lambda) else {
bail!("Unable to generate ray for pixel")
};
let mut depth = 1;
let mut ray = cr.ray;
loop {
if let Some(isect) = aggregate.intersect(&ray, Some(Float::INFINITY)) {
let intr = isect.intr;
if intr.material.is_null() {
log::warn!("Ignoring material")
} else {
let world_from_render = camera.base().camera_transform.world_from_render;
log::debug!("Intersection depth {}\n", depth);
log::debug!(
"World-space p: {}\n",
world_from_render.apply_to_point(intr.p())
);
log::debug!(
"World-space n: {}\n",
world_from_render.apply_to_normal(intr.n())
);
log::debug!(
"World-space ns: {}\n",
world_from_render.apply_to_normal(intr.shading.n)
);
log::debug!("Distance from camera: {}\n", intr.p().distance(cr.ray.o));
let mut is_named = false;
for (name, mtl) in &named_materials {
if *mtl == unsafe { *intr.material.as_ref() } {
log::debug!("Named material: {}\n\n", name);
is_named = true;
break;
}
}
// if !is_named {
// log::warn!("{}\n\n", intr.material.as_ref().to_str());
// }
//
depth += 1;
ray = intr.spawn_ray(ray.d);
}
} else {
if depth == 1 {
bail!("No geometry visible from pixel")
} else {
break;
}
}
}
}
render(
&integrator,
&integrator.base,
&camera,
sampler.as_ref(),
arena,
);
Ok(()) Ok(())
} }

View file

@ -2,7 +2,6 @@ use super::entities::*;
use super::BasicScene; use super::BasicScene;
use crate::spectra::get_colorspace_device; use crate::spectra::get_colorspace_device;
use crate::utils::error::FileLoc; use crate::utils::error::FileLoc;
use crate::utils::normalize_utf8;
use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector}; use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector};
use crate::utils::parser::{ParserError, ParserTarget}; use crate::utils::parser::{ParserError, ParserTarget};
use crate::Arena; use crate::Arena;
@ -17,9 +16,14 @@ use shared::Float;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use std::sync::Arc; use std::sync::Arc;
use unicode_normalization::UnicodeNormalization;
const MAX_TRANSFORMS: usize = 2; const MAX_TRANSFORMS: usize = 2;
fn normalize_utf8(input: &str) -> String {
input.nfc().collect::<String>()
}
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
struct TransformSet { struct TransformSet {
t: [Transform; MAX_TRANSFORMS], t: [Transform; MAX_TRANSFORMS],

View file

@ -31,6 +31,7 @@ use shared::core::shape::Shape;
use shared::core::texture::{GPUFloatTexture, SpectrumType}; use shared::core::texture::{GPUFloatTexture, SpectrumType};
use shared::lights::sampler::LightSampler; use shared::lights::sampler::LightSampler;
use shared::spectra::RGBColorSpace; use shared::spectra::RGBColorSpace;
use shared::textures::FloatConstantTexture;
use shared::{Ptr, Transform}; use shared::{Ptr, Transform};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -508,52 +509,165 @@ impl BasicScene {
Ok((named_materials, materials)) Ok((named_materials, materials))
} }
pub fn create_lights(&self, camera_transform: &CameraTransform, arena: &Arena) -> Vec<Light> { pub fn create_lights(
let state = self.light_state.lock(); &self,
textures: &NamedTextures,
arena: &Arena,
) -> (Vec<Arc<Light>>, HashMap<usize, Vec<Arc<Light>>>) {
let shape_entities = self.shapes.lock();
let light_state = self.light_state.lock();
let material_state = self.material_state.lock();
state let mut all_lights: Vec<Arc<Light>> = Vec::new();
.lights let mut shape_index_to_area_lights: HashMap<usize, Vec<Arc<Light>>> = HashMap::new();
.iter()
.filter_map(|entity| {
let render_from_light = entity.transformed_base.render_from_object.start_transform;
let medium = self for (i, entity) in shape_entities.iter().enumerate() {
.get_medium(&entity.medium, &entity.transformed_base.base.loc) let light_idx = match entity.light_index {
.map(|m| *m); Some(idx) => idx,
None => continue,
};
match crate::core::light::create_light( let material_name = match &entity.material {
&entity.transformed_base.base.name, MaterialRef::Name(name) => {
render_from_light, match material_state
medium, .named_materials
&entity.transformed_base.base.parameters, .iter()
&entity.transformed_base.base.loc, .find(|(n, _)| n == name)
camera_transform.clone(), {
arena, Some((_, mtl_entity)) => {
) { match mtl_entity.parameters.get_one_string("type", "") {
Ok(light) => Some(light), Ok(t) if !t.is_empty() => t,
Err(e) => { _ => {
log::error!( log::error!(
"{}: failed to create light: {}", "{}: named material '{}' missing type",
entity.transformed_base.base.loc, entity.base.loc,
e name
); );
None continue;
}
}
}
None => {
log::error!(
"{}: no named material '{}' defined.",
entity.base.loc,
name
);
continue;
}
} }
} }
}) MaterialRef::Index(idx) => match material_state.materials.get(*idx) {
.collect() Some(mtl_entity) => mtl_entity.name.clone(),
None => {
log::error!("{}: material index {} out of bounds", entity.base.loc, idx);
continue;
}
},
MaterialRef::None => String::new(),
};
if material_name == "interface" || material_name == "none" || material_name.is_empty() {
log::warn!(
"{}: Ignoring area light specification for shape with \"interface\" material.",
entity.base.loc
);
continue;
}
let shape_objects = match Shape::create(
&entity.base.name,
*entity.render_from_object,
*entity.object_from_render,
entity.reverse_orientation,
entity.base.parameters.clone(),
&textures.float_textures,
entity.base.loc.clone(),
arena,
) {
Ok(shapes) => shapes,
Err(e) => {
log::error!("{}: failed to create shape: {}", entity.base.loc, e);
continue;
}
};
if shape_objects.is_empty() {
continue;
}
let alpha_tex = Self::get_alpha_texture(
&entity.base.parameters,
&entity.base.loc,
&textures.float_textures,
);
let default_alpha = Arc::new(FloatTexture::default());
let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha);
let inside = self.get_medium(&entity.inside_medium, &entity.base.loc);
let outside = self.get_medium(&entity.outside_medium, &entity.base.loc);
let medium_interface = MediumInterface {
inside: inside
.as_ref()
.map(|m| Ptr::from(m.as_ref()))
.unwrap_or(Ptr::null()),
outside: outside
.as_ref()
.map(|m| Ptr::from(m.as_ref()))
.unwrap_or(Ptr::null()),
};
let al_entity = &light_state.area_lights[light_idx];
let film_cs = self.film_colorspace.lock();
let colorspace_ref = al_entity
.parameters
.color_space
.as_ref()
.or(film_cs.as_ref());
let mut shape_lights: Vec<Arc<Light>> = Vec::new();
for shape in &shape_objects {
let cs = colorspace_ref.map(|cs| cs.as_ref());
match crate::core::light::create_area_light(
*entity.render_from_object,
None,
&al_entity.parameters,
&al_entity.loc,
shape,
alpha_ref,
cs,
arena,
) {
Ok(light) => {
let light_arc = Arc::new(light);
all_lights.push(Arc::clone(&light_arc));
shape_lights.push(light_arc);
}
Err(e) => {
log::error!("{}: failed to create area light: {}", al_entity.loc, e);
}
}
}
if !shape_lights.is_empty() {
shape_index_to_area_lights.insert(i, shape_lights);
}
}
log::debug!("Finished area lights");
(all_lights, shape_index_to_area_lights)
} }
/// Create area lights for shapes that reference one. Produces a map from
/// shape index to a vec of lights.
/// Must be called after shapes are loaded but before upload_shapes.
pub fn create_area_lights( pub fn create_area_lights(
&self, &self,
loaded_shapes: &[Vec<Shape>], loaded_shapes: &[Vec<Shape>],
shape_entities: &[ShapeSceneEntity],
textures: &NamedTextures, textures: &NamedTextures,
arena: &Arena, arena: &Arena,
) -> HashMap<usize, Vec<Light>> { ) -> HashMap<usize, Vec<Light>> {
let shape_entities = &self.shapes.lock();
let light_state = self.light_state.lock(); let light_state = self.light_state.lock();
let mut shape_lights: HashMap<usize, Vec<Light>> = HashMap::new(); let mut shape_lights: HashMap<usize, Vec<Light>> = HashMap::new();
@ -681,7 +795,7 @@ impl BasicScene {
sampler: Arc<Sampler>, sampler: Arc<Sampler>,
aggregate: Arc<Primitive>, aggregate: Arc<Primitive>,
lights: Vec<Arc<Light>>, lights: Vec<Arc<Light>>,
arena: &Arena arena: &Arena,
) -> PathIntegrator { ) -> PathIntegrator {
let integrator = &self.integrator.lock().clone().unwrap(); let integrator = &self.integrator.lock().clone().unwrap();
PathIntegrator::create( PathIntegrator::create(
@ -691,7 +805,7 @@ impl BasicScene {
aggregate, aggregate,
lights, lights,
PathConfig::SIMPLE, PathConfig::SIMPLE,
arena arena,
) )
.expect("Integrator creation has failed") .expect("Integrator creation has failed")
} }
@ -1114,12 +1228,23 @@ impl BasicScene {
textures: &HashMap<String, Arc<FloatTexture>>, textures: &HashMap<String, Arc<FloatTexture>>,
) -> Option<Arc<FloatTexture>> { ) -> Option<Arc<FloatTexture>> {
let name = params.get_texture("alpha"); let name = params.get_texture("alpha");
if name.is_empty() { if !name.is_empty() {
return None; match textures.get(&name) {
} Some(tex) => Some(tex.clone()),
match textures.get(&name) { None => panic!(
Some(tex) => Some(tex.clone()), "{}: couldn't find float texture '{}' for \"alpha\" parameter.",
None => panic!("{:?}: Alpha texture '{}' not found", loc, name), loc, name
),
}
} else {
let alpha = params.get_one_float("alpha", 1.0).unwrap();
if alpha < 1.0 {
Some(Arc::new(FloatTexture::Constant(FloatConstantTexture::new(
alpha,
))))
} else {
None
}
} }
} }

View file

@ -66,7 +66,7 @@ impl CreateIntegrator for PathIntegrator {
) -> Result<PathIntegrator> { ) -> Result<PathIntegrator> {
let _max_depth = parameters.get_one_int("maxdepth", 5)?; let _max_depth = parameters.get_one_int("maxdepth", 5)?;
let _regularize = parameters.get_one_bool("regularize", false)?; let _regularize = parameters.get_one_bool("regularize", false)?;
let light_sampler = create_light_sampler("bvh", &lights, arena); let light_sampler = create_light_sampler("power", &lights, arena);
let integrator = PathIntegrator::new(aggregate, lights, camera, light_sampler, config); let integrator = PathIntegrator::new(aggregate, lights, camera, light_sampler, config);
Ok(integrator) Ok(integrator)
} }

View file

@ -78,7 +78,7 @@ pub fn render<T>(
_base: &IntegratorBase, _base: &IntegratorBase,
camera: &Camera, camera: &Camera,
sampler_prototype: &Sampler, sampler_prototype: &Sampler,
arena: Arc<Arena>, arena: &Arena,
) where ) where
T: RayIntegratorTrait + Sync, T: RayIntegratorTrait + Sync,
{ {

View file

@ -8,7 +8,6 @@ pub mod mipmap;
pub mod parallel; pub mod parallel;
pub mod parameters; pub mod parameters;
pub mod parser; pub mod parser;
pub mod strings;
pub mod upload; pub mod upload;
pub use error::FileLoc; pub use error::FileLoc;
@ -16,7 +15,6 @@ pub use file::{read_float_file, resolve_filename};
pub use parameters::{ pub use parameters::{
ParameterDictionary, ParsedParameter, ParsedParameterVector, TextureParameterDictionary, ParameterDictionary, ParsedParameter, ParsedParameterVector, TextureParameterDictionary,
}; };
pub use strings::*;
pub use mipmap::{MIPMap, MIPMapFilterOptions}; pub use mipmap::{MIPMap, MIPMapFilterOptions};
pub use upload::{Upload, ArenaUpload}; pub use upload::{Upload, ArenaUpload};

View file

@ -1,47 +1,6 @@
use crossbeam_channel::{Receiver, bounded}; use crossbeam_channel::{Receiver, bounded};
use rayon::prelude::*; use rayon::prelude::*;
pub fn init_parallel(n_threads: usize) {
let threads = if n_threads == 0 {
num_cpus::get()
} else {
n_threads
};
if let Err(e) = rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build_global()
{
eprintln!("Warning: Rayon thread pool already initialized: {}", e);
}
}
pub fn num_system_cores() -> usize {
num_cpus::get()
}
pub fn max_concurrency() -> usize {
rayon::current_num_threads()
}
pub fn parallel_for<F>(start: i64, end: i64, func: F)
where
F: Fn(i64) + Sync + Send,
{
(start..end).into_par_iter().for_each(|i| func(i));
}
pub fn parallel_for_2d<F>(start_x: i64, end_x: i64, start_y: i64, end_y: i64, func: F)
where
F: Fn(i64, i64) + Sync + Send,
{
(start_y..end_y).into_par_iter().for_each(|y| {
(start_x..end_x).into_par_iter().for_each(|x| {
func(x, y);
});
});
}
#[derive(Debug)] #[derive(Debug)]
pub struct AsyncJob<T> { pub struct AsyncJob<T> {
receiver: Receiver<T>, receiver: Receiver<T>,

View file

@ -1,5 +0,0 @@
use unicode_normalization::UnicodeNormalization;
pub fn normalize_utf8(input: &str) -> String {
input.nfc().collect::<String>()
}