Compare commits
3 commits
5ff8044158
...
66032abf76
| Author | SHA1 | Date | |
|---|---|---|---|
| 66032abf76 | |||
| f18aed2c91 | |||
| 8b93ce3d4b |
10 changed files with 633 additions and 513 deletions
|
|
@ -189,7 +189,7 @@ pub mod device {
|
||||||
// on the material, which we can refine later.
|
// on the material, which we can refine later.
|
||||||
let q = &*params.universal_eval_mtl_q.as_raw();
|
let q = &*params.universal_eval_mtl_q.as_raw();
|
||||||
q.push(MaterialEvalWorkItem {
|
q.push(MaterialEvalWorkItem {
|
||||||
p: intr.p(),
|
p: intr.pi(),
|
||||||
n: intr.n(),
|
n: intr.n(),
|
||||||
ns: intr.shading.n,
|
ns: intr.shading.n,
|
||||||
dpdu: intr.shading.dpdu,
|
dpdu: intr.shading.dpdu,
|
||||||
|
|
@ -258,8 +258,6 @@ pub mod device {
|
||||||
pub n_rays: u32,
|
pub n_rays: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Helper functions --
|
|
||||||
|
|
||||||
unsafe fn push_escaped(
|
unsafe fn push_escaped(
|
||||||
params: &IntersectClosestParams,
|
params: &IntersectClosestParams,
|
||||||
work: &RayWorkItem,
|
work: &RayWorkItem,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
use crate::core::bxdf::BxDFFlags;
|
use crate::core::bxdf::BxDFFlags;
|
||||||
use crate::core::geometry::{Normal3f, Point2f, Point3f, Point3fi, Vector3f, RayDifferential};
|
use crate::core::geometry::{
|
||||||
use crate::core::light::LightSampleContext;
|
Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray, RayDifferential, Vector3f,
|
||||||
|
};
|
||||||
use crate::core::light::Light;
|
use crate::core::light::Light;
|
||||||
|
use crate::core::light::LightSampleContext;
|
||||||
use crate::core::material::Material;
|
use crate::core::material::Material;
|
||||||
use crate::core::medium::{Medium, MediumInterface};
|
use crate::core::medium::{Medium, MediumInterface};
|
||||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
|
|
@ -27,6 +29,8 @@ pub struct PixelSampleState {
|
||||||
pub eta_scale: SoABuffer<Float>,
|
pub eta_scale: SoABuffer<Float>,
|
||||||
pub camera_ray_weight: SoABuffer<SampledSpectrum>,
|
pub camera_ray_weight: SoABuffer<SampledSpectrum>,
|
||||||
pub visible_surface_idx: SoABuffer<u32>,
|
pub visible_surface_idx: SoABuffer<u32>,
|
||||||
|
pub samples: SoABuffer<RaySamples>,
|
||||||
|
pub p_pixel: SoABuffer<Point2i>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SoA for PixelSampleState {
|
impl SoA for PixelSampleState {
|
||||||
|
|
@ -48,6 +52,8 @@ impl SoA for PixelSampleState {
|
||||||
eta_scale: alloc_soa_buffer(n, alloc),
|
eta_scale: alloc_soa_buffer(n, alloc),
|
||||||
camera_ray_weight: alloc_soa_buffer(n, alloc),
|
camera_ray_weight: alloc_soa_buffer(n, alloc),
|
||||||
visible_surface_idx: alloc_soa_buffer(n, alloc),
|
visible_surface_idx: alloc_soa_buffer(n, alloc),
|
||||||
|
samples: alloc_soa_buffer(n, alloc),
|
||||||
|
p_pixel: alloc_soa_buffer(n, alloc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,25 +64,33 @@ impl SoA for PixelSampleState {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct RayWorkItem {
|
pub struct RayWorkItem {
|
||||||
pub ray_o: Point3f,
|
pub ray: Ray,
|
||||||
pub ray_d: Vector3f,
|
pub depth: u32,
|
||||||
pub ray_time: Float,
|
pub lambda: SampledWavelengths,
|
||||||
pub ray_medium: Ptr<Medium>,
|
|
||||||
pub pixel_index: u32,
|
pub pixel_index: u32,
|
||||||
pub has_differentials: bool,
|
pub beta: SampledSpectrum,
|
||||||
pub differential: RayDifferential
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
|
pub prev_intr_ctx: LightSampleContext,
|
||||||
|
pub eta_scale: Float,
|
||||||
|
pub specular_bounce: u8,
|
||||||
|
pub any_non_specular_bounces: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct RayWorkItemSoA {
|
pub struct RayWorkItemSoA {
|
||||||
pub ray_o: SoABuffer<Point3f>,
|
pub ray: SoABuffer<Ray>,
|
||||||
pub ray_d: SoABuffer<Vector3f>,
|
pub depth: SoABuffer<u32>,
|
||||||
pub ray_time: SoABuffer<Float>,
|
pub lambda: SoABuffer<SampledWavelengths>,
|
||||||
pub ray_medium: SoABuffer<Ptr<Medium>>,
|
|
||||||
pub pixel_index: SoABuffer<u32>,
|
pub pixel_index: SoABuffer<u32>,
|
||||||
pub has_differentials: SoABuffer<bool>,
|
pub beta: SoABuffer<SampledSpectrum>,
|
||||||
pub differential: SoABuffer<RayDifferential>,
|
pub r_u: SoABuffer<SampledSpectrum>,
|
||||||
|
pub r_l: SoABuffer<SampledSpectrum>,
|
||||||
|
pub prev_intr_ctx: SoABuffer<LightSampleContext>,
|
||||||
|
pub eta_scale: SoABuffer<Float>,
|
||||||
|
pub specular_bounce: SoABuffer<u8>,
|
||||||
|
pub any_non_specular_bounces: SoABuffer<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SoA for RayWorkItemSoA {
|
impl SoA for RayWorkItemSoA {
|
||||||
|
|
@ -84,36 +98,49 @@ impl SoA for RayWorkItemSoA {
|
||||||
|
|
||||||
fn allocate(n: u32, alloc: &dyn SoAAllocator) -> Self {
|
fn allocate(n: u32, alloc: &dyn SoAAllocator) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ray_o: alloc_soa_buffer(n, alloc),
|
ray: alloc_soa_buffer(n, alloc),
|
||||||
ray_d: alloc_soa_buffer(n, alloc),
|
depth: alloc_soa_buffer(n, alloc),
|
||||||
ray_time: alloc_soa_buffer(n, alloc),
|
lambda: alloc_soa_buffer(n, alloc),
|
||||||
ray_medium: alloc_soa_buffer(n, alloc),
|
|
||||||
pixel_index: alloc_soa_buffer(n, alloc),
|
pixel_index: alloc_soa_buffer(n, alloc),
|
||||||
has_differentials: alloc_soa_buffer(n, alloc),
|
beta: alloc_soa_buffer(n, alloc),
|
||||||
differential: alloc_soa_buffer(n, alloc),
|
r_u: alloc_soa_buffer(n, alloc),
|
||||||
|
r_l: alloc_soa_buffer(n, alloc),
|
||||||
|
prev_intr_ctx: alloc_soa_buffer(n, alloc),
|
||||||
|
eta_scale: alloc_soa_buffer(n, alloc),
|
||||||
|
specular_bounce: alloc_soa_buffer(n, alloc),
|
||||||
|
any_non_specular_bounces: alloc_soa_buffer(n, alloc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn get(&self, i: usize) -> RayWorkItem {
|
unsafe fn get(&self, i: usize) -> RayWorkItem {
|
||||||
RayWorkItem {
|
RayWorkItem {
|
||||||
ray_o: self.ray_o.get(i),
|
ray: self.ray.get(i),
|
||||||
ray_d: self.ray_d.get(i),
|
depth: self.depth.get(i),
|
||||||
ray_time: self.ray_time.get(i),
|
lambda: self.lambda.get(i),
|
||||||
ray_medium: self.ray_medium.get(i),
|
|
||||||
pixel_index: self.pixel_index.get(i),
|
pixel_index: self.pixel_index.get(i),
|
||||||
has_differentials: self.has_differentials.get(i),
|
beta: self.beta.get(i),
|
||||||
differential: self.differential.get(i),
|
r_u: self.r_u.get(i),
|
||||||
|
r_l: self.r_l.get(i),
|
||||||
|
prev_intr_ctx: self.prev_intr_ctx.get(i),
|
||||||
|
eta_scale: self.eta_scale.get(i),
|
||||||
|
specular_bounce: self.specular_bounce.get(i),
|
||||||
|
any_non_specular_bounces: self.any_non_specular_bounces.get(i),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn set(&self, i: usize, v: RayWorkItem) {
|
unsafe fn set(&self, i: usize, v: RayWorkItem) {
|
||||||
self.ray_o.set(i, v.ray_o);
|
self.ray.set(i, v.ray);
|
||||||
self.ray_d.set(i, v.ray_d);
|
self.depth.set(i, v.depth);
|
||||||
self.ray_time.set(i, v.ray_time);
|
self.lambda.set(i, v.lambda);
|
||||||
self.ray_medium.set(i, v.ray_medium);
|
|
||||||
self.pixel_index.set(i, v.pixel_index);
|
self.pixel_index.set(i, v.pixel_index);
|
||||||
self.has_differentials.set(i, v.has_differentials);
|
self.beta.set(i, v.beta);
|
||||||
self.differential.set(i, v.differential);
|
self.r_u.set(i, v.r_u);
|
||||||
|
self.r_l.set(i, v.r_l);
|
||||||
|
self.prev_intr_ctx.set(i, v.prev_intr_ctx);
|
||||||
|
self.eta_scale.set(i, v.eta_scale);
|
||||||
|
self.specular_bounce.set(i, v.specular_bounce);
|
||||||
|
self.any_non_specular_bounces
|
||||||
|
.set(i, v.any_non_specular_bounces);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,8 +316,7 @@ impl SoA for HitAreaLightWorkItemSoA {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct MaterialEvalWorkItem {
|
pub struct MaterialEvalWorkItem {
|
||||||
// Surface interaction
|
pub p: Point3fi,
|
||||||
pub p: Point3f,
|
|
||||||
pub n: Normal3f,
|
pub n: Normal3f,
|
||||||
pub ns: Normal3f,
|
pub ns: Normal3f,
|
||||||
pub dpdu: Vector3f,
|
pub dpdu: Vector3f,
|
||||||
|
|
@ -299,30 +325,27 @@ pub struct MaterialEvalWorkItem {
|
||||||
pub wo: Vector3f,
|
pub wo: Vector3f,
|
||||||
pub time: Float,
|
pub time: Float,
|
||||||
pub face_index: i32,
|
pub face_index: i32,
|
||||||
|
|
||||||
// Material
|
|
||||||
pub material: Ptr<Material>,
|
pub material: Ptr<Material>,
|
||||||
pub area_light: Ptr<Light>,
|
pub area_light: Ptr<Light>,
|
||||||
|
|
||||||
// Medium interface
|
|
||||||
pub medium_interface: MediumInterface,
|
pub medium_interface: MediumInterface,
|
||||||
|
|
||||||
// Path state
|
|
||||||
pub pixel_index: u32,
|
pub pixel_index: u32,
|
||||||
pub lambda: SampledWavelengths,
|
pub lambda: SampledWavelengths,
|
||||||
pub beta: SampledSpectrum,
|
pub beta: SampledSpectrum,
|
||||||
pub r_u: SampledSpectrum,
|
pub r_u: SampledSpectrum,
|
||||||
|
// pub r_l: SampledSpectrum,
|
||||||
// For next-event estimation
|
|
||||||
pub any_non_specular_bounces: bool,
|
pub any_non_specular_bounces: bool,
|
||||||
pub depth: u32,
|
pub depth: u32,
|
||||||
pub eta_scale: Float,
|
pub eta_scale: Float,
|
||||||
|
pub dpdus: Vector3f ,
|
||||||
|
pub dpdvs: Vector3f,
|
||||||
|
pub dndus: Normal3f,
|
||||||
|
pub dndvs: Normal3f
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct MaterialEvalWorkItemSoA {
|
pub struct MaterialEvalWorkItemSoA {
|
||||||
pub p: SoABuffer<Point3f>,
|
pub p: SoABuffer<Point3fi>,
|
||||||
pub n: SoABuffer<Normal3f>,
|
pub n: SoABuffer<Normal3f>,
|
||||||
pub ns: SoABuffer<Normal3f>,
|
pub ns: SoABuffer<Normal3f>,
|
||||||
pub dpdu: SoABuffer<Vector3f>,
|
pub dpdu: SoABuffer<Vector3f>,
|
||||||
|
|
@ -338,9 +361,15 @@ pub struct MaterialEvalWorkItemSoA {
|
||||||
pub lambda: SoABuffer<SampledWavelengths>,
|
pub lambda: SoABuffer<SampledWavelengths>,
|
||||||
pub beta: SoABuffer<SampledSpectrum>,
|
pub beta: SoABuffer<SampledSpectrum>,
|
||||||
pub r_u: SoABuffer<SampledSpectrum>,
|
pub r_u: SoABuffer<SampledSpectrum>,
|
||||||
|
// pub r_l: SoABuffer<SampledSpectrum>,
|
||||||
pub any_non_specular_bounces: SoABuffer<u8>,
|
pub any_non_specular_bounces: SoABuffer<u8>,
|
||||||
pub depth: SoABuffer<u32>,
|
pub depth: SoABuffer<u32>,
|
||||||
pub eta_scale: SoABuffer<Float>,
|
pub eta_scale: SoABuffer<Float>,
|
||||||
|
pub dpdus: SoABuffer<Vector3f>,
|
||||||
|
pub dpdvs: SoABuffer<Vector3f>,
|
||||||
|
pub dndus: SoABuffer<Normal3f>,
|
||||||
|
pub dndvs: SoABuffer<Normal3f>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SoA for MaterialEvalWorkItemSoA {
|
impl SoA for MaterialEvalWorkItemSoA {
|
||||||
|
|
@ -364,9 +393,14 @@ impl SoA for MaterialEvalWorkItemSoA {
|
||||||
lambda: alloc_soa_buffer(n, alloc),
|
lambda: alloc_soa_buffer(n, alloc),
|
||||||
beta: alloc_soa_buffer(n, alloc),
|
beta: alloc_soa_buffer(n, alloc),
|
||||||
r_u: alloc_soa_buffer(n, alloc),
|
r_u: alloc_soa_buffer(n, alloc),
|
||||||
|
// r_l: alloc_soa_buffer(n, alloc),
|
||||||
any_non_specular_bounces: alloc_soa_buffer(n, alloc),
|
any_non_specular_bounces: alloc_soa_buffer(n, alloc),
|
||||||
depth: alloc_soa_buffer(n, alloc),
|
depth: alloc_soa_buffer(n, alloc),
|
||||||
eta_scale: alloc_soa_buffer(n, alloc),
|
eta_scale: alloc_soa_buffer(n, alloc),
|
||||||
|
dpdus: alloc_soa_buffer(n, alloc),
|
||||||
|
dpdvs: alloc_soa_buffer(n, alloc),
|
||||||
|
dndus: alloc_soa_buffer(n, alloc),
|
||||||
|
dndvs: alloc_soa_buffer(n, alloc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -388,9 +422,15 @@ impl SoA for MaterialEvalWorkItemSoA {
|
||||||
lambda: self.lambda.get(i),
|
lambda: self.lambda.get(i),
|
||||||
beta: self.beta.get(i),
|
beta: self.beta.get(i),
|
||||||
r_u: self.r_u.get(i),
|
r_u: self.r_u.get(i),
|
||||||
|
// r_l: self.r_l.get(i),
|
||||||
any_non_specular_bounces: self.any_non_specular_bounces.get(i) != 0,
|
any_non_specular_bounces: self.any_non_specular_bounces.get(i) != 0,
|
||||||
depth: self.depth.get(i),
|
depth: self.depth.get(i),
|
||||||
eta_scale: self.eta_scale.get(i),
|
eta_scale: self.eta_scale.get(i),
|
||||||
|
dpdus: self.dpdus.get(i),
|
||||||
|
dpdvs: self.dpdvs.get(i),
|
||||||
|
dndus: self.dndus.get(i),
|
||||||
|
dndvs: self.dndvs.get(i),
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -411,34 +451,39 @@ impl SoA for MaterialEvalWorkItemSoA {
|
||||||
self.lambda.set(i, v.lambda);
|
self.lambda.set(i, v.lambda);
|
||||||
self.beta.set(i, v.beta);
|
self.beta.set(i, v.beta);
|
||||||
self.r_u.set(i, v.r_u);
|
self.r_u.set(i, v.r_u);
|
||||||
|
// self.r_l.set(i, v.r_l);
|
||||||
self.any_non_specular_bounces
|
self.any_non_specular_bounces
|
||||||
.set(i, v.any_non_specular_bounces as u8);
|
.set(i, v.any_non_specular_bounces as u8);
|
||||||
self.depth.set(i, v.depth);
|
self.depth.set(i, v.depth);
|
||||||
self.eta_scale.set(i, v.eta_scale);
|
self.eta_scale.set(i, v.eta_scale);
|
||||||
|
self.dpdus.set(i, v.dpdus);
|
||||||
|
self.dpdvs.set(i, v.dpdvs);
|
||||||
|
self.dndus.set(i, v.dndus);
|
||||||
|
self.dndvs.set(i, v.dndvs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct ShadowRayWorkItem {
|
pub struct ShadowRayWorkItem {
|
||||||
pub ray_o: Point3f,
|
pub ray: Ray,
|
||||||
pub ray_d: Vector3f,
|
|
||||||
pub ray_time: Float,
|
|
||||||
pub t_max: Float,
|
pub t_max: Float,
|
||||||
pub lambda: SampledWavelengths,
|
pub lambda: SampledWavelengths,
|
||||||
pub l_d: SampledSpectrum,
|
pub l_d: SampledSpectrum,
|
||||||
|
pub r_u: SampledSpectrum,
|
||||||
|
pub r_l: SampledSpectrum,
|
||||||
pub pixel_index: u32,
|
pub pixel_index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct ShadowRayWorkItemSoA {
|
pub struct ShadowRayWorkItemSoA {
|
||||||
pub ray_o: SoABuffer<Point3f>,
|
pub ray: SoABuffer<Ray>,
|
||||||
pub ray_d: SoABuffer<Vector3f>,
|
|
||||||
pub ray_time: SoABuffer<Float>,
|
|
||||||
pub t_max: SoABuffer<Float>,
|
pub t_max: SoABuffer<Float>,
|
||||||
pub lambda: SoABuffer<SampledWavelengths>,
|
pub lambda: SoABuffer<SampledWavelengths>,
|
||||||
pub l_d: SoABuffer<SampledSpectrum>,
|
pub l_d: SoABuffer<SampledSpectrum>,
|
||||||
|
pub r_u: SoABuffer<SampledSpectrum>,
|
||||||
|
pub r_l: SoABuffer<SampledSpectrum>,
|
||||||
pub pixel_index: SoABuffer<u32>,
|
pub pixel_index: SoABuffer<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -447,39 +492,61 @@ impl SoA for ShadowRayWorkItemSoA {
|
||||||
|
|
||||||
fn allocate(n: u32, alloc: &dyn SoAAllocator) -> Self {
|
fn allocate(n: u32, alloc: &dyn SoAAllocator) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ray_o: alloc_soa_buffer(n, alloc),
|
ray: alloc_soa_buffer(n, alloc),
|
||||||
ray_d: alloc_soa_buffer(n, alloc),
|
|
||||||
ray_time: alloc_soa_buffer(n, alloc),
|
|
||||||
t_max: alloc_soa_buffer(n, alloc),
|
t_max: alloc_soa_buffer(n, alloc),
|
||||||
lambda: alloc_soa_buffer(n, alloc),
|
lambda: alloc_soa_buffer(n, alloc),
|
||||||
l_d: alloc_soa_buffer(n, alloc),
|
l_d: alloc_soa_buffer(n, alloc),
|
||||||
pixel_index: alloc_soa_buffer(n, alloc),
|
pixel_index: alloc_soa_buffer(n, alloc),
|
||||||
|
r_u: alloc_soa_buffer(n, alloc),
|
||||||
|
r_l: alloc_soa_buffer(n, alloc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn get(&self, i: usize) -> ShadowRayWorkItem {
|
unsafe fn get(&self, i: usize) -> ShadowRayWorkItem {
|
||||||
ShadowRayWorkItem {
|
ShadowRayWorkItem {
|
||||||
ray_o: self.ray_o.get(i),
|
ray: self.ray.get(i),
|
||||||
ray_d: self.ray_d.get(i),
|
|
||||||
ray_time: self.ray_time.get(i),
|
|
||||||
t_max: self.t_max.get(i),
|
t_max: self.t_max.get(i),
|
||||||
lambda: self.lambda.get(i),
|
lambda: self.lambda.get(i),
|
||||||
l_d: self.l_d.get(i),
|
l_d: self.l_d.get(i),
|
||||||
pixel_index: self.pixel_index.get(i),
|
pixel_index: self.pixel_index.get(i),
|
||||||
|
r_u: self.r_u.get(i),
|
||||||
|
r_l: self.r_l.get(i),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn set(&self, i: usize, v: ShadowRayWorkItem) {
|
unsafe fn set(&self, i: usize, v: ShadowRayWorkItem) {
|
||||||
self.ray_o.set(i, v.ray_o);
|
self.ray.set(i, v.ray);
|
||||||
self.ray_d.set(i, v.ray_d);
|
|
||||||
self.ray_time.set(i, v.ray_time);
|
|
||||||
self.t_max.set(i, v.t_max);
|
self.t_max.set(i, v.t_max);
|
||||||
self.lambda.set(i, v.lambda);
|
self.lambda.set(i, v.lambda);
|
||||||
self.l_d.set(i, v.l_d);
|
self.l_d.set(i, v.l_d);
|
||||||
self.pixel_index.set(i, v.pixel_index);
|
self.pixel_index.set(i, v.pixel_index);
|
||||||
|
self.r_u.set(i, v.r_u);
|
||||||
|
self.r_l.set(i, v.r_l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct RaySamples {
|
||||||
|
pub direct: DirectSamples,
|
||||||
|
pub indirect: IndirectSamples,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct DirectSamples {
|
||||||
|
pub uc: Float,
|
||||||
|
pub u: Point2f,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct IndirectSamples {
|
||||||
|
pub uc: Float,
|
||||||
|
pub u: Point2f,
|
||||||
|
pub rr: Float,
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct MediumSampleWorkItem {
|
pub struct MediumSampleWorkItem {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use shared::core::interaction::InteractionTrait;
|
||||||
use shared::core::primitive::PrimitiveTrait;
|
use shared::core::primitive::PrimitiveTrait;
|
||||||
use shared::core::sampler::CameraSample;
|
use shared::core::sampler::CameraSample;
|
||||||
use shared::spectra::{SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN};
|
use shared::spectra::{SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN};
|
||||||
|
use crate::wavefront::integrator::CpuWavefrontRenderer;
|
||||||
use shared::Float;
|
use shared::Float;
|
||||||
|
|
||||||
pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> {
|
pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> {
|
||||||
|
|
@ -125,5 +126,4 @@ pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,13 @@ use crate::lights::sampler::create_light_sampler;
|
||||||
use crate::utils::parallel::{run_async, AsyncJob};
|
use crate::utils::parallel::{run_async, AsyncJob};
|
||||||
use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
|
use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
|
||||||
use crate::utils::resolve_filename;
|
use crate::utils::resolve_filename;
|
||||||
|
use crate::wavefront::{CreateWavefront, CpuAggregate, CpuWavefrontRenderer};
|
||||||
use crate::{Arena, ArenaUpload, FileLoc};
|
use crate::{Arena, ArenaUpload, FileLoc};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use shared::core::aggregates::{BVHAggregate, SplitMethod};
|
use shared::core::aggregates::{BVHAggregate, SplitMethod};
|
||||||
use shared::core::camera::CameraTrait;
|
|
||||||
use shared::core::camera::Camera;
|
use shared::core::camera::Camera;
|
||||||
|
use shared::core::camera::CameraTrait;
|
||||||
use shared::core::color::LINEAR;
|
use shared::core::color::LINEAR;
|
||||||
use shared::core::film::Film;
|
use shared::core::film::Film;
|
||||||
use shared::core::filter::Filter;
|
use shared::core::filter::Filter;
|
||||||
|
|
@ -724,81 +725,10 @@ impl BasicScene {
|
||||||
aggregate: Arc<Primitive>,
|
aggregate: Arc<Primitive>,
|
||||||
lights: Vec<Arc<Light>>,
|
lights: Vec<Arc<Light>>,
|
||||||
arena: &Arena,
|
arena: &Arena,
|
||||||
) -> WavefrontPathIntegrator<CpuAggregate> {
|
) -> CpuWavefrontRenderer {
|
||||||
let entity = self.integrator.lock().clone().unwrap();
|
let integrator_entity = self.integrator.lock().clone().unwrap();
|
||||||
let max_depth = entity
|
let params = &integrator_entity.parameters;
|
||||||
.parameters
|
CpuWavefrontRenderer::create(params.clone(), camera, sampler, aggregate, lights, arena)
|
||||||
.get_one_int("maxdepth", 5)
|
|
||||||
.expect("Could not obtain depth value");
|
|
||||||
let regularize = entity
|
|
||||||
.parameters
|
|
||||||
.get_one_bool("regularize", false)
|
|
||||||
.expect("Could not obtain regularize flag value");
|
|
||||||
|
|
||||||
let spp = sampler.samples_per_pixel() as u32;
|
|
||||||
let film = camera.base().film;
|
|
||||||
let pixel_bounds = film.pixel_bounds();
|
|
||||||
let filter = Ptr::from(&film.base().filter);
|
|
||||||
let light_sampler = create_light_sampler("power", &lights, arena);
|
|
||||||
let res_x = pixel_bounds.diagonal().x() as u32;
|
|
||||||
let max_samples = 1024u32 * 1024;
|
|
||||||
let scanlines_per_pass = (max_samples / res_x).max(1);
|
|
||||||
let max_queue_size = res_x * scanlines_per_pass;
|
|
||||||
|
|
||||||
let mut infinite_lights = gvec();
|
|
||||||
for light in &lights {
|
|
||||||
if light.light_type().is_infinite() {
|
|
||||||
infinite_lights.push(arena.alloc(**light));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let cpu_aggregate = CpuAggregate::new(*aggregate);
|
|
||||||
|
|
||||||
WavefrontPathIntegrator {
|
|
||||||
aggregate: cpu_aggregate,
|
|
||||||
camera: (*camera).clone(),
|
|
||||||
sampler: (*sampler).clone(),
|
|
||||||
max_depth: max_depth.try_into().unwrap(),
|
|
||||||
film,
|
|
||||||
filter,
|
|
||||||
samples_per_pixel: spp,
|
|
||||||
regularize,
|
|
||||||
infinite_lights,
|
|
||||||
max_queue_size,
|
|
||||||
scanlines_per_pass,
|
|
||||||
light_sampler,
|
|
||||||
ray_queues: [
|
|
||||||
WorkQueue::new(
|
|
||||||
RayWorkItemSoA::allocate(max_queue_size, arena),
|
|
||||||
max_queue_size,
|
|
||||||
),
|
|
||||||
WorkQueue::new(
|
|
||||||
RayWorkItemSoA::allocate(max_queue_size, arena),
|
|
||||||
max_queue_size,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
shadow_ray_queue: WorkQueue::new(
|
|
||||||
ShadowRayWorkItemSoA::allocate(max_queue_size, arena),
|
|
||||||
max_queue_size,
|
|
||||||
),
|
|
||||||
escaped_ray_queue: WorkQueue::new(
|
|
||||||
EscapedRayWorkItemSoA::allocate(max_queue_size, arena),
|
|
||||||
max_queue_size,
|
|
||||||
),
|
|
||||||
hit_area_light_queue: WorkQueue::new(
|
|
||||||
HitAreaLightWorkItemSoA::allocate(max_queue_size, arena),
|
|
||||||
max_queue_size,
|
|
||||||
),
|
|
||||||
basic_eval_material_queue: WorkQueue::new(
|
|
||||||
MaterialEvalWorkItemSoA::allocate(max_queue_size, arena),
|
|
||||||
max_queue_size,
|
|
||||||
),
|
|
||||||
universal_eval_material_queue: WorkQueue::new(
|
|
||||||
MaterialEvalWorkItemSoA::allocate(max_queue_size, arena),
|
|
||||||
max_queue_size,
|
|
||||||
),
|
|
||||||
pixel_sample_state: PixelSampleState::allocate(max_queue_size, arena),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@ use crate::core::film::FilmTrait;
|
||||||
use crate::core::image::{HostImage, ImageIO, ImageMetadata};
|
use crate::core::image::{HostImage, ImageIO, ImageMetadata};
|
||||||
use crate::globals::get_options;
|
use crate::globals::get_options;
|
||||||
use crate::spectra::get_spectra_context;
|
use crate::spectra::get_spectra_context;
|
||||||
use crate::Arena;
|
use crate::{Arena, PbrtProgress};
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
|
||||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use shared::core::camera::{Camera, CameraTrait};
|
use shared::core::camera::{Camera, CameraTrait};
|
||||||
use shared::core::geometry::{Bounds2i, Point2i};
|
use shared::core::geometry::{Bounds2i, Point2i};
|
||||||
|
|
@ -17,45 +16,6 @@ use shared::Float;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
struct PbrtProgress {
|
|
||||||
bar: ProgressBar,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PbrtProgress {
|
|
||||||
fn new(total_work: u64, description: &str, quiet: bool) -> Self {
|
|
||||||
if quiet {
|
|
||||||
return Self {
|
|
||||||
bar: ProgressBar::hidden(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let bar = ProgressBar::new(total_work);
|
|
||||||
|
|
||||||
bar.set_style(
|
|
||||||
ProgressStyle::default_bar()
|
|
||||||
.template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}")
|
|
||||||
.unwrap()
|
|
||||||
.progress_chars("=>-"),
|
|
||||||
);
|
|
||||||
|
|
||||||
bar.set_message(description.to_string());
|
|
||||||
|
|
||||||
Self { bar }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&self, amount: u64) {
|
|
||||||
self.bar.inc(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn done(&self) {
|
|
||||||
self.bar.finish_with_message("Done");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn elapsed_seconds(&self) -> f32 {
|
|
||||||
self.bar.elapsed().as_secs_f32()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_tiles(bounds: Bounds2i) -> Vec<Bounds2i> {
|
fn generate_tiles(bounds: Bounds2i) -> Vec<Bounds2i> {
|
||||||
let mut tiles = Vec::new();
|
let mut tiles = Vec::new();
|
||||||
const TILE_SIZE: i32 = 16;
|
const TILE_SIZE: i32 = 16;
|
||||||
|
|
@ -101,9 +61,15 @@ pub fn render<T>(
|
||||||
|
|
||||||
let sample_bounds = camera.get_film().sample_bounds();
|
let sample_bounds = camera.get_film().sample_bounds();
|
||||||
let pixel_bounds = Bounds2i::from_points(
|
let pixel_bounds = Bounds2i::from_points(
|
||||||
Point2i::new(sample_bounds.p_min.x().floor() as i32, sample_bounds.p_min.y().floor() as i32),
|
Point2i::new(
|
||||||
Point2i::new(sample_bounds.p_max.x().ceil() as i32, sample_bounds.p_max.y().ceil() as i32),
|
sample_bounds.p_min.x().floor() as i32,
|
||||||
);
|
sample_bounds.p_min.y().floor() as i32,
|
||||||
|
),
|
||||||
|
Point2i::new(
|
||||||
|
sample_bounds.p_max.x().ceil() as i32,
|
||||||
|
sample_bounds.p_max.y().ceil() as i32,
|
||||||
|
),
|
||||||
|
);
|
||||||
println!(
|
println!(
|
||||||
"pixel_bounds: {:?}, area: {}",
|
"pixel_bounds: {:?}, area: {}",
|
||||||
pixel_bounds,
|
pixel_bounds,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub mod textures;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod wavefront;
|
pub mod wavefront;
|
||||||
|
|
||||||
pub use utils::{Arena, FileLoc, ParameterDictionary, Upload, ArenaUpload};
|
pub use utils::{Arena, ArenaUpload, FileLoc, ParameterDictionary, PbrtProgress, Upload};
|
||||||
pub const MAX_TAGS: u32 = 16;
|
pub const MAX_TAGS: u32 = 16;
|
||||||
pub use shared::{BasicPBRTOptions, PBRTOptions};
|
|
||||||
pub use globals::{get_options, init_pbrt};
|
pub use globals::{get_options, init_pbrt};
|
||||||
|
pub use shared::{BasicPBRTOptions, PBRTOptions};
|
||||||
|
|
|
||||||
|
|
@ -53,3 +53,42 @@ pub fn f16_to_f32(bits: u16) -> f32 {
|
||||||
f16::from_bits(bits).to_f32()
|
f16::from_bits(bits).to_f32()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PbrtProgress {
|
||||||
|
bar: indicatif::ProgressBar,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PbrtProgress {
|
||||||
|
pub fn new(total_work: u64, description: &str, quiet: bool) -> Self {
|
||||||
|
if quiet {
|
||||||
|
return Self {
|
||||||
|
bar: indicatif::ProgressBar::hidden(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let bar = indicatif::ProgressBar::new(total_work);
|
||||||
|
|
||||||
|
bar.set_style(
|
||||||
|
indicatif::ProgressStyle::default_bar()
|
||||||
|
.template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}")
|
||||||
|
.unwrap()
|
||||||
|
.progress_chars("=>-"),
|
||||||
|
);
|
||||||
|
|
||||||
|
bar.set_message(description.to_string());
|
||||||
|
|
||||||
|
Self { bar }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&self, amount: u64) {
|
||||||
|
self.bar.inc(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn done(&self) {
|
||||||
|
self.bar.finish_with_message("Done");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn elapsed_seconds(&self) -> f32 {
|
||||||
|
self.bar.elapsed().as_secs_f32()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
use crate::core::texture::{BasicTextureEvaluator, TextureEvaluator, UniversalTextureEvaluator};
|
|
||||||
use crate::core::primitive::{Primitive, PrimitiveTrait};
|
|
||||||
use crate::core::geometry::{Bounds3f, Ray, Vector3f, VectorLike};
|
|
||||||
use crate::core::interaction::InteractionTrait;
|
|
||||||
use crate::core::material::MaterialTrait;
|
|
||||||
use crate::globals::get_options;
|
use crate::globals::get_options;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
use shared::core::geometry::{Bounds3f, Ray, VectorLike};
|
||||||
|
use shared::core::interaction::{InteractionTrait, SurfaceInteraction};
|
||||||
|
use shared::core::material::MaterialTrait;
|
||||||
|
use shared::core::primitive::{Primitive, PrimitiveTrait};
|
||||||
|
use shared::core::texture::BasicTextureEvaluator;
|
||||||
|
use shared::core::texture::TextureEvaluator;
|
||||||
|
use shared::wavefront::workitems::*;
|
||||||
use shared::wavefront::WavefrontAggregate;
|
use shared::wavefront::WavefrontAggregate;
|
||||||
|
use shared::{Float, Ptr};
|
||||||
|
|
||||||
pub struct CpuAggregate {
|
pub struct CpuAggregate {
|
||||||
pub aggregate: Primitive,
|
pub aggregate: Primitive,
|
||||||
|
|
@ -17,6 +20,13 @@ impl CpuAggregate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn enqueue_after_intersection(
|
||||||
|
// r: RayWorkItem, ray_medium: Medium, t_max: Float, intr: SurfaceInteraction,
|
||||||
|
// mut medium_sample_queue: &MediumQueue, mut next_ray_queue: &RayQueue,
|
||||||
|
// mut hit_area_light_queue: &HitAreaLightQueue, mut basic_eval_mtl_q: &MaterialEvalQueue,
|
||||||
|
// mut universal_eval_mlt_q: MaterialEvalQueue) {
|
||||||
|
// }
|
||||||
|
|
||||||
impl WavefrontAggregate for CpuAggregate {
|
impl WavefrontAggregate for CpuAggregate {
|
||||||
fn bounds(&self) -> Bounds3f {
|
fn bounds(&self) -> Bounds3f {
|
||||||
self.aggregate.bounds()
|
self.aggregate.bounds()
|
||||||
|
|
@ -31,86 +41,62 @@ impl WavefrontAggregate for CpuAggregate {
|
||||||
basic_eval_mtl_q: &MaterialEvalQueue,
|
basic_eval_mtl_q: &MaterialEvalQueue,
|
||||||
universal_eval_mtl_q: &MaterialEvalQueue,
|
universal_eval_mtl_q: &MaterialEvalQueue,
|
||||||
next_ray_q: &RayQueue,
|
next_ray_q: &RayQueue,
|
||||||
pixel_sample_state: &PixelSampleState,
|
_pixel_sample_state: &PixelSampleState,
|
||||||
) {
|
) {
|
||||||
let n_rays = ray_q.size().min(max_rays as u32);
|
let n_rays = ray_q.size().min(max_rays as u32);
|
||||||
let options = get_options();
|
|
||||||
|
|
||||||
|
// Intersect ray with the scene and enqueue resulting work
|
||||||
(0..n_rays as usize).into_par_iter().for_each(|i| {
|
(0..n_rays as usize).into_par_iter().for_each(|i| {
|
||||||
let work = unsafe { ray_q.get(i) };
|
let r = unsafe { ray_q.get(i) };
|
||||||
|
|
||||||
let ray = Ray::new(work.ray_o, work.ray_d, Some(work.ray_time), work.ray_medium);
|
let Some(si) = self.aggregate.intersect(&r.ray, None) else {
|
||||||
|
// EnqueueMiss
|
||||||
let pi = work.pixel_index as usize;
|
|
||||||
let beta = pixel_sample_state.beta.get(pi);
|
|
||||||
let r_u = pixel_sample_state.r_u.get(pi);
|
|
||||||
let r_l = pixel_sample_state.r_l.get(pi);
|
|
||||||
let lambda = pixel_sample_state.lambda.get(pi);
|
|
||||||
let depth = pixel_sample_state.depth.get(pi);
|
|
||||||
let specular_bounce = pixel_sample_state.specular_bounce.get(pi) != 0;
|
|
||||||
let any_non_specular = pixel_sample_state.any_non_specular_bounces.get(pi) != 0;
|
|
||||||
let eta_scale = pixel_sample_state.eta_scale.get(pi);
|
|
||||||
let prev_intr_ctx = pixel_sample_state.prev_intr_ctx.get(pi);
|
|
||||||
|
|
||||||
let Some(si) = self.aggregate.intersect(&ray, None) else {
|
|
||||||
escaped_ray_q.push(EscapedRayWorkItem {
|
escaped_ray_q.push(EscapedRayWorkItem {
|
||||||
ray_o: work.ray_o,
|
ray_o: r.ray.o,
|
||||||
ray_d: work.ray_d,
|
ray_d: r.ray.d,
|
||||||
lambda,
|
lambda: r.lambda,
|
||||||
pixel_index: work.pixel_index,
|
pixel_index: r.pixel_index,
|
||||||
beta,
|
beta: r.beta,
|
||||||
r_u,
|
r_u: r.r_u,
|
||||||
r_l,
|
r_l: r.r_l,
|
||||||
depth,
|
depth: r.depth,
|
||||||
specular_bounce,
|
specular_bounce: r.specular_bounce != 0,
|
||||||
prev_intr_ctx,
|
prev_intr_ctx: r.prev_intr_ctx,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let intr = &si.intr;
|
let intr = &si.intr;
|
||||||
|
|
||||||
|
// Medium transition
|
||||||
if intr.material.is_null() {
|
if intr.material.is_null() {
|
||||||
let ray_o = Ray::offset_origin(&intr.pi(), &intr.n(), &work.ray_d);
|
let mut next = r;
|
||||||
let ray_medium = if work.ray_d.dot(intr.n().into()) > 0.0 {
|
intr.spawn_ray(r.ray.d);
|
||||||
intr.common.medium_interface.outside
|
next.ray = intr.spawn_ray(r.ray.d);
|
||||||
} else {
|
next_ray_q.push(next);
|
||||||
intr.common.medium_interface.inside
|
|
||||||
};
|
|
||||||
next_ray_q.push(RayWorkItem {
|
|
||||||
ray_o,
|
|
||||||
ray_d: work.ray_d,
|
|
||||||
ray_time: work.ray_time,
|
|
||||||
ray_medium,
|
|
||||||
has_differentials: work.has_differentials,
|
|
||||||
differential: work.differential,
|
|
||||||
pixel_index: work.pixel_index,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for area light hit
|
// Area light hit
|
||||||
if !intr.area_light.is_null() {
|
if !intr.area_light.is_null() {
|
||||||
hit_area_light_q.push(HitAreaLightWorkItem {
|
hit_area_light_q.push(HitAreaLightWorkItem {
|
||||||
area_light: intr.area_light,
|
area_light: intr.area_light,
|
||||||
p: intr.p(),
|
p: intr.p(),
|
||||||
n: intr.n(),
|
n: intr.n(),
|
||||||
uv: intr.common.uv,
|
uv: intr.common.uv,
|
||||||
wo: -work.ray_d,
|
wo: -r.ray.d,
|
||||||
lambda,
|
lambda: r.lambda,
|
||||||
pixel_index: work.pixel_index,
|
pixel_index: r.pixel_index,
|
||||||
beta,
|
beta: r.beta,
|
||||||
r_u,
|
r_u: r.r_u,
|
||||||
r_l,
|
r_l: r.r_l,
|
||||||
depth,
|
depth: r.depth,
|
||||||
specular_bounce,
|
specular_bounce: r.specular_bounce != 0,
|
||||||
prev_intr_ctx,
|
prev_intr_ctx: r.prev_intr_ctx,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine which material evaluation queue to use based on
|
// Material eval queue dispatch
|
||||||
// whether the material's textures can be evaluated with the
|
|
||||||
// basic evaluator (cheaper) or need the universal one.
|
|
||||||
let material = *intr.material.get().unwrap();
|
let material = *intr.material.get().unwrap();
|
||||||
let eval_q = if material.can_evaluate_textures(&BasicTextureEvaluator) {
|
let eval_q = if material.can_evaluate_textures(&BasicTextureEvaluator) {
|
||||||
basic_eval_mtl_q
|
basic_eval_mtl_q
|
||||||
|
|
@ -119,25 +105,29 @@ impl WavefrontAggregate for CpuAggregate {
|
||||||
};
|
};
|
||||||
|
|
||||||
eval_q.push(MaterialEvalWorkItem {
|
eval_q.push(MaterialEvalWorkItem {
|
||||||
p: intr.p(),
|
p: intr.pi(),
|
||||||
n: intr.n(),
|
n: intr.n(),
|
||||||
ns: intr.shading.n,
|
ns: intr.shading.n,
|
||||||
dpdu: intr.shading.dpdu,
|
dpdu: intr.shading.dpdu,
|
||||||
dpdv: intr.shading.dpdv,
|
dpdv: intr.shading.dpdv,
|
||||||
uv: intr.common.uv,
|
uv: intr.common.uv,
|
||||||
wo: -work.ray_d,
|
wo: intr.wo(),
|
||||||
time: work.ray_time,
|
time: r.ray.time,
|
||||||
face_index: intr.face_index,
|
face_index: intr.face_index,
|
||||||
material: intr.material,
|
material: intr.material,
|
||||||
area_light: intr.area_light,
|
area_light: intr.area_light,
|
||||||
medium_interface: intr.common.medium_interface,
|
medium_interface: intr.common.medium_interface,
|
||||||
pixel_index: work.pixel_index,
|
pixel_index: r.pixel_index,
|
||||||
lambda,
|
lambda: r.lambda,
|
||||||
beta,
|
beta: r.beta,
|
||||||
r_u,
|
r_u: r.r_u,
|
||||||
any_non_specular_bounces: any_non_specular,
|
any_non_specular_bounces: r.any_non_specular_bounces != 0,
|
||||||
depth,
|
depth: r.depth,
|
||||||
eta_scale,
|
eta_scale: r.eta_scale,
|
||||||
|
dpdus: intr.shading.dpdu,
|
||||||
|
dpdvs: intr.shading.dpdv,
|
||||||
|
dndus: intr.shading.dndu,
|
||||||
|
dndvs: intr.shading.dndv,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -150,23 +140,18 @@ impl WavefrontAggregate for CpuAggregate {
|
||||||
) {
|
) {
|
||||||
let n_rays = shadow_ray_q.size().min(max_rays as u32);
|
let n_rays = shadow_ray_q.size().min(max_rays as u32);
|
||||||
|
|
||||||
for i in 0..n_rays as usize {
|
(0..n_rays as usize).into_par_iter().for_each(|i| {
|
||||||
let work = unsafe { shadow_ray_q.get(i) };
|
let work = unsafe { shadow_ray_q.get(i) };
|
||||||
|
|
||||||
let ray = Ray::new(
|
let ray = Ray::new(work.ray_o, work.ray_d, Some(work.ray_time), Ptr::null());
|
||||||
work.ray_o,
|
|
||||||
work.ray_d,
|
|
||||||
Some(work.ray_time),
|
|
||||||
crate::Ptr::null(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if !self.aggregate.intersect_p(&ray, Some(work.t_max)) {
|
if !self.aggregate.intersect_p(&ray, Some(work.t_max)) {
|
||||||
let pi = work.pixel_index as usize;
|
let pi = work.pixel_index as usize;
|
||||||
|
let ld = work.l_d / (work.r_u + work.r_l).average();
|
||||||
let mut l = pixel_sample_state.l.get(pi);
|
let mut l = pixel_sample_state.l.get(pi);
|
||||||
l += work.l_d;
|
l += ld;
|
||||||
pixel_sample_state.l.set(pi, l);
|
pixel_sample_state.l.set(pi, l);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn intersect_shadow_tr(
|
fn intersect_shadow_tr(
|
||||||
|
|
@ -178,4 +163,3 @@ impl WavefrontAggregate for CpuAggregate {
|
||||||
self.intersect_shadow(max_rays, shadow_ray_q, pixel_sample_state);
|
self.intersect_shadow(max_rays, shadow_ray_q, pixel_sample_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
use super::CpuAggregate;
|
use super::CpuAggregate;
|
||||||
use crate::globals::get_options;
|
use crate::globals::get_options;
|
||||||
|
use crate::lights::sampler::create_light_sampler;
|
||||||
|
use crate::Arena;
|
||||||
|
use crate::ParameterDictionary;
|
||||||
|
use crate::PbrtProgress;
|
||||||
|
use rayon::prelude::*;
|
||||||
use shared::core::bxdf::{FArgs, TransportMode};
|
use shared::core::bxdf::{FArgs, TransportMode};
|
||||||
use shared::core::camera::{Camera, CameraTrait};
|
use shared::core::camera::{Camera, CameraTrait};
|
||||||
|
use shared::core::film::VisibleSurface;
|
||||||
use shared::core::filter::{Filter, FilterTrait};
|
use shared::core::filter::{Filter, FilterTrait};
|
||||||
use shared::core::geometry::{
|
use shared::core::geometry::{
|
||||||
Bounds2i, Point2f, Point2i, Point3f, Point3fi, Ray, RayDifferential, Vector2f, Vector3f,
|
Bounds2i, Point2f, Point2i, Point3f, Point3fi, Ray, RayDifferential, Vector2f, Vector3f,
|
||||||
|
|
@ -10,16 +16,126 @@ use shared::core::geometry::{
|
||||||
use shared::core::interaction::InteractionTrait;
|
use shared::core::interaction::InteractionTrait;
|
||||||
use shared::core::light::{Light, LightSampleContext, LightTrait};
|
use shared::core::light::{Light, LightSampleContext, LightTrait};
|
||||||
use shared::core::material::{MaterialEvalContext, MaterialTrait};
|
use shared::core::material::{MaterialEvalContext, MaterialTrait};
|
||||||
use shared::core::sampler::{CameraSample, Sampler, SamplerTrait};
|
use shared::core::primitive::Primitive;
|
||||||
|
use shared::core::sampler::{get_camera_sample, CameraSample, Sampler, SamplerTrait};
|
||||||
use shared::core::texture::{TextureEvalContext, UniversalTextureEvaluator};
|
use shared::core::texture::{TextureEvalContext, UniversalTextureEvaluator};
|
||||||
use shared::lights::sampler::{LightSampler, LightSamplerTrait};
|
use shared::lights::sampler::{LightSampler, LightSamplerTrait};
|
||||||
use shared::spectra::{SampledSpectrum, SampledWavelengths};
|
use shared::spectra::{SampledSpectrum, SampledWavelengths};
|
||||||
use shared::utils::math::square;
|
use shared::utils::math::square;
|
||||||
use shared::utils::sampling::power_heuristic;
|
use shared::utils::sampling::power_heuristic;
|
||||||
use shared::utils::soa::{SoA, SoAAllocator, WorkQueue};
|
use shared::utils::soa::{SoA, SoAAllocator, WorkQueue};
|
||||||
|
use shared::wavefront::workitems::*;
|
||||||
use shared::wavefront::{WavefrontAggregate, WavefrontPathIntegrator, WavefrontRenderer};
|
use shared::wavefront::{WavefrontAggregate, WavefrontPathIntegrator, WavefrontRenderer};
|
||||||
|
use shared::{gvec, Ptr};
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
|
pub struct CpuWavefrontRenderer(pub WavefrontPathIntegrator<CpuAggregate>);
|
||||||
|
|
||||||
|
impl Deref for CpuWavefrontRenderer {
|
||||||
|
type Target = WavefrontPathIntegrator<CpuAggregate>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for CpuWavefrontRenderer {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CreateWavefront
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
fn create(
|
||||||
|
parameters: ParameterDictionary,
|
||||||
|
camera: Arc<Camera>,
|
||||||
|
sampler: Arc<Sampler>,
|
||||||
|
aggregate: Arc<Primitive>,
|
||||||
|
lights: Vec<Arc<Light>>,
|
||||||
|
arena: &Arena,
|
||||||
|
) -> CpuWavefrontRenderer {
|
||||||
|
let max_depth = parameters
|
||||||
|
.get_one_int("maxdepth", 5)
|
||||||
|
.expect("Could not obtain depth value");
|
||||||
|
let regularize = parameters
|
||||||
|
.get_one_bool("regularize", false)
|
||||||
|
.expect("Could not obtain regularize flag value");
|
||||||
|
|
||||||
|
let spp = sampler.samples_per_pixel() as u32;
|
||||||
|
let film = camera.base().film;
|
||||||
|
let pixel_bounds = film.pixel_bounds();
|
||||||
|
let filter = Ptr::from(&film.base().filter);
|
||||||
|
let light_sampler = create_light_sampler("power", &lights, arena);
|
||||||
|
let res_x = pixel_bounds.diagonal().x() as u32;
|
||||||
|
let max_samples = 1024u32 * 1024;
|
||||||
|
let scanlines_per_pass = (max_samples / res_x).max(1);
|
||||||
|
let max_queue_size = res_x * scanlines_per_pass;
|
||||||
|
|
||||||
|
let mut infinite_lights = gvec();
|
||||||
|
for light in &lights {
|
||||||
|
if light.light_type().is_infinite() {
|
||||||
|
infinite_lights.push(arena.alloc(**light));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for light in
|
||||||
|
|
||||||
|
let cpu_aggregate = CpuAggregate::new(*aggregate);
|
||||||
|
|
||||||
|
CpuWavefrontRenderer(WavefrontPathIntegrator {
|
||||||
|
aggregate: cpu_aggregate,
|
||||||
|
camera: (*camera).clone(),
|
||||||
|
sampler: (*sampler).clone(),
|
||||||
|
max_depth: max_depth.try_into().unwrap(),
|
||||||
|
film,
|
||||||
|
filter,
|
||||||
|
samples_per_pixel: spp,
|
||||||
|
regularize,
|
||||||
|
infinite_lights,
|
||||||
|
max_queue_size,
|
||||||
|
scanlines_per_pass,
|
||||||
|
light_sampler,
|
||||||
|
ray_queues: [
|
||||||
|
WorkQueue::new(
|
||||||
|
RayWorkItemSoA::allocate(max_queue_size, arena),
|
||||||
|
max_queue_size,
|
||||||
|
),
|
||||||
|
WorkQueue::new(
|
||||||
|
RayWorkItemSoA::allocate(max_queue_size, arena),
|
||||||
|
max_queue_size,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
shadow_ray_queue: WorkQueue::new(
|
||||||
|
ShadowRayWorkItemSoA::allocate(max_queue_size, arena),
|
||||||
|
max_queue_size,
|
||||||
|
),
|
||||||
|
escaped_ray_queue: WorkQueue::new(
|
||||||
|
EscapedRayWorkItemSoA::allocate(max_queue_size, arena),
|
||||||
|
max_queue_size,
|
||||||
|
),
|
||||||
|
hit_area_light_queue: WorkQueue::new(
|
||||||
|
HitAreaLightWorkItemSoA::allocate(max_queue_size, arena),
|
||||||
|
max_queue_size,
|
||||||
|
),
|
||||||
|
basic_eval_material_queue: WorkQueue::new(
|
||||||
|
MaterialEvalWorkItemSoA::allocate(max_queue_size, arena),
|
||||||
|
max_queue_size,
|
||||||
|
),
|
||||||
|
universal_eval_material_queue: WorkQueue::new(
|
||||||
|
MaterialEvalWorkItemSoA::allocate(max_queue_size, arena),
|
||||||
|
max_queue_size,
|
||||||
|
),
|
||||||
|
pixel_sample_state: PixelSampleState::allocate(max_queue_size, arena),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateWavefront for CpuWavefrontRenderer {}
|
||||||
|
|
||||||
|
impl CpuWavefrontRenderer {
|
||||||
pub fn render(&mut self) {
|
pub fn render(&mut self) {
|
||||||
let film = self.camera.get_film();
|
let film = self.camera.get_film();
|
||||||
let filter = film.get_filter();
|
let filter = film.get_filter();
|
||||||
|
|
@ -31,7 +147,6 @@ impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
|
||||||
let progress = PbrtProgress::new(total_work, "Rendering", options.quiet);
|
let progress = PbrtProgress::new(total_work, "Rendering", options.quiet);
|
||||||
|
|
||||||
for sample_index in 0..self.samples_per_pixel {
|
for sample_index in 0..self.samples_per_pixel {
|
||||||
// Process image in scanline batches
|
|
||||||
let mut y0 = pixel_bounds.p_min.y();
|
let mut y0 = pixel_bounds.p_min.y();
|
||||||
while y0 < pixel_bounds.p_max.y() {
|
while y0 < pixel_bounds.p_max.y() {
|
||||||
let y1 = (y0 + self.scanlines_per_pass as i32).min(pixel_bounds.p_max.y());
|
let y1 = (y0 + self.scanlines_per_pass as i32).min(pixel_bounds.p_max.y());
|
||||||
|
|
@ -45,7 +160,7 @@ impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
|
||||||
let current = (depth % 2) as usize;
|
let current = (depth % 2) as usize;
|
||||||
let next = ((depth + 1) % 2) as usize;
|
let next = ((depth + 1) % 2) as usize;
|
||||||
|
|
||||||
// Reset output queues before intersection
|
// Reset queues before tracing next batch of rays
|
||||||
self.ray_queues[next].reset();
|
self.ray_queues[next].reset();
|
||||||
self.escaped_ray_queue.reset();
|
self.escaped_ray_queue.reset();
|
||||||
self.hit_area_light_queue.reset();
|
self.hit_area_light_queue.reset();
|
||||||
|
|
@ -53,12 +168,12 @@ impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
|
||||||
self.universal_eval_material_queue.reset();
|
self.universal_eval_material_queue.reset();
|
||||||
self.shadow_ray_queue.reset();
|
self.shadow_ray_queue.reset();
|
||||||
|
|
||||||
// Skip if no rays to trace
|
|
||||||
if self.ray_queues[current].size() == 0 {
|
if self.ray_queues[current].size() == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sorting of rays into output queues
|
self.generate_ray_samples(depth, sample_index);
|
||||||
|
|
||||||
self.aggregate.intersect_closest(
|
self.aggregate.intersect_closest(
|
||||||
self.max_queue_size as usize,
|
self.max_queue_size as usize,
|
||||||
&self.ray_queues[current],
|
&self.ray_queues[current],
|
||||||
|
|
@ -70,40 +185,31 @@ impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
|
||||||
&self.pixel_sample_state,
|
&self.pixel_sample_state,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Infinite light contributions
|
|
||||||
self.handle_escaped_rays();
|
self.handle_escaped_rays();
|
||||||
|
|
||||||
// Area light contributions
|
|
||||||
self.handle_emissive_intersections();
|
self.handle_emissive_intersections();
|
||||||
|
|
||||||
// Last depth — don't evaluate materials or sample lights
|
|
||||||
if depth == self.max_depth {
|
if depth == self.max_depth {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate materials, sample BSDFs, sample direct lighting
|
|
||||||
// This pushes to shadow_ray_queue and ray_queues[next]
|
|
||||||
self.evaluate_materials_and_bsdfs(depth);
|
self.evaluate_materials_and_bsdfs(depth);
|
||||||
|
|
||||||
// Add direct lighting to pixels
|
|
||||||
self.aggregate.intersect_shadow(
|
self.aggregate.intersect_shadow(
|
||||||
self.max_queue_size as usize,
|
self.max_queue_size as usize,
|
||||||
&self.shadow_ray_queue,
|
&self.shadow_ray_queue,
|
||||||
&self.pixel_sample_state,
|
&self.pixel_sample_state,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_film(y0, y1, &pixel_bounds);
|
self.update_film(y0, y1, &pixel_bounds);
|
||||||
let batch_pixels =
|
let batch_pixels =
|
||||||
((y1 - y0) * (pixel_bounds.p_max.x() - pixel_bounds.p_min.x())) as u64;
|
((y1 - y0) * (pixel_bounds.p_max.x() - pixel_bounds.p_min.x())) as u64;
|
||||||
progress.inc(batch_pixels);
|
progress.update(batch_pixels);
|
||||||
|
|
||||||
y0 = y1;
|
y0 = y1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stage 1: Generate camera rays for scanlines [y0, y1).
|
|
||||||
fn generate_camera_rays(
|
fn generate_camera_rays(
|
||||||
&mut self,
|
&mut self,
|
||||||
y0: i32,
|
y0: i32,
|
||||||
|
|
@ -111,133 +217,153 @@ impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
|
||||||
sample_index: u32,
|
sample_index: u32,
|
||||||
pixel_bounds: &Bounds2i,
|
pixel_bounds: &Bounds2i,
|
||||||
) {
|
) {
|
||||||
// For each pixel in the scanline range, generate a camera ray
|
let filter = self.filter;
|
||||||
// and push it to the ray queue. Also initialize the PixelSampleState.
|
let film = self.film;
|
||||||
for y in y0..y1 {
|
let camera = &self.camera;
|
||||||
for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() {
|
let sampler_proto = &self.sampler;
|
||||||
let p_pixel = Point2i::new(x, y);
|
let pixel_sample_state = &self.pixel_sample_state;
|
||||||
|
let ray_queue = &self.ray_queues[0];
|
||||||
|
|
||||||
// TODO: proper sampler state per pixel/sample
|
let x_resolution = pixel_bounds.p_max.x() - pixel_bounds.p_min.x();
|
||||||
// For now, use a simple approach
|
|
||||||
self.sampler
|
|
||||||
.start_pixel_sample(p_pixel, sample_index as i32, Some(0));
|
|
||||||
|
|
||||||
let lambda = SampledWavelengths::sample_visible(self.sampler.get1d());
|
// Iterate the whole queue, exactly like pbrt's ParallelFor(maxQueueSize).
|
||||||
|
// The loop index IS the pixelSampleState key; pPixel is derived from it,
|
||||||
let camera_sample = crate::core::sampler::get_camera_sample(
|
// and every later kernel addresses state by this same absolute index.
|
||||||
&mut self.sampler,
|
(0..self.max_queue_size as usize)
|
||||||
p_pixel,
|
.into_par_iter()
|
||||||
&self.filter,
|
.for_each(|pixel_index| {
|
||||||
|
let p_pixel = Point2i::new(
|
||||||
|
pixel_bounds.p_min.x() + (pixel_index as i32 % x_resolution),
|
||||||
|
y0 + (pixel_index as i32 / x_resolution),
|
||||||
);
|
);
|
||||||
|
pixel_sample_state.p_pixel.set(pixel_index, p_pixel);
|
||||||
|
|
||||||
let Some(camera_ray) = self.camera.generate_ray(camera_sample, &lambda) else {
|
// Skipped pixels contribute nothing; their slots are simply never
|
||||||
continue;
|
// populated, and update_film filters them by the same bounds test.
|
||||||
|
if !pixel_bounds.contains_exclusive(p_pixel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sampler = sampler_proto.clone();
|
||||||
|
sampler.start_pixel_sample(p_pixel, sample_index as i32, Some(0));
|
||||||
|
|
||||||
|
let lu = sampler.get1d();
|
||||||
|
let lambda = film.sample_wavelengths(lu);
|
||||||
|
let camera_sample = get_camera_sample(&mut sampler, p_pixel, &filter);
|
||||||
|
|
||||||
|
pixel_sample_state
|
||||||
|
.l
|
||||||
|
.set(pixel_index, SampledSpectrum::new(0.0));
|
||||||
|
pixel_sample_state.lambda.set(pixel_index, lambda);
|
||||||
|
pixel_sample_state
|
||||||
|
.filter_weight
|
||||||
|
.set(pixel_index, camera_sample.filter_weight);
|
||||||
|
pixel_sample_state
|
||||||
|
.p_film
|
||||||
|
.set(pixel_index, camera_sample.p_film);
|
||||||
|
|
||||||
|
let Some(camera_ray) = camera.generate_ray(camera_sample, &lambda) else {
|
||||||
|
pixel_sample_state
|
||||||
|
.camera_ray_weight
|
||||||
|
.set(pixel_index, SampledSpectrum::new(0.0));
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compute pixel index for this sample
|
pixel_sample_state
|
||||||
let pixel_index = self.ray_queues[0].size();
|
.camera_ray_weight
|
||||||
|
.set(pixel_index, camera_ray.weight);
|
||||||
|
|
||||||
// Initialize persistent pixel state
|
ray_queue.push(RayWorkItem {
|
||||||
let pi = pixel_index as usize;
|
ray: camera_ray.ray,
|
||||||
self.pixel_sample_state.l.set(pi, SampledSpectrum::new(0.0));
|
depth: 0,
|
||||||
self.pixel_sample_state.beta.set(pi, camera_ray.weight);
|
pixel_index: pixel_index as u32,
|
||||||
self.pixel_sample_state.lambda.set(pi, lambda);
|
lambda,
|
||||||
self.pixel_sample_state
|
beta: SampledSpectrum::new(1.0),
|
||||||
.r_u
|
r_u: SampledSpectrum::new(1.0),
|
||||||
.set(pi, SampledSpectrum::new(1.0));
|
r_l: SampledSpectrum::new(1.0),
|
||||||
self.pixel_sample_state
|
prev_intr_ctx: LightSampleContext::default(),
|
||||||
.r_l
|
eta_scale: 1.0,
|
||||||
.set(pi, SampledSpectrum::new(1.0));
|
specular_bounce: 0,
|
||||||
self.pixel_sample_state.depth.set(pi, 0);
|
any_non_specular_bounces: 0,
|
||||||
self.pixel_sample_state.specular_bounce.set(pi, 1);
|
|
||||||
self.pixel_sample_state.any_non_specular_bounces.set(pi, 0);
|
|
||||||
self.pixel_sample_state.eta_scale.set(pi, 1.0);
|
|
||||||
self.pixel_sample_state.p_film.set(pi, camera_sample.p_film);
|
|
||||||
self.pixel_sample_state
|
|
||||||
.filter_weight
|
|
||||||
.set(pi, camera_sample.filter_weight);
|
|
||||||
self.pixel_sample_state
|
|
||||||
.prev_intr_ctx
|
|
||||||
.set(pi, LightSampleContext::default());
|
|
||||||
|
|
||||||
// Push ray to queue
|
|
||||||
self.ray_queues[0].push(RayWorkItem {
|
|
||||||
ray_o: camera_ray.ray.o,
|
|
||||||
ray_d: camera_ray.ray.d,
|
|
||||||
ray_time: camera_ray.ray.time,
|
|
||||||
ray_medium: camera_ray.ray.medium,
|
|
||||||
pixel_index: pixel_index,
|
|
||||||
has_differentials: true,
|
|
||||||
differential: RayDifferential::default(),
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle escaped rays — evaluate infinite lights.
|
/// Evaluate infinite lights.
|
||||||
fn handle_escaped_rays(&self) {
|
fn handle_escaped_rays(&self) {
|
||||||
let n = self.escaped_ray_queue.size();
|
let n = self.escaped_ray_queue.size();
|
||||||
for i in 0..n as usize {
|
let infinite_lights = &self.infinite_lights;
|
||||||
let w = unsafe { self.escaped_ray_queue.storage.get(i) };
|
let light_sampler = &self.light_sampler;
|
||||||
|
let pixel_sample_state = &self.pixel_sample_state;
|
||||||
|
let escaped_ray_queue = &self.escaped_ray_queue;
|
||||||
|
|
||||||
|
(0..n as usize).into_par_iter().for_each(|i| {
|
||||||
|
let w = unsafe { escaped_ray_queue.storage.get(i) };
|
||||||
|
|
||||||
let mut l_contrib = SampledSpectrum::new(0.0);
|
let mut l_contrib = SampledSpectrum::new(0.0);
|
||||||
|
|
||||||
// Evaluate all infinite lights
|
for light_ptr in infinite_lights {
|
||||||
for light_ptr in &self.infinite_lights {
|
|
||||||
let light = light_ptr.get().unwrap();
|
let light = light_ptr.get().unwrap();
|
||||||
let ray = crate::core::geometry::Ray::new(w.ray_o, w.ray_d, None, Ptr::null());
|
let ray = Ray::new(w.ray_o, w.ray_d, None, Ptr::null());
|
||||||
let le = light.le(&ray, &w.lambda);
|
let le = light.le(&ray, &w.lambda);
|
||||||
if le.is_black() {
|
if le.is_black() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if w.depth == 0 || w.specular_bounce {
|
if w.depth == 0 || w.specular_bounce {
|
||||||
// No MIS for direct camera rays or specular bounces
|
|
||||||
l_contrib += w.beta * le / w.r_u.average();
|
l_contrib += w.beta * le / w.r_u.average();
|
||||||
} else {
|
} else {
|
||||||
// MIS with light sampling
|
// MIS: combine BSDF and light sampling weights via ratio tracking
|
||||||
// TODO: compute light PDF for MIS weight
|
let ctx = w.prev_intr_ctx;
|
||||||
// For now, use unidirectional weight only
|
let light_choice_pdf = light_sampler.pmf_with_context(&ctx, light);
|
||||||
l_contrib += w.beta * le / w.r_u.average();
|
let r_l = w.r_l * light_choice_pdf * light.pdf_li(&ctx, w.ray_d, true);
|
||||||
|
l_contrib += w.beta * le / (w.r_u + r_l).average();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !l_contrib.is_black() {
|
if !l_contrib.is_black() {
|
||||||
let pi = w.pixel_index as usize;
|
let pi = w.pixel_index as usize;
|
||||||
let mut l = self.pixel_sample_state.l.get(pi);
|
let mut l = pixel_sample_state.l.get(pi);
|
||||||
l += l_contrib;
|
l += l_contrib;
|
||||||
self.pixel_sample_state.l.set(pi, l);
|
pixel_sample_state.l.set(pi, l);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle emissive intersections — area light contribution with MIS.
|
|
||||||
fn handle_emissive_intersections(&self) {
|
fn handle_emissive_intersections(&self) {
|
||||||
let n = self.hit_area_light_queue.size();
|
let n = self.hit_area_light_queue.size();
|
||||||
for i in 0..n as usize {
|
let light_sampler = &self.light_sampler;
|
||||||
let w = unsafe { self.hit_area_light_queue.storage.get(i) };
|
let pixel_sample_state = &self.pixel_sample_state;
|
||||||
|
let hit_area_light_queue = &self.hit_area_light_queue;
|
||||||
|
|
||||||
|
(0..n as usize).into_par_iter().for_each(|i| {
|
||||||
|
let w = unsafe { hit_area_light_queue.storage.get(i) };
|
||||||
|
|
||||||
let light = w.area_light.get().unwrap();
|
let light = w.area_light.get().unwrap();
|
||||||
let le = light.l(w.p, w.n, w.uv, w.wo, &w.lambda);
|
let le = light.l(w.p, w.n, w.uv, w.wo, &w.lambda);
|
||||||
if le.is_black() {
|
if le.is_black() {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let l_contrib = if w.depth == 0 || w.specular_bounce {
|
let l_contrib = if w.depth == 0 || w.specular_bounce {
|
||||||
w.beta * le / w.r_u.average()
|
w.beta * le / w.r_u.average()
|
||||||
} else {
|
} else {
|
||||||
// MIS: combine BSDF and light sampling weights
|
let ctx = w.prev_intr_ctx;
|
||||||
// TODO: full MIS with light sampler PDF
|
let light_choice_pdf = light_sampler.pmf_with_context(&ctx, light);
|
||||||
w.beta * le / w.r_u.average()
|
// wi from previous interaction to this light hit
|
||||||
|
let wi = (w.p - Point3f::from(ctx.pi)).normalize();
|
||||||
|
let light_pdf = light_choice_pdf * light.pdf_li(&ctx, wi, true);
|
||||||
|
let r_l = w.r_l * light_pdf;
|
||||||
|
w.beta * le / (w.r_u + r_l).average()
|
||||||
};
|
};
|
||||||
|
|
||||||
if !l_contrib.is_black() {
|
if !l_contrib.is_black() {
|
||||||
let pi = w.pixel_index as usize;
|
let pi = w.pixel_index as usize;
|
||||||
let mut l = self.pixel_sample_state.l.get(pi);
|
let mut l = pixel_sample_state.l.get(pi);
|
||||||
l += l_contrib;
|
l += l_contrib;
|
||||||
self.pixel_sample_state.l.set(pi, l);
|
pixel_sample_state.l.set(pi, l);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate_materials_and_bsdfs(&mut self, depth: u32) {
|
fn evaluate_materials_and_bsdfs(&mut self, depth: u32) {
|
||||||
|
|
@ -255,23 +381,25 @@ impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
|
||||||
let n = queue.size();
|
let n = queue.size();
|
||||||
let next = ((depth + 1) % 2) as usize;
|
let next = ((depth + 1) % 2) as usize;
|
||||||
|
|
||||||
for i in 0..n as usize {
|
let pixel_sample_state = &self.pixel_sample_state;
|
||||||
|
let light_sampler = &self.light_sampler;
|
||||||
|
let shadow_ray_queue = &self.shadow_ray_queue;
|
||||||
|
let next_ray_queue = &self.ray_queues[next];
|
||||||
|
let regularize = self.regularize;
|
||||||
|
|
||||||
|
(0..n as usize).into_par_iter().for_each(|i| {
|
||||||
let w = unsafe { queue.storage.get(i) };
|
let w = unsafe { queue.storage.get(i) };
|
||||||
let pi = w.pixel_index as usize;
|
let pi = w.pixel_index as usize;
|
||||||
|
let rs = pixel_sample_state.samples.get(pi);
|
||||||
let lambda = self.pixel_sample_state.lambda.get(pi);
|
|
||||||
let beta = self.pixel_sample_state.beta.get(pi);
|
|
||||||
let any_non_specular = self.pixel_sample_state.any_non_specular_bounces.get(pi) != 0;
|
|
||||||
let eta_scale = self.pixel_sample_state.eta_scale.get(pi);
|
|
||||||
|
|
||||||
let Some(material) = w.material.get() else {
|
let Some(material) = w.material.get() else {
|
||||||
continue;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let tex_eval = UniversalTextureEvaluator;
|
let tex_eval = UniversalTextureEvaluator;
|
||||||
let ctx = MaterialEvalContext {
|
let ctx = MaterialEvalContext {
|
||||||
texture: TextureEvalContext {
|
texture: TextureEvalContext {
|
||||||
p: w.p,
|
p: w.p.into(),
|
||||||
dpdx: Vector3f::zero(),
|
dpdx: Vector3f::zero(),
|
||||||
dpdy: Vector3f::zero(),
|
dpdy: Vector3f::zero(),
|
||||||
n: w.n,
|
n: w.n,
|
||||||
|
|
@ -284,176 +412,183 @@ impl WavefrontRenderer for WavefrontPathIntegrator<CpuAggregate> {
|
||||||
},
|
},
|
||||||
wo: w.wo,
|
wo: w.wo,
|
||||||
ns: w.ns,
|
ns: w.ns,
|
||||||
dpdus: w.dpdu,
|
dpdus: w.dpdus,
|
||||||
};
|
};
|
||||||
|
let lambda = w.lambda;
|
||||||
let mut bsdf = material.get_bsdf(&tex_eval, &ctx, &lambda);
|
let mut bsdf = material.get_bsdf(&tex_eval, &ctx, &lambda);
|
||||||
|
|
||||||
if bsdf.flags().is_empty() {
|
if bsdf.flags().is_empty() {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
if regularize && w.any_non_specular_bounces {
|
||||||
if self.regularize && any_non_specular {
|
|
||||||
bsdf.regularize();
|
bsdf.regularize();
|
||||||
}
|
}
|
||||||
|
|
||||||
if depth >= self.max_depth {
|
// BSDF sample for indirect ray
|
||||||
continue;
|
let wo = w.wo;
|
||||||
}
|
let ns = w.ns;
|
||||||
|
if let Some(bs) = bsdf.sample_f(wo, rs.indirect.uc, rs.indirect.u, FArgs::default()) {
|
||||||
// Sample a light, compute contribution,
|
let wi = bs.wi;
|
||||||
// push shadow ray with deferred visibility
|
let mut beta = w.beta * bs.f * wi.abs_dot(ns.into()) / bs.pdf;
|
||||||
if bsdf.flags().is_non_specular() {
|
let r_u = w.r_u;
|
||||||
let light_ctx = LightSampleContext {
|
let r_l = if bs.pdf_is_proportional {
|
||||||
pi: Point3fi::new_from_point(w.p),
|
r_u / bsdf.pdf(wo, wi, FArgs::default())
|
||||||
n: w.n,
|
} else {
|
||||||
ns: w.ns,
|
r_u / bs.pdf
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(sampled_light) = self
|
let mut eta_scale = w.eta_scale;
|
||||||
.light_sampler
|
if bs.is_transmissive() {
|
||||||
.sample_with_context(&light_ctx, self.sampler.get1d())
|
eta_scale *= square(bs.eta);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rr_beta = (beta * eta_scale / r_u.average()).max_component_value();
|
||||||
|
if rr_beta < 1.0 && w.depth > 1 {
|
||||||
|
let q = (1.0 - rr_beta).max(0.0_f32);
|
||||||
|
if rs.indirect.rr < q {
|
||||||
|
beta = SampledSpectrum::new(0.0);
|
||||||
|
} else {
|
||||||
|
beta /= 1.0 - q;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !beta.is_black() {
|
||||||
|
let ray = Ray::spawn(&w.p, &w.n, w.time, wi);
|
||||||
|
let any_non_specular = !bs.is_specular() || w.any_non_specular_bounces;
|
||||||
|
let ctx = LightSampleContext {
|
||||||
|
pi: w.p,
|
||||||
|
n: w.n,
|
||||||
|
ns,
|
||||||
|
};
|
||||||
|
|
||||||
|
next_ray_queue.push(RayWorkItem {
|
||||||
|
ray,
|
||||||
|
depth: w.depth + 1,
|
||||||
|
pixel_index: w.pixel_index,
|
||||||
|
lambda,
|
||||||
|
beta,
|
||||||
|
r_u,
|
||||||
|
r_l,
|
||||||
|
prev_intr_ctx: ctx,
|
||||||
|
eta_scale,
|
||||||
|
specular_bounce: bs.is_specular() as u8,
|
||||||
|
any_non_specular_bounces: any_non_specular as u8,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct lighting
|
||||||
|
let flags = bsdf.flags();
|
||||||
|
if flags.is_non_specular() {
|
||||||
|
let light_ctx = LightSampleContext {
|
||||||
|
pi: w.p,
|
||||||
|
n: w.n,
|
||||||
|
ns,
|
||||||
|
};
|
||||||
|
if let Some(sampled_light) =
|
||||||
|
light_sampler.sample_with_context(&light_ctx, rs.direct.uc)
|
||||||
{
|
{
|
||||||
if let Some(ls) = sampled_light.light.sample_li(
|
if let Some(ls) =
|
||||||
&light_ctx,
|
sampled_light
|
||||||
self.sampler.get2d(),
|
.light
|
||||||
&lambda,
|
.sample_li(&light_ctx, rs.direct.u, &lambda, true)
|
||||||
true,
|
{
|
||||||
) {
|
|
||||||
if !ls.l.is_black() && ls.pdf > 0.0 {
|
if !ls.l.is_black() && ls.pdf > 0.0 {
|
||||||
let wi = ls.wi;
|
let wi = ls.wi;
|
||||||
if let Some(f_val) = bsdf.f(w.wo, wi, TransportMode::Radiance) {
|
if let Some(f) = bsdf.f(wo, wi, TransportMode::Radiance) {
|
||||||
let f_cos = f_val * wi.abs_dot(w.ns.into());
|
if !f.is_black() {
|
||||||
if !f_cos.is_black() {
|
let beta = w.beta * f * wi.abs_dot(ns.into());
|
||||||
let p_l = sampled_light.p * ls.pdf;
|
let light_pdf = ls.pdf * sampled_light.p;
|
||||||
let l_d = if sampled_light.light.light_type().is_delta_light() {
|
let bsdf_pdf =
|
||||||
beta * ls.l * f_cos / p_l
|
if sampled_light.light.light_type().is_delta_light() {
|
||||||
} else {
|
0.0
|
||||||
let p_b = bsdf.pdf(w.wo, wi, FArgs::default());
|
} else {
|
||||||
let w_l = power_heuristic(1, p_l, 1, p_b);
|
bsdf.pdf(wo, wi, FArgs::default())
|
||||||
beta * w_l * ls.l * f_cos / p_l
|
};
|
||||||
};
|
let r_u = w.r_u * bsdf_pdf;
|
||||||
|
let r_l = w.r_u * light_pdf;
|
||||||
|
let ld = beta * ls.l;
|
||||||
|
|
||||||
if !l_d.is_black() {
|
let ray_o = Ray::spawn_to_interaction(
|
||||||
let ray_o = Ray::offset_origin(
|
&w.p,
|
||||||
&Point3fi::new_from_point(w.p),
|
&w.n,
|
||||||
&w.n,
|
w.time,
|
||||||
&wi,
|
&ls.p_light.pi(),
|
||||||
);
|
&ls.p_light.n(),
|
||||||
let t_max = (1.0 - 1e-4)
|
);
|
||||||
* (Point3f::from(ls.p_light.p()) - ray_o).norm()
|
let t_max = (1.0 - 1e-4)
|
||||||
/ wi.norm();
|
* (Point3f::from(ls.p_light.p()) - ray_o.o).norm()
|
||||||
|
/ wi.norm();
|
||||||
|
|
||||||
self.shadow_ray_queue.push(ShadowRayWorkItem {
|
shadow_ray_queue.push(ShadowRayWorkItem {
|
||||||
ray_o,
|
ray_o: ray_o.o,
|
||||||
ray_d: wi,
|
ray_d: ray_o.d,
|
||||||
ray_time: w.time,
|
ray_time: w.time,
|
||||||
t_max,
|
t_max: 1.0 - 1e-4,
|
||||||
lambda,
|
lambda,
|
||||||
l_d,
|
l_d: ld,
|
||||||
pixel_index: w.pixel_index,
|
r_u,
|
||||||
});
|
r_l,
|
||||||
}
|
pixel_index: w.pixel_index,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// Sample BSDF for next bounce
|
|
||||||
let wo = w.wo;
|
|
||||||
let Some(bs) = bsdf.sample_f(
|
|
||||||
wo,
|
|
||||||
self.sampler.get1d(),
|
|
||||||
self.sampler.get2d(),
|
|
||||||
FArgs::default(),
|
|
||||||
) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let f_cos = bs.f * bs.wi.abs_dot(w.ns.into());
|
|
||||||
if f_cos.is_black() || bs.pdf == 0.0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let new_beta = beta * f_cos / bs.pdf;
|
|
||||||
|
|
||||||
let new_depth = depth + 1;
|
|
||||||
|
|
||||||
// Russian roulette
|
|
||||||
if new_depth > 3 {
|
|
||||||
let rr_beta = new_beta.max_component_value();
|
|
||||||
if rr_beta < 0.25 {
|
|
||||||
let q = (1.0 - rr_beta).max(0.0_f32);
|
|
||||||
if self.sampler.get1d() < q {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let ray_o = Ray::offset_origin(&Point3fi::new_from_point(w.p), &w.n, &bs.wi);
|
|
||||||
|
|
||||||
// Update PixelSampleState
|
|
||||||
self.pixel_sample_state.beta.set(pi, new_beta);
|
|
||||||
self.pixel_sample_state.depth.set(pi, new_depth);
|
|
||||||
self.pixel_sample_state
|
|
||||||
.specular_bounce
|
|
||||||
.set(pi, bs.is_specular() as u8);
|
|
||||||
self.pixel_sample_state
|
|
||||||
.any_non_specular_bounces
|
|
||||||
.set(pi, (any_non_specular || !bs.is_specular()) as u8);
|
|
||||||
self.pixel_sample_state.eta_scale.set(
|
|
||||||
pi,
|
|
||||||
if bs.is_transmissive() {
|
|
||||||
eta_scale * square(bs.eta)
|
|
||||||
} else {
|
|
||||||
eta_scale
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.pixel_sample_state.prev_intr_ctx.set(
|
|
||||||
pi,
|
|
||||||
LightSampleContext {
|
|
||||||
pi: Point3fi::new_from_point(w.p),
|
|
||||||
n: w.n,
|
|
||||||
ns: w.ns,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Push next bounce ray
|
|
||||||
self.ray_queues[next].push(RayWorkItem {
|
|
||||||
ray_o,
|
|
||||||
ray_d: bs.wi,
|
|
||||||
ray_time: w.time,
|
|
||||||
ray_medium: Ptr::null(),
|
|
||||||
pixel_index: w.pixel_index,
|
|
||||||
has_differentials: true,
|
|
||||||
differential: RayDifferential::default(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_film(&self, y0: i32, y1: i32, pixel_bounds: &Bounds2i) {
|
fn update_film(&self, _y0: i32, _y1: i32, pixel_bounds: &Bounds2i) {
|
||||||
// The pixel_sample_state indices map to rays generated in
|
(0..self.max_queue_size as usize)
|
||||||
// generate_camera_rays. We need to walk the same pixel order
|
.into_par_iter()
|
||||||
// and read back the accumulated L values.
|
.for_each(|pixel_index| {
|
||||||
let mut pi = 0usize;
|
let p_pixel = self.pixel_sample_state.p_pixel.get(pixel_index);
|
||||||
for y in y0..y1 {
|
if !pixel_bounds.contains_exclusive(p_pixel) {
|
||||||
for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() {
|
return;
|
||||||
let l = self.pixel_sample_state.l.get(pi);
|
}
|
||||||
let lambda = self.pixel_sample_state.lambda.get(pi);
|
|
||||||
let filter_weight = self.pixel_sample_state.filter_weight.get(pi);
|
|
||||||
let p_film = self.pixel_sample_state.p_film.get(pi);
|
|
||||||
|
|
||||||
// Add sample to film
|
let l = self.pixel_sample_state.l.get(pixel_index);
|
||||||
self.film.add_sample(
|
let camera_weight = self.pixel_sample_state.camera_ray_weight.get(pixel_index);
|
||||||
Point2i::new(p_film.x() as i32, p_film.y() as i32),
|
let weighted_l = l * camera_weight;
|
||||||
l,
|
let lambda = self.pixel_sample_state.lambda.get(pixel_index);
|
||||||
&lambda,
|
let filter_weight = self.pixel_sample_state.filter_weight.get(pixel_index);
|
||||||
Some(&crate::core::film::VisibleSurface::default()),
|
|
||||||
filter_weight,
|
|
||||||
);
|
|
||||||
|
|
||||||
pi += 1;
|
self.film
|
||||||
}
|
.add_sample(p_pixel, weighted_l, &lambda, None, filter_weight);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_ray_samples(&mut self, depth: u32, sample_index: u32) {
|
||||||
|
let current = (depth % 2) as usize;
|
||||||
|
let ray_queue = &self.ray_queues[current];
|
||||||
|
let n = ray_queue.size();
|
||||||
|
let dimension = 6 + 7 * depth;
|
||||||
|
let pixel_sample_state = &self.pixel_sample_state;
|
||||||
|
let sampler_proto = &self.sampler;
|
||||||
|
|
||||||
|
(0..n as usize).into_par_iter().for_each(|i| {
|
||||||
|
let w = unsafe { ray_queue.storage.get(i) };
|
||||||
|
let pi = w.pixel_index as usize;
|
||||||
|
let p_pixel = pixel_sample_state.p_pixel.get(pi);
|
||||||
|
|
||||||
|
let mut sampler = sampler_proto.clone();
|
||||||
|
sampler.start_pixel_sample(p_pixel, sample_index as i32, Some(dimension));
|
||||||
|
|
||||||
|
self.pixel_sample_state.samples.set(
|
||||||
|
pi,
|
||||||
|
RaySamples {
|
||||||
|
direct: DirectSamples {
|
||||||
|
uc: sampler.get1d(),
|
||||||
|
u: sampler.get2d(),
|
||||||
|
},
|
||||||
|
indirect: IndirectSamples {
|
||||||
|
uc: sampler.get1d(),
|
||||||
|
u: sampler.get2d(),
|
||||||
|
rr: sampler.get1d(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@ pub mod aggregate;
|
||||||
pub mod integrator;
|
pub mod integrator;
|
||||||
|
|
||||||
pub use aggregate::CpuAggregate;
|
pub use aggregate::CpuAggregate;
|
||||||
|
pub use integrator::{CreateWavefront, CpuWavefrontRenderer};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue