pbrt/src/core/scene/scene.rs

1305 lines
45 KiB
Rust

use super::entities::*;
use super::state::*;
use crate::core::aggregates::CreateBVH;
use crate::core::camera::CameraFactory;
use crate::core::film::FilmFactory;
use crate::core::filter::FilterFactory;
use crate::core::image::{HostImage, ImageIO};
use crate::core::material::MaterialFactory;
use crate::core::medium::CreateMedium;
use crate::core::primitive::{CreateGeometricPrimitive, CreateSimplePrimitive};
use crate::core::sampler::SamplerFactory;
use crate::core::shape::{ShapeFactory, ShapeWithContext};
use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::integrators::{CreateIntegrator, PathConfig, PathIntegrator};
use crate::lights::sampler::create_light_sampler;
use crate::utils::parallel::{run_async, AsyncJob};
use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
use crate::utils::resolve_filename;
use crate::wavefront::{CpuAggregate, CpuWavefrontRenderer, CreateWavefront};
use crate::{Arena, ArenaUpload, FileLoc};
use anyhow::{anyhow, Result};
use parking_lot::Mutex;
use shared::core::aggregates::{BVHAggregate, SplitMethod};
use shared::core::camera::Camera;
use shared::core::camera::CameraTrait;
use shared::core::color::LINEAR;
use shared::core::film::Film;
use shared::core::filter::Filter;
use shared::core::light::{Light, LightTrait};
use shared::core::material::Material;
use shared::core::medium::{Medium, MediumInterface};
use shared::core::primitive::{AnimatedPrimitive, GeometricPrimitive, Primitive, SimplePrimitive};
use shared::core::sampler::{Sampler, SamplerTrait};
use shared::core::shape::Shape;
use shared::core::texture::SpectrumType;
use shared::core::{LightIdx, MaterialIdx};
use shared::spectra::RGBColorSpace;
use shared::textures::FloatConstantTexture;
use shared::utils::soa::SoA;
use shared::wavefront::*;
use shared::{gvec, Ptr, WorkQueue};
use std::collections::HashMap;
use std::sync::Arc;
fn find_medium(
media: &HashMap<String, Arc<Medium>>,
name: &str,
loc: &FileLoc,
) -> Option<Arc<Medium>> {
if name.is_empty() {
return None;
}
match media.get(name) {
Some(m) => Some(Arc::clone(m)),
None => {
log::error!("{}: medium '{}' not defined", loc, name);
None
}
}
}
fn resolve_medium_interface(
media: &HashMap<String, Arc<Medium>>,
inside: &str,
outside: &str,
loc: &FileLoc,
) -> MediumInterface {
MediumInterface {
inside: find_medium(media, inside, loc)
.as_ref()
.map(|m| Ptr::from(m.as_ref()))
.unwrap_or(Ptr::null()),
outside: find_medium(media, outside, loc)
.as_ref()
.map(|m| Ptr::from(m.as_ref()))
.unwrap_or(Ptr::null()),
}
}
fn resolve_material(
mat_ref: &MaterialRef,
named_materials: &HashMap<String, MaterialIdx>,
materials: &[Material],
loc: &FileLoc,
_arena: &Arena,
) -> MaterialIdx {
match mat_ref {
MaterialRef::Name(name) => match named_materials.get(name) {
Some(m) => *m,
None => {
log::error!("{}: named material '{}' not found", loc, name);
MaterialIdx::NONE
}
},
// Anonymous materials are placed at the front of `materials`, so the
// index handed out by `add_material` is the handle directly.
MaterialRef::Index(idx) => {
if *idx < materials.len() {
MaterialIdx(*idx as u32)
} else {
log::error!("{}: material index {} out of bounds", loc, idx);
MaterialIdx::NONE
}
}
MaterialRef::None => MaterialIdx::NONE,
}
}
/// Resolve alpha texture from parameters
fn get_alpha_texture(
params: &ParameterDictionary,
loc: &FileLoc,
textures: &HashMap<String, Arc<FloatTexture>>,
) -> Option<Arc<FloatTexture>> {
let name = params.get_texture("alpha");
if !name.is_empty() {
match textures.get(&name) {
Some(tex) => return Some(tex.clone()),
None => panic!(
"{}: couldn't find float texture '{}' for \"alpha\" parameter.",
loc, name
),
}
}
let alpha = params.get_one_float("alpha", 1.0).unwrap();
if alpha < 1.0 {
Some(Arc::new(FloatTexture::Constant(FloatConstantTexture::new(
alpha,
))))
} else {
None
}
}
pub struct BasicScene {
pub integrator: Mutex<Option<SceneEntity>>,
pub accelerator: Mutex<Option<SceneEntity>>,
pub film_colorspace: Mutex<Option<Arc<RGBColorSpace>>>,
pub shapes: Mutex<Vec<ShapeSceneEntity>>,
pub animated_shapes: Mutex<Vec<AnimatedShapeSceneEntity>>,
pub instances: Mutex<Vec<InstanceSceneEntity>>,
pub instance_definitions: Mutex<HashMap<String, Arc<InstanceDefinitionSceneEntity>>>,
pub media_state: Mutex<MediaState>,
pub material_state: Mutex<MaterialState>,
pub light_state: Mutex<LightState>,
pub texture_state: Mutex<TextureState>,
pub camera_state: Mutex<SingletonState<Camera>>,
pub sampler_state: Mutex<SingletonState<Sampler>>,
pub film_state: Mutex<SingletonState<Film>>,
}
impl Default for BasicScene {
fn default() -> Self {
Self::new()
}
}
pub type AreaLightMap = HashMap<(usize, usize), LightIdx>;
impl BasicScene {
pub fn new() -> Self {
Self {
integrator: Mutex::new(None),
accelerator: Mutex::new(None),
film_colorspace: Mutex::new(None),
shapes: Mutex::new(Vec::new()),
animated_shapes: Mutex::new(Vec::new()),
instances: Mutex::new(Vec::new()),
instance_definitions: Mutex::new(HashMap::new()),
media_state: Mutex::new(MediaState::default()),
material_state: Mutex::new(MaterialState::default()),
light_state: Mutex::new(LightState::default()),
texture_state: Mutex::new(TextureState::default()),
camera_state: Mutex::new(SingletonState::default()),
sampler_state: Mutex::new(SingletonState::default()),
film_state: Mutex::new(SingletonState::default()),
}
}
pub fn set_options(
self: &Arc<Self>,
filter: SceneEntity,
film: SceneEntity,
camera: CameraSceneEntity,
sampler: SceneEntity,
integ: SceneEntity,
accel: SceneEntity,
arena: &Arena,
) -> Result<()> {
*self.integrator.lock() = Some(integ);
*self.accelerator.lock() = Some(accel);
if let Some(cs) = film.parameters.color_space.as_ref() {
*self.film_colorspace.lock() = Some(Arc::clone(cs));
}
let filter = Filter::create(&filter.name, &filter.parameters, &filter.loc, arena)
.map_err(|e| anyhow!("Failed to create filter: {}", e))?;
let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.)?;
let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.)?;
let exposure_time = shutter_close - shutter_open;
let film_instance = Arc::new(
Film::create(
&film.name,
&film.parameters,
exposure_time,
filter,
Some(camera.camera_transform),
&film.loc,
arena,
)
.map_err(|e| anyhow!("Failed to create film: {}", e))?,
);
*self.film_state.lock() = SingletonState {
result: Some(Arc::clone(&film_instance)),
job: None,
};
let res = film_instance.as_ref().base().full_resolution;
let sampler_result =
Sampler::create(&sampler.name, &sampler.parameters, res, &sampler.loc, arena)
.map_err(|e| anyhow!("Failed to create sampler: {}", e))?;
*self.sampler_state.lock() = SingletonState {
result: Some(Arc::new(sampler_result)),
job: None,
};
let medium = self.get_medium(&camera.medium, &camera.base.loc);
let camera_result = Camera::create(
&camera.base.name,
&camera.base.parameters,
&camera.camera_transform,
medium,
Arc::clone(&film_instance),
&camera.base.loc,
arena,
)
.map_err(|e| anyhow!("Failed to create camera: {}", e))?;
*self.camera_state.lock() = SingletonState {
result: Some(Arc::new(camera_result)),
job: None,
};
Ok(())
}
pub fn add_named_material(&self, name: &str, material: SceneEntity) {
let mut state = self.material_state.lock();
self.start_loading_normal_maps(&mut state, &material.parameters);
state.named_materials.push((name.to_string(), material));
}
pub fn add_medium(&self, name: &str, medium: MediumSceneEntity) {
let mut state = self.media_state.lock();
state.entities.push((name.to_string(), medium));
}
pub fn add_material(&self, material: SceneEntity) -> usize {
let mut state = self.material_state.lock();
self.start_loading_normal_maps(&mut state, &material.parameters);
state.materials.push(material);
state.materials.len() - 1
}
pub fn add_float_texture(
&self,
name: String,
texture: TextureSceneEntity,
arena: Arc<Arena>,
) -> Result<()> {
let mut state = self.texture_state.lock();
self.add_texture_generic(
name,
texture,
&mut state,
|s| &mut s.serial_float_textures,
|s| &mut s.float_texture_jobs,
move |tex| {
let render_from_texture = tex.render_from_object.start_transform;
let tex_dict = TextureParameterDictionary::new(tex.base.parameters.into(), None);
FloatTexture::create(
&tex.base.name,
render_from_texture,
tex_dict,
tex.base.loc,
&arena,
)
.expect("Could not create float texture")
},
)
}
pub fn add_spectrum_texture(
&self,
name: String,
texture: TextureSceneEntity,
arena: Arc<Arena>,
) -> Result<()> {
let mut state = self.texture_state.lock();
if texture.render_from_object.is_animated() {
log::info!(
"{}: animated world-to-texture not supported, using start transform",
texture.base.loc
);
}
if texture.base.name != "imagemap" && texture.base.name != "ptex" {
state.serial_spectrum_textures.push((name, texture));
return Ok(());
}
let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", "")?);
if !self.validate_texture_file(&filename, &texture.base.loc, &mut state.n_missing_textures)
{
return Ok(());
}
// Avoid duplicate work if the same file is already being loaded
if state.loading_texture_filenames.contains(&filename) {
state.serial_spectrum_textures.push((name, texture));
return Ok(());
}
state.loading_texture_filenames.insert(filename.clone());
state
.async_spectrum_textures
.push((name.clone(), texture.clone()));
let job = run_async(move || {
let render_from_texture = texture.render_from_object.start_transform;
let tex_dict = TextureParameterDictionary::new(texture.base.parameters.into(), None);
Arc::new(
SpectrumTexture::create(
&texture.base.name,
render_from_texture,
tex_dict,
SpectrumType::Albedo,
texture.base.loc,
&arena,
)
.expect("Could not create spectrum texture"),
)
});
state.spectrum_texture_jobs.insert(name, job);
Ok(())
}
pub fn add_area_light(&self, light: SceneEntity) -> usize {
let mut state = self.light_state.lock();
state.area_lights.push(light);
state.area_lights.len() - 1
}
pub fn add_light(&self, light: LightSceneEntity) {
self.light_state.lock().lights.push(light);
}
pub fn add_shape(&self, shape: ShapeSceneEntity) {
self.shapes.lock().push(shape);
}
pub fn add_shapes(&self, new_shapes: Vec<ShapeSceneEntity>) {
self.shapes.lock().extend(new_shapes);
}
pub fn add_animated_shapes(&self, new_shapes: Vec<AnimatedShapeSceneEntity>) {
self.animated_shapes.lock().extend(new_shapes);
}
pub fn add_instance_definition(&self, instance: InstanceDefinitionSceneEntity) {
let name = instance.name.clone();
self.instance_definitions
.lock()
.insert(name, Arc::new(instance));
}
pub fn add_instance_uses(&self, uses: Vec<InstanceSceneEntity>) {
self.instances.lock().extend(uses);
}
// Texture creation
pub fn create_textures(&self, arena: &Arena) -> NamedTextures {
let mut state = self.texture_state.lock();
let mut float_textures: HashMap<String, Arc<FloatTexture>> = HashMap::new();
let mut spectrum_textures: HashMap<String, Arc<SpectrumTexture>> = HashMap::new();
for (name, job) in state.float_texture_jobs.drain() {
float_textures.insert(name, job.wait());
}
for (name, job) in state.spectrum_texture_jobs.drain() {
spectrum_textures.insert(name, job.wait());
}
let mut named = NamedTextures {
float_textures: Arc::new(float_textures.clone()),
albedo_spectrum_textures: Arc::new(spectrum_textures.clone()),
illuminant_spectrum_textures: Arc::new(HashMap::new()),
unbounded_spectrum_textures: Arc::new(HashMap::new()),
};
for (name, entity) in state.async_spectrum_textures.drain(..) {
let render_from_texture = entity.render_from_object.start_transform;
let params = entity.base.parameters.clone();
let unbounded = SpectrumTexture::create(
&entity.base.name,
render_from_texture,
TextureParameterDictionary::new(params.clone().into(), None),
SpectrumType::Unbounded,
entity.base.loc.clone(),
arena,
)
.expect("Could not create unbounded spectrum texture");
let illum = SpectrumTexture::create(
&entity.base.name,
render_from_texture,
TextureParameterDictionary::new(params.into(), None),
SpectrumType::Illuminant,
entity.base.loc,
arena,
)
.expect("Could not create illuminant spectrum texture");
Arc::make_mut(&mut named.unbounded_spectrum_textures)
.insert(name.clone(), Arc::new(unbounded));
Arc::make_mut(&mut named.illuminant_spectrum_textures).insert(name, Arc::new(illum));
}
// Serial float textures may reference already-loaded textures
for (name, entity) in state.serial_float_textures.drain(..) {
let render_from_texture = entity.render_from_object.start_transform;
let tex_dict =
TextureParameterDictionary::new(entity.base.parameters.into(), Some(&named));
let tex = FloatTexture::create(
&entity.base.name,
render_from_texture,
tex_dict,
entity.base.loc,
arena,
)
.expect("Could not create float texture");
Arc::make_mut(&mut named.float_textures).insert(name, Arc::new(tex));
}
for (name, entity) in state.serial_spectrum_textures.drain(..) {
let render_from_texture = entity.render_from_object.start_transform;
let make = |st: SpectrumType, named: &NamedTextures, loc| {
let tex_dict = TextureParameterDictionary::new(
entity.base.parameters.clone().into(),
Some(named),
);
SpectrumTexture::create(
&entity.base.name,
render_from_texture,
tex_dict,
st,
loc,
arena,
)
.expect("Could not create spectrum texture")
};
let albedo = make(SpectrumType::Albedo, &named, entity.base.loc.clone());
let unbounded = make(SpectrumType::Unbounded, &named, entity.base.loc.clone());
let illum = make(SpectrumType::Illuminant, &named, entity.base.loc);
Arc::make_mut(&mut named.albedo_spectrum_textures)
.insert(name.clone(), Arc::new(albedo));
Arc::make_mut(&mut named.unbounded_spectrum_textures)
.insert(name.clone(), Arc::new(unbounded));
Arc::make_mut(&mut named.illuminant_spectrum_textures).insert(name, Arc::new(illum));
}
named
}
pub fn create_materials(
&self,
textures: &NamedTextures,
arena: &Arena,
) -> Result<(HashMap<String, MaterialIdx>, Vec<Material>, MaterialIdx)> {
let mut state = self.material_state.lock();
// Finish async normal map loads
let finished: Vec<_> = state.normal_map_jobs.drain().collect();
for (filename, job) in finished {
match std::panic::catch_unwind(|| job.wait()) {
Ok(img) => {
state.normal_maps.insert(filename, img);
}
Err(_) => {
log::error!("Failed to load normal map: {}", filename);
}
}
}
// Named materials must be *created* first: an anonymous material may be a
// mix that resolves its sub-materials by name. They are *placed* after the
// anonymous ones though, so that `MaterialRef::Index(i)` — an index into
// `state.materials` handed out by `add_material` — is `MaterialIdx(i)`.
let mut named_created: Vec<(String, Material)> = Vec::new();
// Value map for resolving named sub-materials (e.g. mix) during creation.
let mut named_values: HashMap<String, Material> = HashMap::new();
for (name, entity) in &state.named_materials {
if named_values.contains_key(name) {
log::error!(
"{}: trying to redefine named material '{}'.",
entity.loc,
name
);
continue;
}
let mat_type = entity.parameters.get_one_string("type", "")?;
if mat_type.is_empty() {
log::error!("{}: missing material type for '{}'", entity.loc, name);
continue;
}
let normal_map = self.get_normal_map(&state, &entity.parameters)?;
let tex_dict = TextureParameterDictionary::new(
Arc::new(entity.parameters.clone()),
Some(textures),
);
match Material::create(
&mat_type,
&tex_dict,
normal_map,
&named_values, // value map, not index map
entity.loc.clone(),
arena,
) {
Ok(mat) => {
named_values.insert(name.clone(), mat);
named_created.push((name.clone(), mat));
}
Err(e) => {
log::error!(
"{}: failed to create material '{}': {}",
entity.loc,
name,
e
);
}
}
}
// Indexed (anonymous) materials occupy [0, state.materials.len()) so that
// a `MaterialRef::Index` needs no offset.
let mut materials: Vec<Material> = Vec::with_capacity(state.materials.len());
for entity in &state.materials {
let result: Result<Material> = (|| {
let normal_map = self.get_normal_map(&state, &entity.parameters)?;
let tex_dict = TextureParameterDictionary::new(
entity.parameters.clone().into(),
Some(textures),
);
Material::create(
&entity.name,
&tex_dict,
normal_map,
&named_values,
entity.loc.clone(),
arena,
)
})();
let mat = match result {
Ok(mat) => mat,
Err(e) => {
log::error!("{}: failed to create material: {}", entity.loc, e);
crate::core::material::default_diffuse_material(arena)
}
};
materials.push(mat);
}
let mut named_materials: HashMap<String, MaterialIdx> = HashMap::new();
for (name, mat) in named_created {
named_materials.insert(name, MaterialIdx(materials.len() as u32));
materials.push(mat);
}
let default_mtl = MaterialIdx(materials.len() as u32);
materials.push(crate::core::material::default_diffuse_material(arena));
Ok((named_materials, materials, default_mtl))
}
pub fn create_media(&self, arena: &Arena) -> HashMap<String, Arc<Medium>> {
let mut state = self.media_state.lock();
if !state.jobs.is_empty() {
let jobs: Vec<(String, AsyncJob<Medium>)> = state.jobs.drain().collect();
for (name, job) in jobs {
state.map.insert(name, Arc::new(job.wait()));
}
}
let entities: Vec<(String, MediumSceneEntity)> = state.entities.drain(..).collect();
for (name, entity) in entities {
if entity.render_from_object.is_animated() {
log::warn!(
"{}: animated media aren't supported, using start transform.",
entity.base.loc
);
}
match Medium::create(
&entity.base.name,
&entity.base.parameters,
entity.render_from_object.start_transform,
&entity.base.loc,
arena,
) {
Ok(m) => {
state.map.insert(name, Arc::new(*m));
}
Err(e) => log::error!("{}: failed to create medium: {}", entity.base.loc, e),
}
}
state.map.clone()
}
pub fn create_lights(
&self,
textures: &NamedTextures,
media: &HashMap<String, Arc<Medium>>,
arena: &Arena,
) -> (Vec<Light>, AreaLightMap) {
let light_state = self.light_state.lock();
let shapes = self.shapes.lock();
let film_cs = self.film_colorspace.lock();
let film_cs_ref = film_cs.as_deref();
let camera = self
.get_camera()
.expect("Camera must be initialized before lights");
let camera_transform = camera.base().camera_transform;
let mut lights: Vec<Light> = Vec::new();
for entity in &light_state.lights {
let medium = self.get_medium(&entity.medium, &entity.transformed_base.base.loc);
if entity.transformed_base.render_from_object.is_animated() {
log::warn!(
"{}: animated lights aren't supported, using start transform.",
entity.transformed_base.base.loc
);
}
match crate::core::light::create_light(
&entity.transformed_base.base.name,
entity.transformed_base.render_from_object.start_transform,
medium.map(|m| *m),
&entity.transformed_base.base.parameters,
&entity.transformed_base.base.loc,
camera_transform,
arena,
) {
Ok(light) => lights.push(light), // bare Light, no Arc
Err(e) => log::error!(
"{}: failed to create light: {}",
entity.transformed_base.base.loc,
e
),
}
}
let mut area_map: AreaLightMap = HashMap::new();
for (entity_idx, entity) in shapes.iter().enumerate() {
let Some(al_idx) = entity.light_index else {
continue;
};
let al = &light_state.area_lights[al_idx];
let created_shapes = 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(s) => s,
Err(_) => continue,
};
let alpha_tex = get_alpha_texture(
&entity.base.parameters,
&entity.base.loc,
&textures.float_textures,
);
let cs = al.parameters.color_space.as_deref().or(film_cs_ref);
for (sub_idx, shape) in created_shapes.iter().enumerate() {
let default_alpha = Arc::new(FloatTexture::default());
let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha);
match crate::core::light::create_area_light(
*entity.render_from_object,
None,
&al.parameters,
&al.loc,
shape,
alpha_ref,
cs,
arena,
) {
Ok(light) => {
let idx = LightIdx(lights.len() as u32);
lights.push(light);
area_map.insert((entity_idx, sub_idx), idx);
}
Err(e) => log::error!("{}: area light creation failed: {}", al.loc, e),
}
}
}
(lights, area_map)
}
pub fn create_aggregate(
&self,
textures: &NamedTextures,
named_materials: &HashMap<String, MaterialIdx>,
materials: &[Material],
area_map: &AreaLightMap,
media: &HashMap<String, Arc<Medium>>,
arena: &Arena,
) -> Arc<Primitive> {
let mut shapes = self.shapes.lock();
let mut animated_shapes = self.animated_shapes.lock();
let mut instance_defs = self.instance_definitions.lock();
let mut instances = self.instances.lock();
let light_state = self.light_state.lock();
let film_cs = self.film_colorspace.lock();
let film_cs_ref = film_cs.as_deref();
log::info!("Starting shapes");
let mut primitives = Self::create_primitives_for_shapes(
&shapes,
textures,
named_materials,
materials,
Some(area_map),
media,
arena,
);
shapes.clear();
shapes.shrink_to_fit();
let animated_primitives = Self::create_primitives_for_animated_shapes(
&animated_shapes,
textures,
named_materials,
materials,
media,
arena,
);
primitives.extend(animated_primitives);
animated_shapes.clear();
animated_shapes.shrink_to_fit();
log::info!("Finished shapes");
log::info!("Starting instances");
let mut resolved_defs: HashMap<String, Option<Primitive>> = HashMap::new();
for (name, def) in instance_defs.drain() {
let mut inst_prims = Self::create_primitives_for_shapes(
&def.shapes,
textures,
named_materials,
materials,
None,
media,
arena,
);
let animated_inst_prims = Self::create_primitives_for_animated_shapes(
&def.animated_shapes,
textures,
named_materials,
materials,
media,
arena,
);
inst_prims.extend(animated_inst_prims);
let aggregate = if inst_prims.len() > 1 {
Some(Primitive::BVH(arena.alloc(BVHAggregate::new(
inst_prims,
4,
SplitMethod::SAH,
))))
} else if inst_prims.len() == 1 {
Some(inst_prims.into_iter().next().unwrap())
} else {
None
};
resolved_defs.insert(name, aggregate);
}
for inst in instances.drain(..) {
let def = match resolved_defs.get(&inst.name) {
Some(Some(prim)) => prim,
Some(None) => continue,
None => {
log::error!("{}: object instance '{}' not defined", inst.loc, inst.name);
continue;
}
};
let prim = match &inst.transform {
InstanceTransform::Static(xform) => {
Primitive::Transformed(shared::core::primitive::TransformedPrimitive {
primitive: arena.alloc(*def),
render_from_primitive: arena.alloc(**xform),
})
}
InstanceTransform::Animated(anim) => Primitive::Animated(AnimatedPrimitive {
primitive: arena.alloc(*def),
render_from_primitive: arena.alloc(*anim),
}),
};
primitives.push(prim);
}
log::info!("Finished instances");
log::info!("Starting top-level accelerator");
let aggregate = if !primitives.is_empty() {
BVHAggregate::new(primitives, 4, SplitMethod::SAH)
} else {
BVHAggregate::empty()
};
let agg_ptr = arena.alloc(aggregate);
log::info!("Finished top-level accelerator");
Arc::new(Primitive::BVH(agg_ptr))
}
// Integrator
pub fn create_integrator(
&self,
camera: Arc<Camera>,
sampler: Arc<Sampler>,
aggregate: Arc<Primitive>,
lights: Vec<Light>,
materials: Vec<Material>,
arena: &Arena,
) -> PathIntegrator {
let integrator_entity = self.integrator.lock().clone().unwrap();
let name = &integrator_entity.name;
match name.as_str() {
"path" | "volpath" => PathIntegrator::create(
integrator_entity.parameters.clone(),
camera,
sampler,
aggregate,
lights,
materials,
PathConfig::FULL,
arena,
)
.expect("Integrator creation failed"),
_ => panic!("Unknown integrator: {}", name),
}
}
pub fn create_wavefront_integrator(
&self,
camera: Arc<Camera>,
sampler: Arc<Sampler>,
aggregate: Arc<Primitive>,
lights: Vec<Light>,
materials: Vec<Material>,
arena: &Arena,
) -> CpuWavefrontRenderer {
let integrator_entity = self.integrator.lock().clone().unwrap();
let params = &integrator_entity.parameters;
CpuWavefrontRenderer::create(
params.clone(),
camera,
sampler,
aggregate,
lights,
materials,
arena,
)
}
// Getters
pub fn get_camera(&self) -> Result<Arc<Camera>> {
self.get_singleton(&self.camera_state, "Camera")
}
pub fn get_sampler(&self) -> Result<Arc<Sampler>> {
self.get_singleton(&self.sampler_state, "Sampler")
}
pub fn get_film(&self) -> Result<Arc<Film>> {
self.get_singleton(&self.film_state, "Film")
}
pub fn get_medium(&self, name: &str, loc: &FileLoc) -> Option<Arc<Medium>> {
if name.is_empty() {
return None;
}
let mut state = self.media_state.lock();
if let Some(medium) = state.map.get(name) {
return Some(Arc::clone(medium));
}
if let Some(job) = state.jobs.remove(name) {
let medium: Arc<Medium> = Arc::new(job.wait());
state.map.insert(name.to_string(), medium.clone());
return Some(medium);
}
log::error!("{}: medium '{}' is not defined.", loc, name);
None
}
fn create_primitives_for_shapes(
shapes: &[ShapeSceneEntity],
textures: &NamedTextures,
named_materials: &HashMap<String, MaterialIdx>,
materials: &[Material],
// `None` for object-instance definitions: the map is keyed by the
// top-level shape index, so it must not be consulted for the
// independently-numbered shapes inside an instance definition.
area_map: Option<&AreaLightMap>,
media: &HashMap<String, Arc<Medium>>,
arena: &Arena,
) -> Vec<Primitive> {
let mut primitives = Vec::new();
for (entity_idx, entity) in shapes.iter().enumerate() {
if area_map.is_none() && entity.light_index.is_some() {
log::error!(
"{}: area lights are not supported with object instancing.",
entity.base.loc
);
}
let created_shapes = 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(s) => s,
Err(e) => {
log::error!("{}: shape creation failed: {}", entity.base.loc, e);
continue;
}
};
if created_shapes.is_empty() {
continue;
}
let mtl: MaterialIdx = resolve_material(
&entity.material,
named_materials,
materials,
&entity.base.loc,
arena,
);
let alpha_tex = get_alpha_texture(
&entity.base.parameters,
&entity.base.loc,
&textures.float_textures,
);
let mi = resolve_medium_interface(
media,
&entity.inside_medium,
&entity.outside_medium,
&entity.base.loc,
);
for (sub_idx, shape) in created_shapes.into_iter().enumerate() {
// look up the pre-created light index instead of creating one
let light_idx = area_map
.and_then(|m| m.get(&(entity_idx, sub_idx)).copied())
.unwrap_or(LightIdx::NONE);
let prim =
if light_idx.is_none() && !mi.is_medium_transition() && alpha_tex.is_none() {
Primitive::Simple(SimplePrimitive::new(shape, mtl)) // mtl is MaterialIdx now
} else {
let alpha_ptr = alpha_tex
.as_ref()
.map(|t| arena.upload(t.as_ref()))
.unwrap_or(Ptr::null());
Primitive::Geometric(GeometricPrimitive::new(
shape, mtl, light_idx, mi, alpha_ptr,
))
};
primitives.push(prim);
}
}
primitives
}
fn create_primitives_for_animated_shapes(
shapes: &[AnimatedShapeSceneEntity],
textures: &NamedTextures,
named_materials: &HashMap<String, MaterialIdx>,
materials: &[Material],
media: &HashMap<String, Arc<Medium>>,
arena: &Arena,
) -> Vec<Primitive> {
let mut primitives = Vec::new();
for entity in shapes {
let start_xform = entity.transformed_base.render_from_object.start_transform;
let created_shapes = match Shape::create(
&entity.transformed_base.base.name,
start_xform,
start_xform.inverse(),
entity.reverse_orientation,
entity.transformed_base.base.parameters.clone(),
&textures.float_textures,
entity.transformed_base.base.loc.clone(),
arena,
) {
Ok(s) => s,
Err(e) => {
log::error!(
"{}: animated shape creation failed: {}",
entity.transformed_base.base.loc,
e
);
continue;
}
};
if created_shapes.is_empty() {
continue;
}
let mtl = resolve_material(
&entity.material,
named_materials,
materials,
&entity.transformed_base.base.loc,
arena,
);
let alpha_tex = get_alpha_texture(
&entity.transformed_base.base.parameters,
&entity.transformed_base.base.loc,
&textures.float_textures,
);
let mi = resolve_medium_interface(
media,
&entity.inside_medium,
&entity.outside_medium,
&entity.transformed_base.base.loc,
);
if entity.light_index.is_some() {
log::error!(
"{}: animated area lights are not supported.",
entity.transformed_base.base.loc
);
}
// Build base primitives, then wrap each in AnimatedPrimitive
let mut base_prims = Vec::new();
for shape in created_shapes {
let base = if !mi.is_medium_transition() && alpha_tex.is_none() {
Primitive::Simple(SimplePrimitive::new(shape, mtl))
} else {
let alpha_ptr = alpha_tex
.as_ref()
.map(|t| arena.upload(t.as_ref()))
.unwrap_or(Ptr::null());
Primitive::Geometric(GeometricPrimitive::new(
shape,
mtl,
LightIdx::default(), // no area light on animated shapes
mi,
alpha_ptr,
))
};
base_prims.push(base);
}
// Collapse multiple sub-shapes into a BVH, then animate the whole thing
let base = if base_prims.len() > 1 {
let bvh = BVHAggregate::new(base_prims, 4, SplitMethod::SAH);
Primitive::BVH(arena.alloc(bvh))
} else {
base_prims.into_iter().next().unwrap()
};
primitives.push(Primitive::Animated(AnimatedPrimitive {
primitive: arena.alloc(base),
render_from_primitive: arena.alloc(entity.transformed_base.render_from_object),
}));
}
primitives
}
fn add_texture_generic<T, F>(
&self,
name: String,
texture: TextureSceneEntity,
state: &mut TextureState,
get_serial: impl FnOnce(&mut TextureState) -> &mut Vec<(String, TextureSceneEntity)>,
get_jobs: impl FnOnce(&mut TextureState) -> &mut HashMap<String, AsyncJob<Arc<T>>>,
create_fn: F,
) -> Result<()>
where
T: Send + Sync + 'static,
F: FnOnce(TextureSceneEntity) -> T + Send + 'static,
{
if texture.render_from_object.is_animated() {
log::info!(
"{}: animated world-to-texture not supported, using start transform",
texture.base.loc
);
}
// Non-image textures must be created serially (they may reference other textures)
if texture.base.name != "imagemap" && texture.base.name != "ptex" {
get_serial(state).push((name, texture));
return Ok(());
}
let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", "")?);
if !self.validate_texture_file(&filename, &texture.base.loc, &mut state.n_missing_textures)
{
return Ok(());
}
// Already loading this file — fall back to serial to avoid duplicate work
if state.loading_texture_filenames.contains(&filename) {
get_serial(state).push((name, texture));
return Ok(());
}
state.loading_texture_filenames.insert(filename);
let job = run_async(move || Arc::new(create_fn(texture)));
get_jobs(state).insert(name, job);
Ok(())
}
fn validate_texture_file(&self, filename: &str, loc: &FileLoc, n_missing: &mut usize) -> bool {
if filename.is_empty() {
log::error!(
"{}: \"string filename\" not provided for image texture.",
loc
);
*n_missing += 1;
return false;
}
if !std::path::Path::new(filename).exists() {
log::error!("{}: {}: file not found.", loc, filename);
*n_missing += 1;
return false;
}
true
}
// Material helpers
fn start_loading_normal_maps(
&self,
state: &mut MaterialState,
params: &ParameterDictionary,
) -> Result<()> {
let filename = resolve_filename(&params.get_one_string("normalmap", "")?);
if filename.is_empty() {
return Ok(());
}
if state.normal_map_jobs.contains_key(&filename)
|| state.normal_maps.contains_key(&filename)
{
return Ok(());
}
let filename_clone = filename.clone();
let job = run_async(move || {
let path = std::path::Path::new(&filename_clone);
let immeta = HostImage::read(path, Some(LINEAR))
.unwrap_or_else(|e| panic!("{}: failed to read normal map: {}", filename_clone, e));
let rgb_desc = immeta
.image
.get_channel_desc(&["R", "G", "B"])
.unwrap_or_else(|_| {
panic!(
"{}: normal map must contain R, G, B channels",
filename_clone
)
});
Arc::new(immeta.image.select_channels(&rgb_desc))
});
state.normal_map_jobs.insert(filename, job);
Ok(())
}
fn get_normal_map(
&self,
state: &MaterialState,
params: &ParameterDictionary,
) -> Result<Option<Arc<HostImage>>> {
let filename = resolve_filename(&params.get_one_string("normalmap", "")?);
if filename.is_empty() {
return Ok(None);
}
Ok(state.normal_maps.get(&filename).cloned())
}
fn get_singleton<T: Send + 'static>(
&self,
state: &Mutex<SingletonState<T>>,
name: &str,
) -> Result<Arc<T>> {
let mut guard = state.lock();
if let Some(ref res) = guard.result {
return Ok(res.clone());
}
if let Some(job) = guard.job.take() {
let val = job.wait()?;
let res = Arc::new(val);
guard.result = Some(res.clone());
return Ok(res);
}
Err(anyhow!("{} requested but not initialized!", name))
}
#[allow(dead_code)]
fn upload_shapes(
&self,
arena: &Arena,
entities: &[ShapeSceneEntity],
loaded: Vec<ShapeWithContext>,
textures: &NamedTextures,
named_materials: &HashMap<String, MaterialIdx>,
materials: &[Material],
media: &HashMap<String, Arc<Medium>>,
shape_lights: &AreaLightMap,
) -> Vec<Primitive> {
// TODO: GPU wavefront path — upload shapes into device-visible arena,
// build SOA primitive arrays for kernel dispatch
let _ = (
arena,
entities,
loaded,
textures,
named_materials,
materials,
media,
shape_lights,
);
Vec::new()
}
#[allow(dead_code)]
fn upload_animated_shapes(
&self,
arena: &Arena,
entities: &[AnimatedShapeSceneEntity],
loaded: Vec<Ptr<Shape>>,
textures: &NamedTextures,
named_materials: &HashMap<String, MaterialIdx>,
materials: &[Material],
media: &HashMap<String, Arc<Medium>>,
) -> Vec<Primitive> {
// TODO: GPU wavefront path — animated shape upload
let _ = (
arena,
entities,
loaded,
textures,
named_materials,
materials,
media,
);
Vec::new()
}
}