Compare commits
6 commits
c659ea0f44
...
dad7300a14
| Author | SHA1 | Date | |
|---|---|---|---|
| dad7300a14 | |||
| 645556da22 | |||
| 1e0840dcda | |||
| 2fc366878f | |||
| f21cb7cf08 | |||
| c8d083df62 |
68 changed files with 3780 additions and 1910 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -12,3 +12,5 @@ tests/
|
|||
*.spv
|
||||
*.json
|
||||
*.txt
|
||||
scenes/
|
||||
compile.sh
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ cuda = ["dep:cudarc", "dep:cust", "dep:cust_raw", "dep:cuda-runtime-sys"]
|
|||
vulkan = ["ash", "gpu-allocator"]
|
||||
ash = ["dep:ash"]
|
||||
gpu-allocator = ["dep:gpu-allocator"]
|
||||
jemalloc = ["jemallocator"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
|
|
@ -52,12 +53,16 @@ cust = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default
|
|||
cust_raw = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", default-features = false, optional = true }
|
||||
cuda-runtime-sys = { version = "0.3.0-alpha.1", optional = true}
|
||||
cudarc = { version = "0.18.2", features = ["cuda-13000"], optional = true }
|
||||
jemallocator = { version = "0.5", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true }
|
||||
cuda_builder = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", optional = true }
|
||||
cc = "1.2.53"
|
||||
|
||||
[dev-dependencies]
|
||||
sysinfo = "0.30"
|
||||
|
||||
[workspace]
|
||||
members = ["shared"]
|
||||
exclude = ["crates/ptex-filter", "kernels"]
|
||||
|
|
@ -67,3 +72,6 @@ excessive_precision = "allow"
|
|||
approx_constant = "allow"
|
||||
upper_case_acronyms = "allow"
|
||||
wrong_self_convention = "allow"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
|
|
|||
189
shared/src/core/aggregates.rs
Normal file
189
shared/src/core/aggregates.rs
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
use crate::core::geometry::{Bounds3f, Ray, Vector3f};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::primitive::{Primitive, PrimitiveTrait};
|
||||
use crate::core::shape::ShapeIntersection;
|
||||
use crate::utils::Ptr;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LinearBVHNode {
|
||||
pub bounds: Bounds3f,
|
||||
pub primitives_offset: usize,
|
||||
pub n_primitives: u16,
|
||||
pub axis: u8,
|
||||
pub pad: u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DeviceBVHAggregate {
|
||||
pub max_prims_in_node: u32,
|
||||
pub primitives: Ptr<Primitive>,
|
||||
pub primitive_count: u32,
|
||||
pub nodes: Ptr<LinearBVHNode>,
|
||||
pub node_count: u32,
|
||||
}
|
||||
|
||||
impl DeviceBVHAggregate {
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
max_prims_in_node: 0,
|
||||
primitives: Ptr::null(),
|
||||
primitive_count: 0,
|
||||
nodes: Ptr::null(),
|
||||
node_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn node(&self, i: usize) -> &LinearBVHNode {
|
||||
unsafe { self.nodes.at(i) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn primitive(&self, i: usize) -> &Primitive {
|
||||
unsafe { self.primitives.at(i) }
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimitiveTrait for DeviceBVHAggregate {
|
||||
fn bounds(&self) -> Bounds3f {
|
||||
if self.nodes.is_null() || self.node_count == 0 {
|
||||
Bounds3f::default()
|
||||
} else {
|
||||
self.node(0).bounds
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||
if self.nodes.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut hit_t = t_max.unwrap_or(Float::INFINITY);
|
||||
let mut best_si: Option<ShapeIntersection> = None;
|
||||
|
||||
let inv_dir = Vector3f::new(1.0 / r.d.x(), 1.0 / r.d.y(), 1.0 / r.d.z());
|
||||
let dir_is_neg = [
|
||||
if inv_dir.x() < 0.0 { 1 } else { 0 },
|
||||
if inv_dir.y() < 0.0 { 1 } else { 0 },
|
||||
if inv_dir.z() < 0.0 { 1 } else { 0 },
|
||||
];
|
||||
|
||||
let mut stack = [0usize; 64];
|
||||
let mut stack_ptr = 0;
|
||||
let mut node_idx = 0usize;
|
||||
|
||||
loop {
|
||||
let node = self.node(node_idx);
|
||||
|
||||
if node
|
||||
.bounds
|
||||
.intersect_p(r.o, hit_t, inv_dir, &dir_is_neg)
|
||||
.is_none()
|
||||
{
|
||||
if stack_ptr == 0 {
|
||||
break;
|
||||
}
|
||||
stack_ptr -= 1;
|
||||
node_idx = stack[stack_ptr];
|
||||
continue;
|
||||
}
|
||||
|
||||
if node.n_primitives > 0 {
|
||||
// Leaf: test all primitives
|
||||
for i in 0..node.n_primitives {
|
||||
let prim_idx = node.primitives_offset + i as usize;
|
||||
let prim = self.primitive(prim_idx);
|
||||
if let Some(si) = prim.intersect(r, Some(hit_t)) {
|
||||
hit_t = si.t_hit();
|
||||
best_si = Some(si);
|
||||
}
|
||||
}
|
||||
|
||||
if stack_ptr == 0 {
|
||||
break;
|
||||
}
|
||||
stack_ptr -= 1;
|
||||
node_idx = stack[stack_ptr];
|
||||
} else {
|
||||
// Interior: push far, visit near
|
||||
if dir_is_neg[node.axis as usize] == 1 {
|
||||
stack[stack_ptr] = node_idx + 1;
|
||||
stack_ptr += 1;
|
||||
node_idx = node.primitives_offset; // second child
|
||||
} else {
|
||||
stack[stack_ptr] = node.primitives_offset;
|
||||
stack_ptr += 1;
|
||||
node_idx += 1; // first child
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
best_si
|
||||
}
|
||||
|
||||
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
|
||||
if self.nodes.is_null() || self.node_count == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let t_max = t_max.unwrap_or(Float::INFINITY);
|
||||
|
||||
let inv_dir = Vector3f::new(1.0 / r.d.x(), 1.0 / r.d.y(), 1.0 / r.d.z());
|
||||
let dir_is_neg = [
|
||||
if inv_dir.x() < 0.0 { 1 } else { 0 },
|
||||
if inv_dir.y() < 0.0 { 1 } else { 0 },
|
||||
if inv_dir.z() < 0.0 { 1 } else { 0 },
|
||||
];
|
||||
|
||||
let mut stack = [0usize; 64];
|
||||
let mut stack_ptr = 0;
|
||||
let mut node_idx = 0usize;
|
||||
|
||||
loop {
|
||||
let node = self.node(node_idx);
|
||||
|
||||
if node
|
||||
.bounds
|
||||
.intersect_p(r.o, t_max, inv_dir, &dir_is_neg)
|
||||
.is_none()
|
||||
{
|
||||
if stack_ptr == 0 {
|
||||
break;
|
||||
}
|
||||
stack_ptr -= 1;
|
||||
node_idx = stack[stack_ptr];
|
||||
continue;
|
||||
}
|
||||
|
||||
if node.n_primitives > 0 {
|
||||
for i in 0..node.n_primitives {
|
||||
let prim_idx = node.primitives_offset + i as usize;
|
||||
let prim = self.primitive(prim_idx);
|
||||
if prim.intersect_p(r, Some(t_max)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if stack_ptr == 0 {
|
||||
break;
|
||||
}
|
||||
stack_ptr -= 1;
|
||||
node_idx = stack[stack_ptr];
|
||||
} else {
|
||||
if dir_is_neg[node.axis as usize] == 1 {
|
||||
stack[stack_ptr] = node_idx + 1;
|
||||
stack_ptr += 1;
|
||||
node_idx = node.primitives_offset;
|
||||
} else {
|
||||
stack[stack_ptr] = node.primitives_offset;
|
||||
stack_ptr += 1;
|
||||
node_idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -13,13 +13,13 @@ pub struct FilterSample {
|
|||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct FilterSampler {
|
||||
pub struct DeviceFilterSampler {
|
||||
pub domain: Bounds2f,
|
||||
pub distrib: DevicePiecewiseConstant2D,
|
||||
pub f: DeviceArray2D<Float>,
|
||||
}
|
||||
|
||||
impl FilterSampler {
|
||||
impl DeviceFilterSampler {
|
||||
pub fn sample(&self, u: Point2f) -> FilterSample {
|
||||
let (p, pdf, pi) = self.distrib.sample(u);
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ pub trait FilterTrait {
|
|||
fn radius(&self) -> Vector2f;
|
||||
fn evaluate(&self, p: Point2f) -> Float;
|
||||
fn integral(&self) -> Float;
|
||||
fn sample(&self, u: Point2f) -> FilterSample;
|
||||
fn sample(&self, u: Point2f) -> DeviceFilterSample;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
|
|||
|
|
@ -193,3 +193,4 @@ pub enum Material {
|
|||
ThinDielectric(ThinDielectricMaterial),
|
||||
Mix(MixMaterial),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod aggregates;
|
||||
pub mod bsdf;
|
||||
pub mod bssrdf;
|
||||
pub mod bxdf;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::core::geometry::{Bounds3f, Ray};
|
||||
use crate::core::aggregates::DeviceBVHAggregate;
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||
use crate::core::light::Light;
|
||||
use crate::core::material::Material;
|
||||
|
|
@ -6,9 +7,11 @@ use crate::core::medium::{Medium, MediumInterface};
|
|||
use crate::core::pbrt::Float;
|
||||
use crate::core::shape::{Shape, ShapeIntersection, ShapeTrait};
|
||||
use crate::core::texture::{GPUFloatTexture, TextureEvalContext};
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::hash::hash_float;
|
||||
use crate::utils::transform::{AnimatedTransform, Transform};
|
||||
use crate::utils::Ptr;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::sync::Arc;
|
||||
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
|
|
@ -96,22 +99,24 @@ pub struct SimplePrimitive {
|
|||
|
||||
impl PrimitiveTrait for SimplePrimitive {
|
||||
fn bounds(&self) -> Bounds3f {
|
||||
todo!()
|
||||
self.shape.bounds()
|
||||
}
|
||||
|
||||
fn intersect(&self, _r: &Ray, _t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||
todo!()
|
||||
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||
let mut si = self.shape.intersect(r, t_max)?;
|
||||
si.set_intersection_properties(self.material, Ptr::null(), MediumInterface::default(), r.medium);
|
||||
Some(si)
|
||||
}
|
||||
|
||||
fn intersect_p(&self, _r: &Ray, _t_max: Option<Float>) -> bool {
|
||||
todo!()
|
||||
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
|
||||
self.shape.intersect_p(r, t_max)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TransformedPrimitive {
|
||||
pub primitive: Ptr<Primitive>,
|
||||
pub render_from_primitive: Transform,
|
||||
pub render_from_primitive: Ptr<Transform>,
|
||||
}
|
||||
|
||||
impl PrimitiveTrait for TransformedPrimitive {
|
||||
|
|
@ -137,16 +142,17 @@ impl PrimitiveTrait for TransformedPrimitive {
|
|||
Some(si)
|
||||
}
|
||||
|
||||
fn intersect_p(&self, _r: &Ray, _t_max: Option<Float>) -> bool {
|
||||
todo!()
|
||||
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
|
||||
let (ray, t_max) = self.render_from_primitive.apply_inverse_ray(r, t_max);
|
||||
self.primitive.intersect_p(&ray, Some(t_max))
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct AnimatedPrimitive {
|
||||
primitive: Ptr<Primitive>,
|
||||
render_from_primitive: AnimatedTransform,
|
||||
pub primitive: Ptr<Primitive>,
|
||||
pub render_from_primitive: Ptr<AnimatedTransform>,
|
||||
}
|
||||
|
||||
impl PrimitiveTrait for AnimatedPrimitive {
|
||||
|
|
@ -182,41 +188,7 @@ pub struct LinearBVHNode {
|
|||
bounds: Bounds3f,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BVHAggregatePrimitive {
|
||||
max_prims_in_node: u32,
|
||||
primitives: Ptr<[Primitive]>,
|
||||
nodes: Ptr<LinearBVHNode>,
|
||||
}
|
||||
|
||||
impl PrimitiveTrait for BVHAggregatePrimitive {
|
||||
fn bounds(&self) -> Bounds3f {
|
||||
if !self.nodes.is_null() {
|
||||
self.nodes.bounds
|
||||
} else {
|
||||
Bounds3f::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect(&self, _r: &Ray, _t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||
if !self.nodes.is_null() {
|
||||
return None;
|
||||
}
|
||||
todo!()
|
||||
// self.intersect(r, t_max)
|
||||
}
|
||||
|
||||
fn intersect_p(&self, _r: &Ray, _t_max: Option<Float>) -> bool {
|
||||
if !self.nodes.is_null() {
|
||||
return false;
|
||||
}
|
||||
todo!()
|
||||
// self.intersect_p(r, t_max)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KdTreeAggregate;
|
||||
|
||||
impl PrimitiveTrait for KdTreeAggregate {
|
||||
|
|
@ -233,13 +205,14 @@ impl PrimitiveTrait for KdTreeAggregate {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
#[enum_dispatch(PrimitiveTrait)]
|
||||
pub enum Primitive {
|
||||
Simple(SimplePrimitive),
|
||||
Geometric(GeometricPrimitive),
|
||||
Transformed(TransformedPrimitive),
|
||||
Animated(AnimatedPrimitive),
|
||||
BVH(BVHAggregatePrimitive),
|
||||
BVH(DeviceBVHAggregate),
|
||||
KdTree(KdTreeAggregate),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ pub enum Spectrum {
|
|||
|
||||
impl Spectrum {
|
||||
pub fn std_illuminant_d65() -> Self {
|
||||
todo!()
|
||||
unimplemented!("Use crate::spectra::default_illuminant() on host")
|
||||
}
|
||||
|
||||
pub fn to_xyz(&self, std: &StandardSpectra) -> XYZ {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Float;
|
||||
use crate::core::filter::{FilterSample, FilterTrait};
|
||||
use crate::core::filter::{DeviceFilterSample, FilterTrait};
|
||||
use crate::core::geometry::{Point2f, Vector2f};
|
||||
use crate::utils::math::lerp;
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ impl FilterTrait for BoxFilter {
|
|||
(2.0 * self.radius.x()) * (2.0 * self.radius.y())
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
fn sample(&self, u: Point2f) -> DeviceFilterSample {
|
||||
let p = Point2f::new(
|
||||
lerp(u[0], -self.radius.x(), self.radius.x()),
|
||||
lerp(u[1], -self.radius.y(), self.radius.y()),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Float;
|
||||
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
|
||||
use crate::core::filter::{DeviceFilterSample, FilterSampler, FilterTrait};
|
||||
use crate::core::geometry::{Point2f, Vector2f};
|
||||
use crate::utils::math::{gaussian, gaussian_integral};
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ impl FilterTrait for GaussianFilter {
|
|||
- 2.0 * self.radius.y() * self.exp_y)
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
fn sample(&self, u: Point2f) -> DeviceFilterSample {
|
||||
self.sampler.sample(u)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Float;
|
||||
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
|
||||
use crate::core::filter::{DeviceFilterSample, FilterSampler, FilterTrait};
|
||||
use crate::core::geometry::{Point2f, Vector2f};
|
||||
use crate::utils::math::{lerp, windowed_sinc};
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ impl FilterTrait for LanczosSincFilter {
|
|||
self.integral
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
fn sample(&self, u: Point2f) -> DeviceFilterSample {
|
||||
self.sampler.sample(u)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Float;
|
||||
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
|
||||
use crate::core::filter::{DeviceFilterSample, FilterSampler, FilterTrait};
|
||||
use crate::core::geometry::{Point2f, Vector2f};
|
||||
use num_traits::Float as NumFloat;
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ pub struct MitchellFilter {
|
|||
pub radius: Vector2f,
|
||||
pub b: Float,
|
||||
pub c: Float,
|
||||
pub sampler: FilterSampler,
|
||||
pub sampler: DeviceFilterSampler,
|
||||
}
|
||||
|
||||
impl MitchellFilter {
|
||||
|
|
@ -50,7 +50,7 @@ impl FilterTrait for MitchellFilter {
|
|||
self.radius.x() * self.radius.y() / 4.0
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
fn sample(&self, u: Point2f) -> DeviceFilterSample {
|
||||
self.sampler.sample(u)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Float;
|
||||
use crate::core::filter::{FilterSample, FilterTrait};
|
||||
use crate::core::filter::{DeviceFilterSample, FilterTrait};
|
||||
use crate::core::geometry::{Point2f, Vector2f};
|
||||
use crate::utils::math::sample_tent;
|
||||
use num_traits::Float as NumFloat;
|
||||
|
|
@ -29,11 +29,11 @@ impl FilterTrait for TriangleFilter {
|
|||
self.radius.x().powi(2) * self.radius.y().powi(2)
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
fn sample(&self, u: Point2f) -> DeviceFilterSample {
|
||||
let p = Point2f::new(
|
||||
sample_tent(u[0], self.radius.x()),
|
||||
sample_tent(u[1], self.radius.y()),
|
||||
);
|
||||
FilterSample { p, weight: 1.0 }
|
||||
DeviceFilterSample { p, weight: 1.0 }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#![allow(unused_imports, dead_code)]
|
||||
#![feature(associated_type_defaults)]
|
||||
#![no_std]
|
||||
extern crate alloc;
|
||||
|
||||
pub mod bxdfs;
|
||||
pub mod cameras;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::core::pbrt::Float;
|
||||
use crate::utils::math::{next_float_down, next_float_up};
|
||||
use num_traits::Zero;
|
||||
use core::ops::{Add, Div, Mul, Neg, Sub};
|
||||
use num_traits::Zero;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
|
|
|
|||
|
|
@ -18,6 +18,15 @@ pub use options::PBRTOptions;
|
|||
pub use ptr::Ptr;
|
||||
pub use transform::{AnimatedTransform, Transform, TransformGeneric};
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
parse_macro_input, Attribute, Data, DeriveInput, Expr, Fields, GenericArgument, Ident, Lit,
|
||||
PathArguments, Type, Variant,
|
||||
};
|
||||
|
||||
|
||||
use crate::Float;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
|
|
@ -128,3 +137,515 @@ pub fn gpu_array_from_fn<T, const N: usize>(mut f: impl FnMut(usize) -> T) -> [T
|
|||
arr.assume_init()
|
||||
}
|
||||
}
|
||||
|
||||
/// # Enum variant attributes
|
||||
///
|
||||
/// | Attribute | Effect |
|
||||
/// |-----------|--------|
|
||||
/// | *(none)* | Inner type has `DeviceRepr`; auto-call `upload_value` |
|
||||
/// | `#[device(clone)]` | Same type on both sides, just clone |
|
||||
/// | `#[device(custom = "method")]` | You provide `fn method(inner: &T, arena) -> DeviceT` |
|
||||
/// | `#[device(variant_type = "T")]` | Override the device-side variant's inner type |
|
||||
///
|
||||
/// # Container attribute
|
||||
///
|
||||
/// `#[device(name = "DeviceFoo")]` — override the generated type name (default: `Device{Name}`).
|
||||
#[proc_macro_derive(Device, attributes(device))]
|
||||
pub fn derive_device(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
match derive_impl(input) {
|
||||
Ok(tokens) => tokens.into(),
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream2> {
|
||||
match &input.data {
|
||||
Data::Struct(_) => derive_struct(input),
|
||||
Data::Enum(_) => derive_enum(input),
|
||||
Data::Union(_) => Err(syn::Error::new_spanned(
|
||||
&input.ident,
|
||||
"Device derive does not support unions",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// Struct derivation
|
||||
|
||||
fn derive_struct(input: DeriveInput) -> syn::Result<TokenStream2> {
|
||||
let host_name = &input.ident;
|
||||
let vis = &input.vis;
|
||||
let device_name = get_device_name(&input.attrs, host_name)?;
|
||||
|
||||
let fields = match &input.data {
|
||||
Data::Struct(s) => match &s.fields {
|
||||
Fields::Named(named) => &named.named,
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
host_name,
|
||||
"Device derive only supports structs with named fields",
|
||||
))
|
||||
}
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut device_fields = Vec::new();
|
||||
let mut upload_stmts = Vec::new();
|
||||
let mut device_field_inits = Vec::new();
|
||||
let mut spread_expr: Option<Expr> = None;
|
||||
|
||||
for field in fields {
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
let attrs = parse_field_attrs(&field.attrs)?;
|
||||
|
||||
if attrs.skip {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(ref expr_str) = attrs.spread {
|
||||
spread_expr = Some(syn::parse_str(expr_str).map_err(|e| {
|
||||
syn::Error::new_spanned(field, format!("invalid device(spread): {}", e))
|
||||
})?);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(expr_str) = &attrs.expr {
|
||||
let expr: Expr = syn::parse_str(expr_str).map_err(|e| {
|
||||
syn::Error::new_spanned(field, format!("invalid device(expr): {}", e))
|
||||
})?;
|
||||
let ty = &field.ty;
|
||||
device_fields.push(quote! { pub #field_name: #ty });
|
||||
upload_stmts.push(quote! { let #field_name = #expr; });
|
||||
device_field_inits.push(quote! { #field_name });
|
||||
continue;
|
||||
}
|
||||
|
||||
match classify_type(&field.ty) {
|
||||
FieldClass::VecCopy(inner_ty) => {
|
||||
let len_name = format_ident!("{}_len", field_name);
|
||||
device_fields.push(quote! { pub #field_name: Ptr<#inner_ty> });
|
||||
device_fields.push(quote! { pub #len_name: usize });
|
||||
upload_stmts.push(quote! {
|
||||
let (#field_name, #len_name) = arena.alloc_slice(&self.#field_name);
|
||||
});
|
||||
device_field_inits.push(quote! { #field_name });
|
||||
device_field_inits.push(quote! { #len_name });
|
||||
}
|
||||
FieldClass::VecUploadable(inner_ty) => {
|
||||
let len_name = format_ident!("{}_len", field_name);
|
||||
device_fields.push(quote! {
|
||||
pub #field_name: Ptr<<#inner_ty as DeviceRepr>::Target>
|
||||
});
|
||||
device_fields.push(quote! { pub #len_name: usize });
|
||||
upload_stmts.push(quote! {
|
||||
let __up: Vec<<#inner_ty as DeviceRepr>::Target> = self.#field_name
|
||||
.iter()
|
||||
.map(|item| DeviceRepr::upload_value(item, arena))
|
||||
.collect();
|
||||
let (#field_name, #len_name) = arena.alloc_slice(&__up);
|
||||
});
|
||||
device_field_inits.push(quote! { #field_name });
|
||||
device_field_inits.push(quote! { #len_name });
|
||||
}
|
||||
FieldClass::Option(inner_ty) => {
|
||||
device_fields.push(quote! {
|
||||
pub #field_name: Ptr<<#inner_ty as DeviceRepr>::Target>
|
||||
});
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = match &self.#field_name {
|
||||
Some(val) => DeviceRepr::upload(val, arena),
|
||||
None => Ptr::null(),
|
||||
};
|
||||
});
|
||||
device_field_inits.push(quote! { #field_name });
|
||||
}
|
||||
FieldClass::Arc(inner_ty) => {
|
||||
if attrs.flatten {
|
||||
device_fields.push(quote! {
|
||||
pub #field_name: <#inner_ty as DeviceRepr>::Target
|
||||
});
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = DeviceRepr::upload_value(&*self.#field_name, arena);
|
||||
});
|
||||
} else {
|
||||
device_fields.push(quote! {
|
||||
pub #field_name: Ptr<<#inner_ty as DeviceRepr>::Target>
|
||||
});
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = DeviceRepr::upload(&*self.#field_name, arena);
|
||||
});
|
||||
}
|
||||
device_field_inits.push(quote! { #field_name });
|
||||
}
|
||||
FieldClass::Plain => {
|
||||
let ty = &field.ty;
|
||||
if attrs.copy_upload {
|
||||
device_fields.push(quote! { pub #field_name: #ty });
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = self.#field_name.clone();
|
||||
});
|
||||
} else if attrs.flatten {
|
||||
device_fields.push(quote! {
|
||||
pub #field_name: <#ty as DeviceRepr>::Target
|
||||
});
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = DeviceRepr::upload_value(&self.#field_name, arena);
|
||||
});
|
||||
} else if attrs.upload {
|
||||
device_fields.push(quote! {
|
||||
pub #field_name: Ptr<<#ty as DeviceRepr>::Target>
|
||||
});
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = DeviceRepr::upload(&self.#field_name, arena);
|
||||
});
|
||||
} else {
|
||||
device_fields.push(quote! { pub #field_name: #ty });
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = self.#field_name;
|
||||
});
|
||||
}
|
||||
device_field_inits.push(quote! { #field_name });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let constructor = if let Some(spread) = spread_expr {
|
||||
quote! {
|
||||
#device_name {
|
||||
#(#device_field_inits,)*
|
||||
..#spread
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#device_name {
|
||||
#(#device_field_inits,)*
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#vis struct #device_name {
|
||||
#(#device_fields,)*
|
||||
}
|
||||
|
||||
unsafe impl Send for #device_name {}
|
||||
unsafe impl Sync for #device_name {}
|
||||
|
||||
impl DeviceRepr for #host_name {
|
||||
type Target = #device_name;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> Self::Target {
|
||||
#(#upload_stmts)*
|
||||
#constructor
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Enum derivation
|
||||
fn derive_enum(input: DeriveInput) -> syn::Result<TokenStream2> {
|
||||
let host_name = &input.ident;
|
||||
let vis = &input.vis;
|
||||
let device_name = get_device_name(&input.attrs, host_name)?;
|
||||
|
||||
let variants = match &input.data {
|
||||
Data::Enum(e) => &e.variants,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut device_variants = Vec::new();
|
||||
let mut match_arms = Vec::new();
|
||||
|
||||
for variant in variants {
|
||||
let var_name = &variant.ident;
|
||||
let var_attrs = parse_variant_attrs(&variant.attrs)?;
|
||||
let inner_ty = get_variant_inner_type(variant)?;
|
||||
|
||||
// Determine the device-side inner type for this variant
|
||||
let device_inner: Type = if let Some(ref ty_str) = var_attrs.variant_type {
|
||||
syn::parse_str(ty_str).map_err(|e| {
|
||||
syn::Error::new_spanned(variant, format!("invalid variant_type: {}", e))
|
||||
})?
|
||||
} else if var_attrs.clone_variant {
|
||||
// clone: same type on both sides
|
||||
inner_ty.clone()
|
||||
} else {
|
||||
// auto-upload: use DeviceRepr::Target
|
||||
syn::parse_str(&format!("<{} as DeviceRepr>::Target", quote!(#inner_ty))).map_err(
|
||||
|e| {
|
||||
syn::Error::new_spanned(variant, format!("cannot construct Target type: {}", e))
|
||||
},
|
||||
)?
|
||||
};
|
||||
|
||||
device_variants.push(quote! { #var_name(#device_inner) });
|
||||
|
||||
if var_attrs.clone_variant {
|
||||
match_arms.push(quote! {
|
||||
#host_name::#var_name(inner) => #device_name::#var_name(inner.clone())
|
||||
});
|
||||
} else if let Some(ref method) = var_attrs.custom {
|
||||
let method_ident = format_ident!("{}", method);
|
||||
match_arms.push(quote! {
|
||||
#host_name::#var_name(inner) => {
|
||||
#device_name::#var_name(Self::#method_ident(inner, arena))
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Default: inner implements DeviceRepr
|
||||
match_arms.push(quote! {
|
||||
#host_name::#var_name(inner) => {
|
||||
#device_name::#var_name(DeviceRepr::upload_value(inner, arena))
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(quote! {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#vis enum #device_name {
|
||||
#(#device_variants,)*
|
||||
}
|
||||
|
||||
unsafe impl Send for #device_name {}
|
||||
unsafe impl Sync for #device_name {}
|
||||
|
||||
impl DeviceRepr for #host_name {
|
||||
type Target = #device_name;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> Self::Target {
|
||||
match self {
|
||||
#(#match_arms,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_variant_inner_type(variant: &Variant) -> syn::Result<Type> {
|
||||
match &variant.fields {
|
||||
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
|
||||
Ok(fields.unnamed.first().unwrap().ty.clone())
|
||||
}
|
||||
Fields::Unit => Err(syn::Error::new_spanned(
|
||||
variant,
|
||||
"Device derive: enum variants must have exactly one field, e.g. Variant(Type)",
|
||||
)),
|
||||
_ => Err(syn::Error::new_spanned(
|
||||
variant,
|
||||
"Device derive: only single-field tuple variants supported, e.g. Variant(Type)",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute parsing for variants
|
||||
struct VariantAttrs {
|
||||
clone_variant: bool,
|
||||
custom: Option<String>,
|
||||
variant_type: Option<String>,
|
||||
}
|
||||
|
||||
fn parse_variant_attrs(attrs: &[Attribute]) -> syn::Result<VariantAttrs> {
|
||||
let mut result = VariantAttrs {
|
||||
clone_variant: false,
|
||||
custom: None,
|
||||
variant_type: None,
|
||||
};
|
||||
|
||||
for attr in attrs {
|
||||
if !attr.path().is_ident("device") {
|
||||
continue;
|
||||
}
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("clone") {
|
||||
result.clone_variant = true;
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("custom") {
|
||||
let value = meta.value()?;
|
||||
let lit: Lit = value.parse()?;
|
||||
if let Lit::Str(s) = lit {
|
||||
result.custom = Some(s.value());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error("expected string literal"))
|
||||
}
|
||||
} else if meta.path.is_ident("variant_type") {
|
||||
let value = meta.value()?;
|
||||
let lit: Lit = value.parse()?;
|
||||
if let Lit::Str(s) = lit {
|
||||
result.variant_type = Some(s.value());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error("expected string literal"))
|
||||
}
|
||||
} else {
|
||||
Err(meta.error("unknown device variant attribute"))
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// Attribute parsing for fields
|
||||
struct FieldAttrs {
|
||||
skip: bool,
|
||||
expr: Option<String>,
|
||||
copy_upload: bool,
|
||||
flatten: bool,
|
||||
upload: bool,
|
||||
spread: Option<String>,
|
||||
}
|
||||
|
||||
fn parse_field_attrs(attrs: &[Attribute]) -> syn::Result<FieldAttrs> {
|
||||
let mut result = FieldAttrs {
|
||||
skip: false,
|
||||
expr: None,
|
||||
copy_upload: false,
|
||||
flatten: false,
|
||||
upload: false,
|
||||
spread: None,
|
||||
};
|
||||
|
||||
for attr in attrs {
|
||||
if !attr.path().is_ident("device") {
|
||||
continue;
|
||||
}
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("skip") {
|
||||
result.skip = true;
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("expr") {
|
||||
let value = meta.value()?;
|
||||
let lit: Lit = value.parse()?;
|
||||
if let Lit::Str(s) = lit {
|
||||
result.expr = Some(s.value());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error("expected string literal"))
|
||||
}
|
||||
} else if meta.path.is_ident("copy_upload") {
|
||||
result.copy_upload = true;
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("flatten") {
|
||||
result.flatten = true;
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("upload") {
|
||||
result.upload = true;
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("spread") {
|
||||
let value = meta.value()?;
|
||||
let lit: Lit = value.parse()?;
|
||||
if let Lit::Str(s) = lit {
|
||||
result.spread = Some(s.value());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error("expected string literal"))
|
||||
}
|
||||
} else {
|
||||
Err(meta.error("unknown device attribute"))
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// Container-level name attribute
|
||||
fn get_device_name(attrs: &[Attribute], host_name: &Ident) -> syn::Result<Ident> {
|
||||
for attr in attrs {
|
||||
if !attr.path().is_ident("device") {
|
||||
continue;
|
||||
}
|
||||
let mut name = None;
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("name") {
|
||||
let value = meta.value()?;
|
||||
let lit: Lit = value.parse()?;
|
||||
if let Lit::Str(s) = lit {
|
||||
name = Some(format_ident!("{}", s.value()));
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error("expected string literal"))
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
if let Some(n) = name {
|
||||
return Ok(n);
|
||||
}
|
||||
}
|
||||
Ok(format_ident!("Device{}", host_name))
|
||||
}
|
||||
|
||||
// Type classification
|
||||
enum FieldClass {
|
||||
VecCopy(Type),
|
||||
VecUploadable(Type),
|
||||
Option(Type),
|
||||
Arc(Type),
|
||||
Plain,
|
||||
}
|
||||
|
||||
fn classify_type(ty: &Type) -> FieldClass {
|
||||
if let Some(inner) = extract_generic_arg(ty, "Vec") {
|
||||
if is_copy_primitive(&inner) {
|
||||
FieldClass::VecCopy(inner)
|
||||
} else {
|
||||
FieldClass::VecUploadable(inner)
|
||||
}
|
||||
} else if let Some(inner) = extract_generic_arg(ty, "Option") {
|
||||
FieldClass::Option(inner)
|
||||
} else if let Some(inner) = extract_generic_arg(ty, "Arc") {
|
||||
FieldClass::Arc(inner)
|
||||
} else {
|
||||
FieldClass::Plain
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_generic_arg(ty: &Type, wrapper: &str) -> Option<Type> {
|
||||
if let Type::Path(type_path) = ty {
|
||||
let seg = type_path.path.segments.last()?;
|
||||
if seg.ident != wrapper {
|
||||
return None;
|
||||
}
|
||||
if let PathArguments::AngleBracketed(args) = &seg.arguments {
|
||||
if let Some(GenericArgument::Type(inner)) = args.args.first() {
|
||||
return Some(inner.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_copy_primitive(ty: &Type) -> bool {
|
||||
if let Type::Path(type_path) = ty {
|
||||
if let Some(seg) = type_path.path.segments.last() {
|
||||
let name = seg.ident.to_string();
|
||||
return matches!(
|
||||
name.as_str(),
|
||||
"f32"
|
||||
| "f64"
|
||||
| "u8"
|
||||
| "u16"
|
||||
| "u32"
|
||||
| "u64"
|
||||
| "i8"
|
||||
| "i16"
|
||||
| "i32"
|
||||
| "i64"
|
||||
| "usize"
|
||||
| "isize"
|
||||
| "bool"
|
||||
| "Float"
|
||||
);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -695,8 +695,8 @@ impl VarianceEstimator {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct PLSample {
|
||||
pub p: Point2f,
|
||||
pub pdf: Float,
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
use rayon::prelude::*;
|
||||
use shared::Float;
|
||||
use shared::core::aggregates::{{DeviceBVHAggregate, LinearBVHNode};
|
||||
use shared::core::geometry::{Bounds3f, Point3f, Ray, Vector3f};
|
||||
use shared::core::primitive::PrimitiveTrait;
|
||||
use shared::core::primitive::{Primitive, PrimitiveTrait};
|
||||
use shared::core::shape::ShapeIntersection;
|
||||
use shared::utils::math::encode_morton_3;
|
||||
use shared::utils::{find_interval, partition_slice};
|
||||
use crate::Arena;
|
||||
use shared::Float;
|
||||
use std::cmp::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -25,14 +26,6 @@ struct BVHSplitBucket {
|
|||
pub bounds: Bounds3f,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LinearBVHNode {
|
||||
pub bounds: Bounds3f,
|
||||
pub primitives_offset: usize,
|
||||
pub n_primitives: u16,
|
||||
pub axis: u8,
|
||||
pub pad: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
struct MortonPrimitive {
|
||||
|
|
@ -47,7 +40,7 @@ struct LBVHTreelet {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BVHPrimitiveInfo {
|
||||
primitive_number: usize, // Index into the original primitives vector
|
||||
primitive_number: usize,
|
||||
bounds: Bounds3f,
|
||||
centroid: Point3f,
|
||||
}
|
||||
|
|
@ -65,9 +58,9 @@ impl BVHPrimitiveInfo {
|
|||
#[derive(Clone, Debug)]
|
||||
pub enum BVHBuildNode {
|
||||
Leaf {
|
||||
first_prim_offset: usize,
|
||||
n_primitives: usize,
|
||||
bounds: Bounds3f,
|
||||
primitive_indices: Vec<usize>,
|
||||
},
|
||||
Interior {
|
||||
split_axis: u8,
|
||||
|
|
@ -79,19 +72,19 @@ pub enum BVHBuildNode {
|
|||
impl Default for BVHBuildNode {
|
||||
fn default() -> Self {
|
||||
BVHBuildNode::Leaf {
|
||||
first_prim_offset: 0,
|
||||
n_primitives: 0,
|
||||
bounds: Bounds3f::default(),
|
||||
primitive_indices: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BVHBuildNode {
|
||||
pub fn new_leaf(first_prim_offset: usize, n_primitives: usize, bounds: Bounds3f) -> Self {
|
||||
pub fn new_leaf(n_primitives: usize, bounds: Bounds3f, indices: Vec<usize>) -> Self {
|
||||
Self::Leaf {
|
||||
bounds,
|
||||
first_prim_offset,
|
||||
n_primitives,
|
||||
primitive_indices: indices,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,17 +113,17 @@ impl BVHBuildNode {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct SharedPrimitiveBuffer<'a> {
|
||||
ptr: *mut Arc<dyn PrimitiveTrait>,
|
||||
pub struct SharedPrimitiveBuffer<'a, P> {
|
||||
ptr: *mut P,
|
||||
pub offset: &'a AtomicUsize,
|
||||
_marker: std::marker::PhantomData<&'a mut [Arc<dyn PrimitiveTrait>]>,
|
||||
_marker: std::marker::PhantomData<&'a mut [P]>,
|
||||
}
|
||||
|
||||
unsafe impl<'a> Sync for SharedPrimitiveBuffer<'a> {}
|
||||
unsafe impl<'a> Send for SharedPrimitiveBuffer<'a> {}
|
||||
unsafe impl<'a, P> Sync for SharedPrimitiveBuffer<'a, P> {}
|
||||
unsafe impl<'a, P> Send for SharedPrimitiveBuffer<'a, P> {}
|
||||
|
||||
impl<'a> SharedPrimitiveBuffer<'a> {
|
||||
pub fn new(slice: &'a mut [Arc<dyn PrimitiveTrait>], offset: &'a AtomicUsize) -> Self {
|
||||
impl<'a, P> SharedPrimitiveBuffer<'a, P> {
|
||||
pub fn new(slice: &'a mut [P], offset: &'a AtomicUsize) -> Self {
|
||||
Self {
|
||||
ptr: slice.as_mut_ptr(),
|
||||
offset,
|
||||
|
|
@ -138,11 +131,10 @@ impl<'a> SharedPrimitiveBuffer<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn append(
|
||||
&self,
|
||||
primitives: &[Arc<dyn PrimitiveTrait>],
|
||||
indices: &[BVHPrimitiveInfo],
|
||||
) -> usize {
|
||||
pub fn append(&self, primitives: &[P], indices: &[BVHPrimitiveInfo]) -> usize
|
||||
where
|
||||
P: Clone,
|
||||
{
|
||||
let count = indices.len();
|
||||
let start_index = self.offset.fetch_add(count, AtomicOrdering::Relaxed);
|
||||
|
||||
|
|
@ -156,16 +148,16 @@ impl<'a> SharedPrimitiveBuffer<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct BVHAggregate {
|
||||
max_prims_in_node: usize,
|
||||
primitives: Vec<Arc<dyn PrimitiveTrait>>,
|
||||
split_method: SplitMethod,
|
||||
nodes: Vec<LinearBVHNode>,
|
||||
pub struct BVHAggregate<P: PrimitiveTrait + Clone + Send + Sync> {
|
||||
pub max_prims_in_node: usize,
|
||||
pub primitives: Vec<P>,
|
||||
pub split_method: SplitMethod,
|
||||
pub nodes: Vec<LinearBVHNode>,
|
||||
}
|
||||
|
||||
impl BVHAggregate {
|
||||
impl<P: PrimitiveTrait + Clone + Send + Sync> BVHAggregate<P> {
|
||||
pub fn new(
|
||||
mut primitives: Vec<Arc<dyn PrimitiveTrait>>,
|
||||
mut primitives: Vec<P>,
|
||||
max_prims_in_node: usize,
|
||||
split_method: SplitMethod,
|
||||
) -> Self {
|
||||
|
|
@ -186,50 +178,44 @@ impl BVHAggregate {
|
|||
.map(|(i, p)| BVHPrimitiveInfo::new(i, p.bounds()))
|
||||
.collect();
|
||||
|
||||
let ordered_prims: Vec<Arc<dyn PrimitiveTrait>>;
|
||||
let total_nodes_count: usize;
|
||||
let root: Box<BVHBuildNode>;
|
||||
|
||||
match split_method {
|
||||
SplitMethod::Hlbvh => {
|
||||
let nodes_counter = AtomicUsize::new(0);
|
||||
let ordered_prims_offset = AtomicUsize::new(0);
|
||||
let mut local_ordered = vec![primitives[0].clone(); primitives.len()];
|
||||
let shared_buffer =
|
||||
SharedPrimitiveBuffer::new(&mut local_ordered, &ordered_prims_offset);
|
||||
|
||||
root =
|
||||
Self::build_hlbvh(&primitive_info, &nodes_counter, &shared_buffer, &primitives);
|
||||
|
||||
ordered_prims = local_ordered;
|
||||
root = Self::build_hlbvh(
|
||||
&primitive_info,
|
||||
&nodes_counter,
|
||||
&primitives,
|
||||
max_prims_in_node,
|
||||
);
|
||||
total_nodes_count = nodes_counter.load(AtomicOrdering::Relaxed);
|
||||
}
|
||||
_ => {
|
||||
let nodes_counter = AtomicUsize::new(0);
|
||||
let ordered_prims_offset = AtomicUsize::new(0);
|
||||
let mut local_ordered = vec![primitives[0].clone(); primitives.len()];
|
||||
let shared_buffer =
|
||||
SharedPrimitiveBuffer::new(&mut local_ordered, &ordered_prims_offset);
|
||||
|
||||
root = Self::build_recursive(
|
||||
&mut primitive_info,
|
||||
&nodes_counter,
|
||||
&shared_buffer,
|
||||
&primitives,
|
||||
max_prims_in_node,
|
||||
split_method,
|
||||
);
|
||||
|
||||
ordered_prims = local_ordered;
|
||||
total_nodes_count = nodes_counter.load(AtomicOrdering::Relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
primitives = ordered_prims;
|
||||
// Walk the tree and collect primitive indices in the exact order
|
||||
// the linear layout will visit them (left-to-right, depth-first)
|
||||
let mut leaf_order = Vec::with_capacity(primitives.len());
|
||||
Self::leaf_order(&root, &mut leaf_order);
|
||||
Self::reorder(&mut primitives, &leaf_order);
|
||||
drop(leaf_order);
|
||||
|
||||
let mut nodes = vec![LinearBVHNode::default(); total_nodes_count];
|
||||
let mut offset = 0;
|
||||
Self::flatten_bvh(&root, &mut nodes, &mut offset);
|
||||
let mut prim_offset = 0;
|
||||
Self::flatten(&root, &mut nodes, &mut offset, &mut prim_offset);
|
||||
|
||||
Self {
|
||||
max_prims_in_node,
|
||||
|
|
@ -239,21 +225,65 @@ impl BVHAggregate {
|
|||
}
|
||||
}
|
||||
|
||||
fn flatten_bvh(node: &BVHBuildNode, nodes: &mut [LinearBVHNode], offset: &mut usize) -> usize {
|
||||
fn reorder(primitives: &mut [P], order: &[usize]) {
|
||||
let n = primitives.len();
|
||||
assert_eq!(n, order.len());
|
||||
|
||||
let mut done = vec![false; n];
|
||||
for i in 0..n {
|
||||
if done[i] || order[i] == i {
|
||||
done[i] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut prev = i;
|
||||
let mut curr = order[i];
|
||||
while curr != i {
|
||||
primitives.swap(prev, curr);
|
||||
done[prev] = true;
|
||||
prev = curr;
|
||||
curr = order[prev];
|
||||
}
|
||||
done[prev] = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn leaf_order(node: &BVHBuildNode, out: &mut Vec<usize>) {
|
||||
match node {
|
||||
BVHBuildNode::Leaf {
|
||||
primitive_indices, ..
|
||||
} => {
|
||||
out.extend_from_slice(primitive_indices);
|
||||
}
|
||||
BVHBuildNode::Interior { children, .. } => {
|
||||
Self::leaf_order(&children[0], out);
|
||||
Self::leaf_order(&children[1], out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten(
|
||||
node: &BVHBuildNode,
|
||||
nodes: &mut [LinearBVHNode],
|
||||
offset: &mut usize,
|
||||
prim_offset: &mut usize,
|
||||
) -> usize {
|
||||
let local_offset = *offset;
|
||||
*offset += 1;
|
||||
|
||||
match node {
|
||||
BVHBuildNode::Leaf {
|
||||
first_prim_offset,
|
||||
n_primitives,
|
||||
bounds,
|
||||
..
|
||||
} => {
|
||||
let n = *n_primitives;
|
||||
let linear_node = &mut nodes[local_offset];
|
||||
linear_node.bounds = *bounds;
|
||||
linear_node.n_primitives = *n_primitives as u16;
|
||||
linear_node.primitives_offset = *first_prim_offset;
|
||||
linear_node.n_primitives = n as u16;
|
||||
linear_node.primitives_offset = *prim_offset;
|
||||
linear_node.axis = 0; // Irrelevant for leaves
|
||||
*prim_offset += n;
|
||||
}
|
||||
|
||||
BVHBuildNode::Interior {
|
||||
|
|
@ -265,8 +295,8 @@ impl BVHAggregate {
|
|||
nodes[local_offset].axis = *split_axis;
|
||||
nodes[local_offset].n_primitives = 0;
|
||||
|
||||
Self::flatten_bvh(&children[0], nodes, offset);
|
||||
let second_child_offset = Self::flatten_bvh(&children[1], nodes, offset);
|
||||
Self::flatten(&children[0], nodes, offset, prim_offset);
|
||||
let second_child_offset = Self::flatten(&children[1], nodes, offset, prim_offset);
|
||||
nodes[local_offset].primitives_offset = second_child_offset;
|
||||
}
|
||||
}
|
||||
|
|
@ -277,8 +307,8 @@ impl BVHAggregate {
|
|||
pub fn build_hlbvh(
|
||||
bvh_primitives: &[BVHPrimitiveInfo],
|
||||
total_nodes: &AtomicUsize,
|
||||
ordered_prims: &SharedPrimitiveBuffer,
|
||||
original_primitives: &[Arc<dyn PrimitiveTrait>],
|
||||
original_primitives: &[P],
|
||||
max_prims_in_node: usize,
|
||||
) -> Box<BVHBuildNode> {
|
||||
let bounds = bvh_primitives
|
||||
.iter()
|
||||
|
|
@ -310,7 +340,11 @@ impl BVHAggregate {
|
|||
let m1 = w[0].morton_code & TREELET_MASK;
|
||||
let m2 = w[1].morton_code & TREELET_MASK;
|
||||
// If mask changes, the split is at index i + 1
|
||||
if m1 != m2 { Some(i + 1) } else { None }
|
||||
if m1 != m2 {
|
||||
Some(i + 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
|
@ -333,15 +367,12 @@ impl BVHAggregate {
|
|||
let mut nodes_created = 0;
|
||||
|
||||
const FIRST_BIT_INDEX: i32 = 29 - 12;
|
||||
|
||||
let root = Self::emit_lbvh(
|
||||
bvh_primitives,
|
||||
&morton_prims[tr.start_index..tr.start_index + tr.n_primitives],
|
||||
&mut nodes_created,
|
||||
ordered_prims,
|
||||
original_primitives,
|
||||
FIRST_BIT_INDEX,
|
||||
4,
|
||||
max_prims_in_node,
|
||||
);
|
||||
|
||||
total_nodes.fetch_add(nodes_created, AtomicOrdering::Relaxed);
|
||||
|
|
@ -362,8 +393,6 @@ impl BVHAggregate {
|
|||
bvh_primitives: &[BVHPrimitiveInfo],
|
||||
morton_prims: &[MortonPrimitive],
|
||||
total_nodes: &mut usize,
|
||||
ordered_prims: &SharedPrimitiveBuffer,
|
||||
original_primitives: &[Arc<dyn PrimitiveTrait>],
|
||||
bit_index: i32,
|
||||
max_prims_in_node: usize,
|
||||
) -> Box<BVHBuildNode> {
|
||||
|
|
@ -371,23 +400,18 @@ impl BVHAggregate {
|
|||
if bit_index == -1 || n_primitives <= max_prims_in_node {
|
||||
*total_nodes += 1;
|
||||
|
||||
// Calculate bounds while collecting indices
|
||||
let mut bounds = Bounds3f::default();
|
||||
let mut indices = Vec::with_capacity(n_primitives);
|
||||
|
||||
for mp in morton_prims {
|
||||
let info = &bvh_primitives[mp.primitive_index];
|
||||
bounds = bounds.union(info.bounds);
|
||||
indices.push(info.clone());
|
||||
indices.push(mp.primitive_index);
|
||||
}
|
||||
|
||||
let first_prim_offset = ordered_prims.append(original_primitives, &indices);
|
||||
return Box::new(BVHBuildNode::new_leaf(
|
||||
first_prim_offset,
|
||||
n_primitives,
|
||||
bounds,
|
||||
));
|
||||
return Box::new(BVHBuildNode::new_leaf(n_primitives, bounds, indices));
|
||||
}
|
||||
|
||||
let mask = 1 << bit_index;
|
||||
let first_code = morton_prims[0].morton_code;
|
||||
let last_match_index = find_interval(n_primitives.try_into().unwrap(), |index| {
|
||||
|
|
@ -401,23 +425,18 @@ impl BVHAggregate {
|
|||
bvh_primitives,
|
||||
morton_prims,
|
||||
total_nodes,
|
||||
ordered_prims,
|
||||
original_primitives,
|
||||
bit_index - 1,
|
||||
max_prims_in_node,
|
||||
);
|
||||
}
|
||||
|
||||
let (left_morton, right_morton) = morton_prims.split_at(split_offset);
|
||||
|
||||
*total_nodes += 1;
|
||||
|
||||
let child0 = Self::emit_lbvh(
|
||||
bvh_primitives,
|
||||
left_morton,
|
||||
total_nodes,
|
||||
ordered_prims,
|
||||
original_primitives,
|
||||
bit_index - 1,
|
||||
max_prims_in_node,
|
||||
);
|
||||
|
|
@ -426,8 +445,6 @@ impl BVHAggregate {
|
|||
bvh_primitives,
|
||||
right_morton,
|
||||
total_nodes,
|
||||
ordered_prims,
|
||||
original_primitives,
|
||||
bit_index - 1,
|
||||
max_prims_in_node,
|
||||
);
|
||||
|
|
@ -574,8 +591,7 @@ impl BVHAggregate {
|
|||
fn build_recursive(
|
||||
bvh_primitives: &mut [BVHPrimitiveInfo],
|
||||
total_nodes: &AtomicUsize,
|
||||
ordered_prims: &SharedPrimitiveBuffer,
|
||||
original_primitives: &[Arc<dyn PrimitiveTrait>],
|
||||
original_primitives: &[P],
|
||||
max_prims_in_node: usize,
|
||||
split_method: SplitMethod,
|
||||
) -> Box<BVHBuildNode> {
|
||||
|
|
@ -586,28 +602,17 @@ impl BVHAggregate {
|
|||
|
||||
let n_primitives = bvh_primitives.len();
|
||||
if bounds.surface_area() == 0.0 || n_primitives == 1 || n_primitives <= max_prims_in_node {
|
||||
let first_prim_offset = ordered_prims.append(original_primitives, bvh_primitives);
|
||||
|
||||
return Box::new(BVHBuildNode::new_leaf(
|
||||
first_prim_offset,
|
||||
n_primitives,
|
||||
bounds,
|
||||
));
|
||||
let indices: Vec<usize> = bvh_primitives.iter().map(|p| p.primitive_number).collect();
|
||||
return Box::new(BVHBuildNode::new_leaf(n_primitives, bounds, indices));
|
||||
}
|
||||
|
||||
let centroid_bounds = bvh_primitives.iter().fold(Bounds3f::default(), |b, p| {
|
||||
b.union_point(p.bounds.centroid())
|
||||
});
|
||||
|
||||
let dim = centroid_bounds.max_dimension();
|
||||
if centroid_bounds.p_max[dim] == centroid_bounds.p_min[dim] {
|
||||
let first_prim_offset = ordered_prims.append(original_primitives, bvh_primitives);
|
||||
|
||||
return Box::new(BVHBuildNode::new_leaf(
|
||||
first_prim_offset,
|
||||
n_primitives,
|
||||
bounds,
|
||||
));
|
||||
let indices: Vec<usize> = bvh_primitives.iter().map(|p| p.primitive_number).collect();
|
||||
return Box::new(BVHBuildNode::new_leaf(n_primitives, bounds, indices));
|
||||
}
|
||||
|
||||
let mut mid: usize;
|
||||
|
|
@ -695,69 +700,43 @@ impl BVHAggregate {
|
|||
}
|
||||
b <= min_cost_split_bucket
|
||||
});
|
||||
if mid == 0 || mid == n_primitives {
|
||||
mid = n_primitives / 2;
|
||||
bvh_primitives.select_nth_unstable_by(mid, |a, b| {
|
||||
a.centroid[dim]
|
||||
.partial_cmp(&b.centroid[dim])
|
||||
.unwrap_or(Ordering::Equal)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let first_prim_offset =
|
||||
ordered_prims.append(original_primitives, bvh_primitives);
|
||||
return Box::new(BVHBuildNode::new_leaf(
|
||||
first_prim_offset,
|
||||
n_primitives,
|
||||
bounds,
|
||||
));
|
||||
let indices: Vec<usize> =
|
||||
bvh_primitives.iter().map(|p| p.primitive_number).collect();
|
||||
return Box::new(BVHBuildNode::new_leaf(n_primitives, bounds, indices));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (left_prims, right_prims) = bvh_primitives.split_at_mut(mid);
|
||||
if n_primitives > 128 * 1024 {
|
||||
let (child0, child1) = rayon::join(
|
||||
|| {
|
||||
let build_leaf = |prims: &mut [BVHPrimitiveInfo]| -> Box<BVHBuildNode> {
|
||||
Self::build_recursive(
|
||||
left_prims,
|
||||
prims,
|
||||
total_nodes,
|
||||
ordered_prims,
|
||||
original_primitives,
|
||||
max_prims_in_node,
|
||||
split_method,
|
||||
)
|
||||
},
|
||||
|| {
|
||||
Self::build_recursive(
|
||||
right_prims,
|
||||
total_nodes,
|
||||
ordered_prims,
|
||||
original_primitives,
|
||||
max_prims_in_node,
|
||||
split_method,
|
||||
)
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
let axis = dim as u8;
|
||||
Box::new(BVHBuildNode::new_interior(axis, child0, child1))
|
||||
let (child0, child1) = if n_primitives > 128 * 1024 {
|
||||
rayon::join(|| build_leaf(left_prims), || build_leaf(right_prims))
|
||||
} else {
|
||||
let child0 = Self::build_recursive(
|
||||
left_prims,
|
||||
total_nodes,
|
||||
ordered_prims,
|
||||
original_primitives,
|
||||
max_prims_in_node,
|
||||
split_method,
|
||||
);
|
||||
|
||||
let child1 = Self::build_recursive(
|
||||
right_prims,
|
||||
total_nodes,
|
||||
ordered_prims,
|
||||
original_primitives,
|
||||
max_prims_in_node,
|
||||
split_method,
|
||||
);
|
||||
(build_leaf(left_prims), build_leaf(right_prims))
|
||||
};
|
||||
|
||||
let axis = dim as u8;
|
||||
Box::new(BVHBuildNode::new_interior(axis, child0, child1))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||
if self.nodes.is_empty() {
|
||||
|
|
@ -908,3 +887,29 @@ impl BVHAggregate {
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl BVHAggregate<Primitive> {
|
||||
pub fn to_device(&self, arena: &mut Arena) -> DeviceBVHAggregate {
|
||||
let (prims_ptr, _) = arena.alloc_slice(&self.primitives);
|
||||
|
||||
let shared_nodes: Vec<shared::core::aggregates::LinearBVHNode> = self.nodes
|
||||
.iter()
|
||||
.map(|n| shared::core::aggregates::LinearBVHNode {
|
||||
bounds: n.bounds,
|
||||
primitives_offset: n.primitives_offset,
|
||||
n_primitives: n.n_primitives,
|
||||
axis: n.axis,
|
||||
pad: 0,
|
||||
})
|
||||
.collect();
|
||||
let (nodes_ptr, _) = arena.alloc_slice(&shared_nodes);
|
||||
|
||||
DeviceBVHAggregate {
|
||||
max_prims_in_node: self.max_prims_in_node as u32,
|
||||
primitives: prims_ptr,
|
||||
primitive_count: self.primitives.len() as u32,
|
||||
nodes: nodes_ptr,
|
||||
node_count: self.nodes.len() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ use crate::core::image::{Image, ImageIO};
|
|||
use crate::globals::get_options;
|
||||
use crate::utils::read_float_file;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::Ptr;
|
||||
use anyhow::{anyhow, Result};
|
||||
use shared::cameras::*;
|
||||
use shared::core::camera::{Camera, CameraBase, CameraTrait, CameraTransform};
|
||||
use shared::core::color::SRGB;
|
||||
|
|
@ -14,6 +13,7 @@ use shared::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f, Vector3f};
|
|||
use shared::core::image::PixelFormat;
|
||||
use shared::core::medium::Medium;
|
||||
use shared::utils::math::square;
|
||||
use shared::Ptr;
|
||||
use shared::{Float, PI};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -25,14 +25,14 @@ pub struct CameraBaseParameters {
|
|||
pub shutter_open: Float,
|
||||
pub shutter_close: Float,
|
||||
pub film: Arc<Film>,
|
||||
pub medium: Arc<Medium>,
|
||||
pub medium: Option<Arc<Medium>>,
|
||||
}
|
||||
|
||||
impl CameraBaseParameters {
|
||||
pub fn new(
|
||||
camera_transform: &CameraTransform,
|
||||
film: Arc<Film>,
|
||||
medium: Arc<Medium>,
|
||||
medium: Option<Arc<Medium>>,
|
||||
params: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
) -> Result<Self> {
|
||||
|
|
@ -63,7 +63,10 @@ pub trait CameraBaseFactory {
|
|||
shutter_open: p.shutter_open,
|
||||
shutter_close: p.shutter_close,
|
||||
film: Ptr::from(p.film.clone().as_ref()),
|
||||
medium: Ptr::from(p.medium.clone().as_ref()),
|
||||
medium: match p.medium {
|
||||
Some(ref m) => Ptr::from(m.as_ref()),
|
||||
None => Ptr::null(),
|
||||
},
|
||||
min_pos_differential_x: Vector3f::default(),
|
||||
min_pos_differential_y: Vector3f::default(),
|
||||
min_dir_differential_x: Vector3f::default(),
|
||||
|
|
@ -96,7 +99,7 @@ pub trait CameraFactory {
|
|||
name: &str,
|
||||
params: &ParameterDictionary,
|
||||
camera_transform: &CameraTransform,
|
||||
medium: Medium,
|
||||
medium: Option<Arc<Medium>>,
|
||||
film: Arc<Film>,
|
||||
loc: &FileLoc,
|
||||
arena: &Arena,
|
||||
|
|
@ -110,7 +113,7 @@ impl CameraFactory for Camera {
|
|||
name: &str,
|
||||
params: &ParameterDictionary,
|
||||
camera_transform: &CameraTransform,
|
||||
medium: Medium,
|
||||
medium: Option<Arc<Medium>>,
|
||||
film: Arc<Film>,
|
||||
loc: &FileLoc,
|
||||
arena: &Arena,
|
||||
|
|
@ -122,7 +125,7 @@ impl CameraFactory for Camera {
|
|||
"perspective" => {
|
||||
let full_res = film.full_resolution();
|
||||
let camera_params =
|
||||
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc)?;
|
||||
CameraBaseParameters::new(camera_transform, film, medium, params, loc)?;
|
||||
let base = CameraBase::create(camera_params);
|
||||
let lens_radius = params.get_one_float("lensradius", 0.)?;
|
||||
let focal_distance = params.get_one_float("focaldistance", 1e6)?;
|
||||
|
|
@ -169,7 +172,7 @@ impl CameraFactory for Camera {
|
|||
"orthographic" => {
|
||||
let full_res = film.full_resolution();
|
||||
let camera_params =
|
||||
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc)?;
|
||||
CameraBaseParameters::new(camera_transform, film, medium, params, loc)?;
|
||||
let base = CameraBase::create(camera_params);
|
||||
let lens_radius = params.get_one_float("lensradius", 0.)?;
|
||||
let focal_distance = params.get_one_float("focaldistance", 1e6)?;
|
||||
|
|
@ -211,7 +214,7 @@ impl CameraFactory for Camera {
|
|||
}
|
||||
"realistic" => {
|
||||
let camera_params =
|
||||
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc)?;
|
||||
CameraBaseParameters::new(camera_transform, film, medium, params, loc)?;
|
||||
let base = CameraBase::create(camera_params);
|
||||
let aperture_diameter = params.get_one_float("aperturediameter", 1.)?;
|
||||
let focal_distance = params.get_one_float("focaldistance", 10.)?;
|
||||
|
|
@ -391,7 +394,7 @@ impl CameraFactory for Camera {
|
|||
"spherical" => {
|
||||
let full_res = film.full_resolution();
|
||||
let camera_params =
|
||||
CameraBaseParameters::new(camera_transform, film, medium.into(), params, loc)?;
|
||||
CameraBaseParameters::new(camera_transform, film, medium, params, loc)?;
|
||||
let base = CameraBase::create(camera_params);
|
||||
let m = params.get_one_string("mapping", "equalarea")?;
|
||||
let mapping = match m.as_str() {
|
||||
|
|
|
|||
|
|
@ -21,13 +21,13 @@ impl Deref for RGBToSpectrumTableData {
|
|||
|
||||
impl RGBToSpectrumTableData {
|
||||
pub fn new(z_nodes: Vec<Float>, coeffs: Vec<Float>) -> Self {
|
||||
eprintln!("z_nodes.len() = {}, coeffs.len() = {}", z_nodes.len(), coeffs.len());
|
||||
assert_eq!(z_nodes.len(), RES as usize);
|
||||
assert_eq!(coeffs.len(), (RES * RES * RES) as usize * 3 * 3); // bucket*z*y*x*3(coeffs)
|
||||
let coeffs_struct = Coeffs::from(&[coeffs[0], coeffs[1], coeffs[2]]);
|
||||
assert_eq!(coeffs.len(), (RES * RES * RES) as usize * 3 * 3);
|
||||
|
||||
let view = RGBToSpectrumTable {
|
||||
z_nodes: Ptr::from(z_nodes.as_ptr()),
|
||||
coeffs: Ptr::from(&coeffs_struct),
|
||||
coeffs: Ptr::from(coeffs.as_ptr() as *const Coeffs),
|
||||
n_nodes: z_nodes.len() as u32,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use crate::filters::*;
|
||||
use crate::utils::containers::Array2D;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::utils::DeviceRepr;
|
||||
use crate::utils::{FileLoc, ParameterDictionary};
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::Float;
|
||||
use shared::core::filter::{Filter, FilterSampler};
|
||||
use anyhow::{anyhow, Result};
|
||||
use shared::core::filter::{DeviceFilterSampler, Filter};
|
||||
use shared::core::geometry::{Bounds2f, Point2f, Vector2f};
|
||||
use shared::filters::*;
|
||||
use shared::Float;
|
||||
|
||||
pub trait FilterFactory {
|
||||
fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result<Filter>;
|
||||
|
|
@ -54,14 +55,16 @@ impl FilterFactory for Filter {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait CreateFilterSampler {
|
||||
fn new<F>(radius: Vector2f, func: F) -> Self
|
||||
where
|
||||
F: Fn(Point2f) -> Float;
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct FilterSampler {
|
||||
pub domain: Bounds2f,
|
||||
pub distrib: PiecewiseConstant2D,
|
||||
pub f: Array2D<Float>,
|
||||
}
|
||||
|
||||
impl CreateFilterSampler for FilterSampler {
|
||||
fn new<F>(radius: Vector2f, func: F) -> Self
|
||||
impl FilterSampler {
|
||||
pub fn new<F>(radius: Vector2f, func: F) -> Self
|
||||
where
|
||||
F: Fn(Point2f) -> Float,
|
||||
{
|
||||
|
|
@ -72,7 +75,6 @@ impl CreateFilterSampler for FilterSampler {
|
|||
|
||||
let nx = (32.0 * radius.x()) as i32;
|
||||
let ny = (32.0 * radius.y()) as i32;
|
||||
|
||||
let mut f = Array2D::new_dims(nx, ny);
|
||||
for y in 0..f.y_size() {
|
||||
for x in 0..f.x_size() {
|
||||
|
|
@ -83,11 +85,21 @@ impl CreateFilterSampler for FilterSampler {
|
|||
f[(x as i32, y as i32)] = func(p);
|
||||
}
|
||||
}
|
||||
|
||||
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
|
||||
Self {
|
||||
domain,
|
||||
f: *f.device(),
|
||||
distrib: distrib.device,
|
||||
|
||||
Self { domain, distrib, f }
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceRepr for FilterSampler {
|
||||
type Target = DeviceFilterSampler;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> DeviceFilterSampler {
|
||||
DeviceFilterSampler {
|
||||
domain: self.domain,
|
||||
distrib: self.distrib.upload_value(arena),
|
||||
f: self.f.upload_value(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ use shared::core::light::Light;
|
|||
use shared::core::medium::Medium;
|
||||
use shared::core::shape::Shape;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::lights::*;
|
||||
use shared::spectra::RGBColorSpace;
|
||||
use shared::utils::Transform;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -19,111 +18,79 @@ pub fn lookup_spectrum(s: &Spectrum) -> Arc<DenselySampledSpectrumBuffer> {
|
|||
cache.lookup(dense_spectrum).into()
|
||||
}
|
||||
|
||||
pub trait CreateLight {
|
||||
fn create(
|
||||
render_from_light: Transform,
|
||||
medium: Medium,
|
||||
parameters: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
shape: &Shape,
|
||||
alpha_text: &FloatTexture,
|
||||
colorspace: Option<&RGBColorSpace>,
|
||||
arena: &Arena,
|
||||
) -> Result<Light>;
|
||||
// Placeholders for non-area lights that never inspect these arguments.
|
||||
// TODO: refactor each light's create to only take what it actually needs,
|
||||
// then delete these.
|
||||
fn dummy_shape() -> Shape {
|
||||
Shape::default()
|
||||
}
|
||||
|
||||
pub trait LightFactory {
|
||||
fn create(
|
||||
name: &str,
|
||||
arena: &mut Arena,
|
||||
render_from_light: Transform,
|
||||
medium: Medium,
|
||||
parameters: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
shape: &Shape,
|
||||
alpha_tex: &FloatTexture,
|
||||
colorspace: Option<&RGBColorSpace>,
|
||||
camera_transform: CameraTransform,
|
||||
) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
fn dummy_alpha() -> FloatTexture {
|
||||
FloatTexture::default()
|
||||
}
|
||||
|
||||
impl LightFactory for Light {
|
||||
fn create(
|
||||
/// Create a non-area light from a scene file directive.
|
||||
pub fn create_light(
|
||||
name: &str,
|
||||
arena: &mut Arena,
|
||||
render_from_light: Transform,
|
||||
medium: Medium,
|
||||
medium: Option<Medium>,
|
||||
parameters: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
shape: &Shape,
|
||||
alpha_tex: &FloatTexture,
|
||||
colorspace: Option<&RGBColorSpace>,
|
||||
camera_transform: CameraTransform,
|
||||
) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
arena: &mut Arena,
|
||||
) -> Result<Light> {
|
||||
let shape = dummy_shape();
|
||||
let alpha = dummy_alpha();
|
||||
|
||||
match name {
|
||||
"diffuse" => DiffuseAreaLight::create(
|
||||
"point" => crate::lights::point::create(
|
||||
render_from_light,
|
||||
medium,
|
||||
parameters,
|
||||
loc,
|
||||
shape,
|
||||
alpha_tex,
|
||||
colorspace,
|
||||
&shape,
|
||||
&alpha,
|
||||
None,
|
||||
arena,
|
||||
),
|
||||
"point" => PointLight::create(
|
||||
"spot" => crate::lights::spot::create(
|
||||
render_from_light,
|
||||
medium,
|
||||
parameters,
|
||||
loc,
|
||||
shape,
|
||||
alpha_tex,
|
||||
colorspace,
|
||||
&shape,
|
||||
&alpha,
|
||||
None,
|
||||
arena,
|
||||
),
|
||||
"spot" => SpotLight::create(
|
||||
"distant" => crate::lights::distant::create(
|
||||
render_from_light,
|
||||
medium,
|
||||
parameters,
|
||||
loc,
|
||||
shape,
|
||||
alpha_tex,
|
||||
colorspace,
|
||||
&shape,
|
||||
&alpha,
|
||||
None,
|
||||
arena,
|
||||
),
|
||||
"goniometric" => GoniometricLight::create(
|
||||
"goniometric" => crate::lights::goniometric::create(
|
||||
render_from_light,
|
||||
medium,
|
||||
parameters,
|
||||
loc,
|
||||
shape,
|
||||
alpha_tex,
|
||||
colorspace,
|
||||
&shape,
|
||||
&alpha,
|
||||
None,
|
||||
arena,
|
||||
),
|
||||
"projection" => ProjectionLight::create(
|
||||
"projection" => crate::lights::projection::create(
|
||||
render_from_light,
|
||||
medium,
|
||||
parameters,
|
||||
loc,
|
||||
shape,
|
||||
alpha_tex,
|
||||
colorspace,
|
||||
arena,
|
||||
),
|
||||
"distant" => DistantLight::create(
|
||||
render_from_light,
|
||||
medium,
|
||||
parameters,
|
||||
loc,
|
||||
shape,
|
||||
alpha_tex,
|
||||
colorspace,
|
||||
&shape,
|
||||
&alpha,
|
||||
None,
|
||||
arena,
|
||||
),
|
||||
"infinite" => crate::lights::infinite::create(
|
||||
|
|
@ -131,11 +98,38 @@ impl LightFactory for Light {
|
|||
medium.into(),
|
||||
camera_transform,
|
||||
parameters,
|
||||
colorspace,
|
||||
None,
|
||||
loc,
|
||||
arena,
|
||||
),
|
||||
_ => Err(anyhow!("{}: unknown light type: \"{}\"", loc, name)),
|
||||
"diffuse" => Err(anyhow!(
|
||||
"{}: \"diffuse\" is an area light; use create_area_light with a shape",
|
||||
loc
|
||||
)),
|
||||
_ => Err(anyhow!("{}: unknown light type \"{}\"", loc, name)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a diffuse area light bound to a specific shape.
|
||||
/// Called once per sub-shape (e.g. once per triangle in a mesh).
|
||||
pub fn create_area_light(
|
||||
render_from_light: Transform,
|
||||
medium: Option<Medium>,
|
||||
parameters: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
shape: &Shape,
|
||||
alpha_tex: &FloatTexture,
|
||||
colorspace: Option<&RGBColorSpace>,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Light> {
|
||||
crate::lights::diffuse::create(
|
||||
render_from_light,
|
||||
medium,
|
||||
parameters,
|
||||
loc,
|
||||
shape,
|
||||
alpha_tex,
|
||||
colorspace,
|
||||
arena,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,10 @@ impl MaterialFactory for Material {
|
|||
named_materials: &HashMap<String, Material>,
|
||||
loc: FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Self> where Self: Sized {
|
||||
) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match name {
|
||||
"diffuse" => {
|
||||
DiffuseMaterial::create(parameters, normal_map, named_materials, &loc, arena)
|
||||
|
|
@ -83,3 +86,21 @@ impl MaterialFactory for Material {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_diffuse_material(arena: &Arena) -> Material {
|
||||
use shared::core::texture::GPUSpectrumTexture;
|
||||
use shared::core::texture::SpectrumConstantTexture;
|
||||
use shared::core::spectrum::{ConstantSpectrum, Spectrum};
|
||||
use shared::materials::DiffuseMaterial;
|
||||
use shared::utils::Ptr;
|
||||
|
||||
let grey = Spectrum::Constant(ConstantSpectrum { c: 0.5 });
|
||||
let tex = GPUSpectrumTexture::Constant(SpectrumConstantTexture::new(grey));
|
||||
let tex_ptr = arena.alloc(tex);
|
||||
|
||||
Material::Diffuse(DiffuseMaterial {
|
||||
normal_map: Ptr::null(),
|
||||
displacement: Ptr::null(),
|
||||
reflectance: tex_ptr,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -640,8 +640,9 @@ impl ParserTarget for BasicSceneBuilder {
|
|||
loc: FileLoc,
|
||||
arena: Arc<Arena>,
|
||||
) -> Result<(), ParserError> {
|
||||
eprintln!("TEXTURE: name='{}' type='{}' tex='{}'", orig_name, type_name, tex_name);
|
||||
let name = normalize_utf8(orig_name);
|
||||
self.verify_world("Texture", &loc);
|
||||
self.verify_world("Texture", &loc)?;
|
||||
let dict = ParameterDictionary::from_array(
|
||||
params.clone(),
|
||||
&self.graphics_state.texture_attributes,
|
||||
|
|
@ -701,7 +702,7 @@ impl ParserTarget for BasicSceneBuilder {
|
|||
params: ParsedParameterVector,
|
||||
loc: FileLoc,
|
||||
) -> Result<(), ParserError> {
|
||||
self.verify_world("material", &loc);
|
||||
self.verify_world("material", &loc)?;
|
||||
let entity = SceneEntity {
|
||||
name: name.to_string(),
|
||||
loc,
|
||||
|
|
@ -733,7 +734,7 @@ impl ParserTarget for BasicSceneBuilder {
|
|||
let dict = ParameterDictionary::from_array(
|
||||
params.clone(),
|
||||
&self.graphics_state.medium_attributes,
|
||||
self.graphics_state.color_space.clone()
|
||||
self.graphics_state.color_space.clone(),
|
||||
)?;
|
||||
|
||||
let render_from_light = AnimatedTransform::new(
|
||||
|
|
@ -757,7 +758,6 @@ impl ParserTarget for BasicSceneBuilder {
|
|||
|
||||
self.scene.add_light(entity);
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
fn area_light_source(
|
||||
|
|
@ -786,7 +786,7 @@ impl ParserTarget for BasicSceneBuilder {
|
|||
let dict = ParameterDictionary::from_array(
|
||||
params.clone(),
|
||||
&self.graphics_state.shape_attributes,
|
||||
self.graphics_state.color_space.clone()
|
||||
self.graphics_state.color_space.clone(),
|
||||
)?;
|
||||
|
||||
let render_from_object = self.graphics_state.ctm[0];
|
||||
|
|
@ -797,7 +797,7 @@ impl ParserTarget for BasicSceneBuilder {
|
|||
let light_entity = SceneEntity {
|
||||
name: al.name.clone(),
|
||||
loc: al.loc.clone(),
|
||||
parameters: al_dict
|
||||
parameters: al_dict,
|
||||
};
|
||||
Some(self.scene.add_area_light(light_entity))
|
||||
} else {
|
||||
|
|
@ -816,7 +816,7 @@ impl ParserTarget for BasicSceneBuilder {
|
|||
base: SceneEntity {
|
||||
name: name.to_string(),
|
||||
loc,
|
||||
parameters: dict
|
||||
parameters: dict,
|
||||
},
|
||||
render_from_object: Arc::new(render_from_object),
|
||||
object_from_render: Arc::new(object_from_render),
|
||||
|
|
@ -828,7 +828,11 @@ impl ParserTarget for BasicSceneBuilder {
|
|||
};
|
||||
|
||||
if self.active_instance_definition.is_some() {
|
||||
self.active_instance_definition.as_mut().unwrap().shapes.push(entity)
|
||||
self.active_instance_definition
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.shapes
|
||||
.push(entity)
|
||||
} else {
|
||||
self.scene.add_shape(entity);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
mod builder;
|
||||
mod entities;
|
||||
mod scene;
|
||||
mod state;
|
||||
pub mod builder;
|
||||
pub mod entities;
|
||||
pub mod scene;
|
||||
pub mod state;
|
||||
|
||||
pub use builder::BasicSceneBuilder;
|
||||
pub use entities::*;
|
||||
pub use scene::{BasicScene, SceneLookup};
|
||||
pub use state::*;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,28 +3,26 @@ use super::state::*;
|
|||
use crate::core::camera::CameraFactory;
|
||||
use crate::core::film::FilmFactory;
|
||||
use crate::core::filter::FilterFactory;
|
||||
use crate::core::image::{Image, io::ImageIO};
|
||||
use crate::core::light::LightFactory;
|
||||
use crate::core::image::{io::ImageIO, Image};
|
||||
use crate::core::material::MaterialFactory;
|
||||
use crate::core::primitive::{CreateGeometricPrimitive, CreateSimplePrimitive};
|
||||
use crate::core::sampler::SamplerFactory;
|
||||
use crate::core::shape::ShapeFactory;
|
||||
use crate::core::shape::{ShapeFactory, ShapeWithContext};
|
||||
use crate::core::texture::{FloatTexture, SpectrumTexture};
|
||||
use crate::utils::parallel::{AsyncJob, run_async};
|
||||
use crate::utils::parallel::{run_async, AsyncJob};
|
||||
use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
|
||||
use crate::utils::{Upload, resolve_filename};
|
||||
use crate::utils::{resolve_filename, Upload};
|
||||
use crate::{Arena, FileLoc};
|
||||
use anyhow::{Result, anyhow};
|
||||
use anyhow::{anyhow, Result};
|
||||
use parking_lot::Mutex;
|
||||
use rayon::prelude::*;
|
||||
use shared::core::camera::{CameraTransform, Camera};
|
||||
use shared::core::camera::{Camera, CameraTransform};
|
||||
use shared::core::color::LINEAR;
|
||||
use shared::core::film::Film;
|
||||
use shared::core::filter::Filter;
|
||||
use shared::core::light::Light;
|
||||
use shared::core::material::Material;
|
||||
use shared::core::medium::{Medium, MediumInterface};
|
||||
use shared::core::primitive::{GeometricPrimitive, Primitive, SimplePrimitive};
|
||||
use shared::core::primitive::{AnimatedPrimitive, GeometricPrimitive, Primitive, SimplePrimitive};
|
||||
use shared::core::sampler::Sampler;
|
||||
use shared::core::shape::Shape;
|
||||
use shared::core::texture::SpectrumType;
|
||||
|
|
@ -47,16 +45,28 @@ impl<'a> SceneLookup<'a> {
|
|||
return None;
|
||||
}
|
||||
self.media.get(name).cloned().or_else(|| {
|
||||
panic!("{}: medium '{}' not defined", loc, name);
|
||||
log::error!("{}: medium '{}' not defined", loc, name);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resolve_material(&self, mat_ref: &MaterialRef, _loc: &FileLoc) -> Option<Material> {
|
||||
pub fn resolve_material(&self, mat_ref: &MaterialRef, loc: &FileLoc) -> Option<Material> {
|
||||
match mat_ref {
|
||||
MaterialRef::Name(name) => {
|
||||
Some(*self.named_materials.get(name).expect("Material not found"))
|
||||
MaterialRef::Name(name) => match self.named_materials.get(name) {
|
||||
Some(m) => Some(*m),
|
||||
None => {
|
||||
log::error!("{}: named material '{}' not found", loc, name);
|
||||
None
|
||||
}
|
||||
},
|
||||
MaterialRef::Index(idx) => {
|
||||
if *idx < self.materials.len() {
|
||||
Some(self.materials[*idx])
|
||||
} else {
|
||||
log::error!("{}: material index {} out of bounds", loc, idx);
|
||||
None
|
||||
}
|
||||
}
|
||||
MaterialRef::Index(idx) => Some(self.materials[*idx]),
|
||||
MaterialRef::None => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -168,7 +178,7 @@ impl BasicScene {
|
|||
&camera.base.name,
|
||||
&camera.base.parameters,
|
||||
&camera.camera_transform,
|
||||
*medium.unwrap(),
|
||||
medium,
|
||||
camera_film,
|
||||
&camera.base.loc,
|
||||
&arena_camera,
|
||||
|
|
@ -236,7 +246,7 @@ impl BasicScene {
|
|||
|
||||
fn validate_texture_file(&self, filename: &str, loc: &FileLoc, n_missing: &mut usize) -> bool {
|
||||
if filename.is_empty() {
|
||||
log::error!(
|
||||
eprintln!(
|
||||
"[{:?}] \"string filename\" not provided for image texture.",
|
||||
loc
|
||||
);
|
||||
|
|
@ -244,7 +254,7 @@ impl BasicScene {
|
|||
return false;
|
||||
}
|
||||
if !std::path::Path::new(filename).exists() {
|
||||
log::error!("[{:?}] {}: file not found.", loc, filename);
|
||||
eprintln!("[{:?}] {}: file not found.", loc, filename);
|
||||
*n_missing += 1;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -502,127 +512,406 @@ impl BasicScene {
|
|||
&self,
|
||||
camera_transform: &CameraTransform,
|
||||
arena: &mut Arena,
|
||||
) -> Result<Vec<Light>> {
|
||||
) -> Vec<Light> {
|
||||
let state = self.light_state.lock();
|
||||
|
||||
state.lights.par_iter().map(|entity| {
|
||||
state
|
||||
.lights
|
||||
.iter()
|
||||
.filter_map(|entity| {
|
||||
let render_from_light = entity.transformed_base.render_from_object.start_transform;
|
||||
let medium = self.get_medium(
|
||||
&entity.medium,
|
||||
|
||||
let medium = self
|
||||
.get_medium(&entity.medium, &entity.transformed_base.base.loc)
|
||||
.map(|m| *m);
|
||||
|
||||
match crate::core::light::create_light(
|
||||
&entity.transformed_base.base.name,
|
||||
render_from_light,
|
||||
medium,
|
||||
&entity.transformed_base.base.parameters,
|
||||
&entity.transformed_base.base.loc,
|
||||
camera_transform.clone(),
|
||||
arena,
|
||||
) {
|
||||
Ok(light) => Some(light),
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
"{}: failed to create light: {}",
|
||||
entity.transformed_base.base.loc,
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Create area lights for shapes that reference one. Produces a map from
|
||||
/// shape index to a vec of lights.
|
||||
/// Must be called after shapes are loaded but before upload_shapes.
|
||||
pub fn create_area_lights(
|
||||
&self,
|
||||
loaded_shapes: &[Vec<Shape>],
|
||||
shape_entities: &[ShapeSceneEntity],
|
||||
textures: &NamedTextures,
|
||||
arena: &mut Arena,
|
||||
) -> HashMap<usize, Vec<Light>> {
|
||||
let light_state = self.light_state.lock();
|
||||
let mut shape_lights: HashMap<usize, Vec<Light>> = HashMap::new();
|
||||
|
||||
for (i, entity) in shape_entities.iter().enumerate() {
|
||||
let light_idx = match entity.light_index {
|
||||
Some(idx) => idx,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let shapes = match loaded_shapes.get(i) {
|
||||
Some(s) if !s.is_empty() => s,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let al_entity = &light_state.area_lights[light_idx];
|
||||
|
||||
let alpha_tex = Self::get_alpha_texture(
|
||||
&entity.base.parameters,
|
||||
&entity.base.loc,
|
||||
&textures.float_textures,
|
||||
);
|
||||
|
||||
Light::create(
|
||||
&entity.transformed_base.base.name,
|
||||
&entity.transformed_base.base.parameters,
|
||||
let default_alpha = Arc::new(FloatTexture::default());
|
||||
let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha);
|
||||
|
||||
// Use the film colorspace as fallback for area light emission
|
||||
let film_cs = self.film_colorspace.lock();
|
||||
let colorspace_ref = al_entity
|
||||
.parameters
|
||||
.color_space
|
||||
.as_ref()
|
||||
.or(film_cs.as_ref());
|
||||
|
||||
let render_from_light = *entity.render_from_object;
|
||||
|
||||
let lights: Vec<Light> = shapes
|
||||
.iter()
|
||||
.filter_map(|shape| {
|
||||
match crate::core::light::create_area_light(
|
||||
render_from_light,
|
||||
camera_transform,
|
||||
medium.map(|m| *m),
|
||||
&entity.transformed_base.base.loc,
|
||||
None,
|
||||
&al_entity.parameters,
|
||||
&al_entity.loc,
|
||||
shape,
|
||||
alpha_ref,
|
||||
colorspace_ref.map(|cs| cs.as_ref()),
|
||||
arena,
|
||||
)
|
||||
}).collect()
|
||||
) {
|
||||
Ok(light) => Some(light),
|
||||
Err(e) => {
|
||||
log::error!("{}: failed to create area light: {}", al_entity.loc, e);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !lights.is_empty() {
|
||||
shape_lights.insert(i, lights);
|
||||
}
|
||||
}
|
||||
|
||||
shape_lights
|
||||
}
|
||||
|
||||
pub fn create_aggregate(
|
||||
&self,
|
||||
textures: &NamedTextures,
|
||||
named_materials: &HashMap<String, Material>,
|
||||
materials: &Vec<Material>,
|
||||
shape_lights: &HashMap<usize, Vec<Light>>,
|
||||
materials: &[Material],
|
||||
arena: &mut Arena,
|
||||
) -> Vec<Primitive> {
|
||||
let shapes = self.shapes.lock();
|
||||
let animated_shapes = self.animated_shapes.lock();
|
||||
) -> (Vec<Primitive>, Vec<Arc<Light>>) {
|
||||
let entities = self.shapes.lock();
|
||||
let animated = self.animated_shapes.lock();
|
||||
let light_state = self.light_state.lock();
|
||||
let media = self.media_state.lock();
|
||||
|
||||
let lookup = SceneLookup {
|
||||
textures,
|
||||
media: &media.map,
|
||||
named_materials,
|
||||
materials,
|
||||
shape_lights,
|
||||
};
|
||||
let film_cs = self.film_colorspace.lock();
|
||||
|
||||
let mut primitives = Vec::new();
|
||||
let mut area_lights = Vec::new();
|
||||
|
||||
let loaded = self.load_shapes_parallel(&shapes, &lookup, arena);
|
||||
primitives.extend(self.upload_shapes(arena, &shapes, loaded, &lookup));
|
||||
|
||||
let loaded_anim = self.load_animated_shapes_parallel(&animated_shapes, &lookup, arena);
|
||||
primitives.extend(self.upload_animated_shapes(
|
||||
for entity in entities.iter() {
|
||||
Self::build_primitives_for_entity(
|
||||
entity,
|
||||
textures,
|
||||
named_materials,
|
||||
materials,
|
||||
&light_state,
|
||||
&media,
|
||||
film_cs.as_ref().map(|v| &**v),
|
||||
arena,
|
||||
&animated_shapes,
|
||||
loaded_anim,
|
||||
&lookup,
|
||||
));
|
||||
|
||||
primitives
|
||||
&mut primitives,
|
||||
&mut area_lights,
|
||||
);
|
||||
}
|
||||
|
||||
fn load_shapes_parallel(
|
||||
&self,
|
||||
entities: &[ShapeSceneEntity],
|
||||
lookup: &SceneLookup,
|
||||
for entity in animated.iter() {
|
||||
Self::build_animated_primitives_for_entity(
|
||||
entity,
|
||||
textures,
|
||||
named_materials,
|
||||
materials,
|
||||
&light_state,
|
||||
&media,
|
||||
film_cs.as_ref().map(|v| &**v),
|
||||
arena,
|
||||
&mut primitives,
|
||||
&mut area_lights,
|
||||
);
|
||||
}
|
||||
|
||||
(primitives, area_lights)
|
||||
}
|
||||
|
||||
fn build_primitives_for_entity(
|
||||
entity: &ShapeSceneEntity,
|
||||
textures: &NamedTextures,
|
||||
named_materials: &HashMap<String, Material>,
|
||||
materials: &[Material],
|
||||
light_state: &LightState,
|
||||
media: &MediaState,
|
||||
film_cs: Option<&RGBColorSpace>,
|
||||
arena: &mut Arena,
|
||||
) -> Vec<Vec<Shape>> {
|
||||
entities
|
||||
.par_iter()
|
||||
.filter_map(|sh| {
|
||||
Shape::create(
|
||||
&sh.base.name,
|
||||
*sh.render_from_object.as_ref(),
|
||||
*sh.object_from_render.as_ref(),
|
||||
sh.reverse_orientation,
|
||||
sh.base.parameters.clone(),
|
||||
&lookup.textures.float_textures,
|
||||
sh.base.loc.clone(),
|
||||
primitives: &mut Vec<Primitive>,
|
||||
area_lights: &mut Vec<Arc<Light>>,
|
||||
) {
|
||||
let shapes = Shape::create(
|
||||
&entity.base.name,
|
||||
*entity.render_from_object.as_ref(),
|
||||
*entity.object_from_render.as_ref(),
|
||||
entity.reverse_orientation,
|
||||
entity.base.parameters.clone(),
|
||||
&textures.float_textures,
|
||||
entity.base.loc.clone(),
|
||||
arena,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("Shape '{}' failed: {}", entity.base.name, e);
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
let mtl = match &entity.material {
|
||||
MaterialRef::Name(name) => named_materials.get(name).copied(),
|
||||
MaterialRef::Index(idx) => materials.get(*idx).copied(),
|
||||
MaterialRef::None => None,
|
||||
}
|
||||
.unwrap_or_else(|| crate::core::material::default_diffuse_material(arena));
|
||||
|
||||
let alpha_tex = Self::get_alpha_texture(
|
||||
&entity.base.parameters,
|
||||
&entity.base.loc,
|
||||
&textures.float_textures,
|
||||
);
|
||||
|
||||
let mi = MediumInterface {
|
||||
inside: media
|
||||
.map
|
||||
.get(&entity.inside_medium)
|
||||
.map(|m| Ptr::from(m.as_ref()))
|
||||
.unwrap_or(Ptr::null()),
|
||||
outside: media
|
||||
.map
|
||||
.get(&entity.outside_medium)
|
||||
.map(|m| Ptr::from(m.as_ref()))
|
||||
.unwrap_or(Ptr::null()),
|
||||
};
|
||||
|
||||
let al_params = entity.light_index.map(|idx| &light_state.area_lights[idx]);
|
||||
|
||||
for shape in shapes {
|
||||
let area_light = al_params.and_then(|al_entity| {
|
||||
let colorspace_ref = al_entity.parameters.color_space.as_deref().or(film_cs);
|
||||
let default_alpha = Arc::new(FloatTexture::default());
|
||||
let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha);
|
||||
|
||||
crate::core::light::create_area_light(
|
||||
*entity.render_from_object,
|
||||
None,
|
||||
&al_entity.parameters,
|
||||
&al_entity.loc,
|
||||
&shape,
|
||||
alpha_ref,
|
||||
colorspace_ref,
|
||||
arena,
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
let uploaded_light = area_light
|
||||
.as_ref()
|
||||
.map(|l| l.upload(arena))
|
||||
.unwrap_or(Ptr::null());
|
||||
|
||||
if let Some(ref light) = area_light {
|
||||
area_lights.push(Arc::new(light.clone()));
|
||||
}
|
||||
|
||||
fn load_animated_shapes_parallel(
|
||||
&self,
|
||||
entities: &[AnimatedShapeSceneEntity],
|
||||
lookup: &SceneLookup,
|
||||
arena: &Arena,
|
||||
) -> Vec<Vec<Shape>> {
|
||||
entities
|
||||
.par_iter()
|
||||
.map(|sh| {
|
||||
Shape::create(
|
||||
&sh.transformed_base.base.name,
|
||||
*sh.identity.as_ref(),
|
||||
*sh.identity.as_ref(),
|
||||
sh.reverse_orientation,
|
||||
sh.transformed_base.base.parameters.clone(),
|
||||
&lookup.textures.float_textures,
|
||||
sh.transformed_base.base.loc.clone(),
|
||||
let shape_ptr = shape.upload(arena);
|
||||
|
||||
let prim =
|
||||
if uploaded_light.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() {
|
||||
Primitive::Simple(SimplePrimitive::new(shape_ptr, Ptr::from(&mtl)))
|
||||
} else {
|
||||
Primitive::Geometric(GeometricPrimitive::new(
|
||||
shape_ptr,
|
||||
mtl.upload(arena),
|
||||
uploaded_light,
|
||||
mi.clone(),
|
||||
alpha_tex
|
||||
.as_ref()
|
||||
.map(|t| t.upload(arena))
|
||||
.unwrap_or(Ptr::null()),
|
||||
))
|
||||
};
|
||||
|
||||
primitives.push(prim);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_animated_primitives_for_entity(
|
||||
entity: &AnimatedShapeSceneEntity,
|
||||
textures: &NamedTextures,
|
||||
named_materials: &HashMap<String, Material>,
|
||||
materials: &[Material],
|
||||
light_state: &LightState,
|
||||
media: &MediaState,
|
||||
film_cs: Option<&RGBColorSpace>,
|
||||
arena: &mut Arena,
|
||||
primitives: &mut Vec<Primitive>,
|
||||
area_lights: &mut Vec<Arc<Light>>,
|
||||
) {
|
||||
let shapes = Shape::create(
|
||||
&entity.transformed_base.base.name,
|
||||
entity.transformed_base.render_from_object.start_transform,
|
||||
entity
|
||||
.transformed_base
|
||||
.render_from_object
|
||||
.start_transform
|
||||
.inverse(),
|
||||
entity.reverse_orientation,
|
||||
entity.transformed_base.base.parameters.clone(),
|
||||
&textures.float_textures,
|
||||
entity.transformed_base.base.loc.clone(),
|
||||
arena,
|
||||
)
|
||||
.expect("Could not create shape")
|
||||
})
|
||||
.collect()
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!(
|
||||
"Animated shape '{}' failed: {}",
|
||||
entity.transformed_base.base.name, e
|
||||
);
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
let mtl = match &entity.material {
|
||||
MaterialRef::Name(name) => named_materials.get(name).copied(),
|
||||
MaterialRef::Index(idx) => materials.get(*idx).copied(),
|
||||
MaterialRef::None => None,
|
||||
}
|
||||
.unwrap_or_else(|| crate::core::material::default_diffuse_material(arena));
|
||||
|
||||
let alpha_tex = Self::get_alpha_texture(
|
||||
&entity.transformed_base.base.parameters,
|
||||
&entity.transformed_base.base.loc,
|
||||
&textures.float_textures,
|
||||
);
|
||||
|
||||
let mi = MediumInterface {
|
||||
inside: media
|
||||
.map
|
||||
.get(&entity.inside_medium)
|
||||
.map(|m| Ptr::from(m.as_ref()))
|
||||
.unwrap_or(Ptr::null()),
|
||||
outside: media
|
||||
.map
|
||||
.get(&entity.outside_medium)
|
||||
.map(|m| Ptr::from(m.as_ref()))
|
||||
.unwrap_or(Ptr::null()),
|
||||
};
|
||||
|
||||
let al_params = entity.light_index.map(|idx| &light_state.area_lights[idx]);
|
||||
|
||||
for shape in shapes {
|
||||
let area_light = al_params.and_then(|al_entity| {
|
||||
let colorspace_ref = al_entity.parameters.color_space.as_deref().or(film_cs);
|
||||
let default_alpha = Arc::new(FloatTexture::default());
|
||||
let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha);
|
||||
|
||||
crate::core::light::create_area_light(
|
||||
entity.transformed_base.render_from_object.start_transform,
|
||||
None,
|
||||
&al_entity.parameters,
|
||||
&al_entity.loc,
|
||||
&shape,
|
||||
alpha_ref,
|
||||
colorspace_ref,
|
||||
arena,
|
||||
)
|
||||
.ok()
|
||||
});
|
||||
|
||||
let uploaded_light = area_light
|
||||
.as_ref()
|
||||
.map(|l| l.upload(arena))
|
||||
.unwrap_or(Ptr::null());
|
||||
|
||||
if let Some(ref light) = area_light {
|
||||
area_lights.push(Arc::new(light.clone()));
|
||||
}
|
||||
|
||||
let shape_ptr = shape.upload(arena);
|
||||
|
||||
let base_prim =
|
||||
if uploaded_light.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() {
|
||||
Primitive::Simple(SimplePrimitive::new(shape_ptr, Ptr::from(&mtl)))
|
||||
} else {
|
||||
Primitive::Geometric(GeometricPrimitive::new(
|
||||
shape_ptr,
|
||||
mtl.upload(arena),
|
||||
uploaded_light,
|
||||
mi.clone(),
|
||||
alpha_tex
|
||||
.as_ref()
|
||||
.map(|t| t.upload(arena))
|
||||
.unwrap_or(Ptr::null()),
|
||||
))
|
||||
};
|
||||
|
||||
let base_ptr = arena.alloc(base_prim);
|
||||
|
||||
primitives.push(Primitive::Animated(AnimatedPrimitive {
|
||||
primitive: base_ptr,
|
||||
render_from_primitive: arena.alloc(entity.transformed_base.render_from_object),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_shapes(
|
||||
&self,
|
||||
arena: &Arena,
|
||||
entities: &[ShapeSceneEntity],
|
||||
loaded: Vec<Vec<Shape>>,
|
||||
loaded: Vec<ShapeWithContext>,
|
||||
lookup: &SceneLookup,
|
||||
) -> Vec<Primitive> {
|
||||
let mut primitives = Vec::new();
|
||||
let mut count = 0;
|
||||
|
||||
for (i, (entity, shapes)) in entities.iter().zip(loaded).enumerate() {
|
||||
if shapes.is_empty() {
|
||||
continue;
|
||||
for shape_ctx in loaded {
|
||||
count += 1;
|
||||
if count % 500_000 == 0 {
|
||||
eprintln!(" processed {} primitives", count);
|
||||
}
|
||||
let entity = &entities[shape_ctx.entity_index];
|
||||
|
||||
let alpha_tex = self.get_alpha_texture(
|
||||
let alpha_tex = Self::get_alpha_texture(
|
||||
&entity.base.parameters,
|
||||
&entity.base.loc,
|
||||
&lookup.textures.float_textures,
|
||||
|
|
@ -630,51 +919,44 @@ impl BasicScene {
|
|||
|
||||
let mtl = lookup
|
||||
.resolve_material(&entity.material, &entity.base.loc)
|
||||
.unwrap();
|
||||
.unwrap_or_else(|| crate::core::material::default_diffuse_material(arena));
|
||||
|
||||
let mi = MediumInterface::new(
|
||||
lookup
|
||||
let mi = MediumInterface {
|
||||
inside: lookup
|
||||
.find_medium(&entity.inside_medium, &entity.base.loc)
|
||||
.unwrap()
|
||||
.as_ref(),
|
||||
lookup
|
||||
.map(|m| Ptr::from(m.as_ref()))
|
||||
.unwrap_or(Ptr::null()),
|
||||
outside: lookup
|
||||
.find_medium(&entity.outside_medium, &entity.base.loc)
|
||||
.unwrap()
|
||||
.as_ref(),
|
||||
);
|
||||
.map(|m| Ptr::from(m.as_ref()))
|
||||
.unwrap_or(Ptr::null()),
|
||||
};
|
||||
|
||||
let shape_lights_opt = lookup.shape_lights.get(&i);
|
||||
let shape_lights_opt = lookup
|
||||
.shape_lights
|
||||
.get(&shape_ctx.entity_index)
|
||||
.and_then(|lights| lights.get(shape_ctx.shape_index));
|
||||
|
||||
for (j, shape) in shapes.into_iter().enumerate() {
|
||||
let mut area_light = None;
|
||||
if entity.light_index.is_some() {
|
||||
if let Some(lights) = shape_lights_opt {
|
||||
if j < lights.len() {
|
||||
area_light = Some(lights[j].clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let area_light = shape_lights_opt
|
||||
.map(|l| l.upload(arena))
|
||||
.unwrap_or(Ptr::null());
|
||||
|
||||
let shape_ptr = shape.upload(arena);
|
||||
|
||||
let prim =
|
||||
if area_light.is_none() && !mi.is_medium_transition() && alpha_tex.is_none() {
|
||||
let p = SimplePrimitive::new(shape_ptr, Ptr::from(&mtl));
|
||||
Primitive::Simple(p)
|
||||
let shape_ptr = shape_ctx.shape;
|
||||
let prim = if area_light.is_null() && !mi.is_medium_transition() && alpha_tex.is_none()
|
||||
{
|
||||
Primitive::Simple(SimplePrimitive::new(shape_ptr, Ptr::from(&mtl)))
|
||||
} else {
|
||||
let p = GeometricPrimitive::new(
|
||||
Primitive::Geometric(GeometricPrimitive::new(
|
||||
shape_ptr,
|
||||
mtl.upload(arena),
|
||||
area_light.upload(arena),
|
||||
area_light,
|
||||
mi.clone(),
|
||||
alpha_tex.upload(arena),
|
||||
);
|
||||
Primitive::Geometric(p)
|
||||
))
|
||||
};
|
||||
|
||||
primitives.push(prim);
|
||||
}
|
||||
}
|
||||
|
||||
primitives
|
||||
}
|
||||
|
|
@ -683,7 +965,7 @@ impl BasicScene {
|
|||
&self,
|
||||
_arena: &mut Arena,
|
||||
_entities: &[AnimatedShapeSceneEntity],
|
||||
_loaded: Vec<Vec<Shape>>,
|
||||
_loaded: Vec<Ptr<Shape>>,
|
||||
_lookup: &SceneLookup,
|
||||
) -> Vec<Primitive> {
|
||||
// TODO: implement animated shape upload
|
||||
|
|
@ -779,26 +1061,18 @@ impl BasicScene {
|
|||
}
|
||||
|
||||
fn get_alpha_texture(
|
||||
&self,
|
||||
params: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
textures: &HashMap<String, Arc<FloatTexture>>,
|
||||
) -> Option<Arc<FloatTexture>> {
|
||||
let name = params.get_texture("alpha");
|
||||
if name.is_empty() {
|
||||
return None;
|
||||
}
|
||||
match textures.get(&name) {
|
||||
Some(tex) => Some(tex.clone()),
|
||||
None => panic!("{:?}: Alpha texture '{}' not found", loc, name),
|
||||
}
|
||||
// } else {
|
||||
// let alpha_val = params.get_one_float("alpha", 1.0);
|
||||
// if alpha_val < 1.0 {
|
||||
// Some(Arc::new(FloatTexture::Constant(FloatConstantTexture::new(
|
||||
// alpha_val,
|
||||
// ))))
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn get_medium(&self, name: &str, loc: &FileLoc) -> Option<Arc<Medium>> {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use super::{SceneEntity, TextureSceneEntity, LightSceneEntity};
|
||||
use super::{LightSceneEntity, SceneEntity, TextureSceneEntity};
|
||||
use crate::core::image::Image;
|
||||
use crate::core::texture::{FloatTexture, SpectrumTexture};
|
||||
use crate::utils::parallel::AsyncJob;
|
||||
use anyhow::Result;
|
||||
use shared::core::light::Light;
|
||||
use shared::core::medium::Medium;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use crate::core::texture::FloatTexture;
|
||||
use crate::shapes::{BilinearPatchMesh, TriangleMesh};
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||
use anyhow::{Result, anyhow};
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary, resolve_filename};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use parking_lot::Mutex;
|
||||
use shared::core::shape::*;
|
||||
use shared::Ptr;
|
||||
use shared::shapes::*;
|
||||
use shared::utils::Transform;
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -12,6 +13,13 @@ use std::sync::Arc;
|
|||
pub static ALL_TRIANGLE_MESHES: Mutex<Vec<Arc<TriangleMesh>>> = Mutex::new(Vec::new());
|
||||
pub static ALL_BILINEAR_MESHES: Mutex<Vec<Arc<BilinearPatchMesh>>> = Mutex::new(Vec::new());
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShapeWithContext {
|
||||
pub shape: Ptr<Shape>,
|
||||
pub entity_index: usize,
|
||||
pub shape_index: usize,
|
||||
}
|
||||
|
||||
pub trait CreateShape {
|
||||
fn create(
|
||||
render_from_object: Transform,
|
||||
|
|
@ -21,7 +29,7 @@ pub trait CreateShape {
|
|||
float_textures: &HashMap<String, Arc<FloatTexture>>,
|
||||
loc: FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Vec<Shape>>;
|
||||
) -> Result<Vec<Ptr<Shape>>>;
|
||||
}
|
||||
|
||||
pub trait ShapeFactory {
|
||||
|
|
@ -34,7 +42,7 @@ pub trait ShapeFactory {
|
|||
float_textures: &HashMap<String, Arc<FloatTexture>>,
|
||||
loc: FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Vec<Shape>>;
|
||||
) -> Result<Vec<Ptr<Shape>>>;
|
||||
}
|
||||
|
||||
impl ShapeFactory for Shape {
|
||||
|
|
@ -47,7 +55,7 @@ impl ShapeFactory for Shape {
|
|||
float_textures: &HashMap<String, Arc<FloatTexture>>,
|
||||
loc: FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Vec<Shape>> {
|
||||
) -> Result<Vec<Ptr<Shape>>> {
|
||||
match name {
|
||||
"sphere" => SphereShape::create(
|
||||
render_from_object,
|
||||
|
|
@ -95,20 +103,32 @@ impl ShapeFactory for Shape {
|
|||
arena,
|
||||
),
|
||||
"plymesh" => {
|
||||
// let filename = resolve_filename(¶meters.get_one_string("filename", ""));
|
||||
// let ply_mesh = TriQuadMesh::read_ply(filename);
|
||||
// let mut edge_length = parameters.get_one_float("edgelength", 1.);
|
||||
// edge_length *= get_options().displacement_edge_scale;
|
||||
// let displacement_tex_name = parameters.get_texture("displacement");
|
||||
TriangleShape::create(
|
||||
render_from_object,
|
||||
object_from_render,
|
||||
reverse_orientation,
|
||||
parameters,
|
||||
float_textures,
|
||||
loc,
|
||||
arena,
|
||||
)
|
||||
let filename = resolve_filename(¶meters.get_one_string("filename", "")?);
|
||||
if filename.is_empty() {
|
||||
bail!("{}: plymesh requires \"filename\" parameter", loc);
|
||||
}
|
||||
|
||||
let tri_mesh =
|
||||
TriangleMesh::from_ply(&filename, &render_from_object, reverse_orientation)?;
|
||||
|
||||
let host_arc = Arc::new(tri_mesh);
|
||||
let mut global_store = ALL_TRIANGLE_MESHES.lock();
|
||||
global_store.push(host_arc.clone());
|
||||
drop(global_store);
|
||||
|
||||
let n_tris = host_arc.device.n_triangles;
|
||||
let mesh_ptr = Ptr::from(&host_arc.device);
|
||||
let shapes: Vec<Ptr<Shape>> = (0..n_tris)
|
||||
.map(|i| {
|
||||
let tri_shape = Shape::Triangle(TriangleShape {
|
||||
mesh: mesh_ptr,
|
||||
tri_index: i as i32,
|
||||
});
|
||||
arena.alloc(tri_shape)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(shapes)
|
||||
}
|
||||
_ => Err(anyhow!("Unknown shape name")),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,12 @@ pub enum FloatTexture {
|
|||
Wrinkled(WrinkledTexture),
|
||||
}
|
||||
|
||||
impl Default for FloatTexture {
|
||||
fn default() -> Self {
|
||||
FloatTexture::Constant(FloatConstantTexture::new(1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl FloatTextureTrait for Arc<FloatTexture> {
|
||||
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||
self.as_ref().evaluate(ctx)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::*;
|
||||
use crate::Arena;
|
||||
use crate::core::film::{CreateFilmBase, PixelSensor};
|
||||
use crate::utils::containers::Array2D;
|
||||
use crate::Arena;
|
||||
use anyhow::Result;
|
||||
use shared::core::camera::CameraTransform;
|
||||
use shared::core::film::{Film, FilmBase, RGBFilm, RGBPixel};
|
||||
|
|
@ -69,7 +69,7 @@ impl CreateFilm for RGBFilm {
|
|||
loc: &FileLoc,
|
||||
_arena: &Arena,
|
||||
) -> Result<Film> {
|
||||
let colorspace = params.color_space.as_ref().unwrap();
|
||||
let colorspace = params.color_space.as_ref().cloned().unwrap_or_else(crate::spectra::default_colorspace_arc);
|
||||
let max_component_value = params.get_one_float("maxcomponentvalue", Float::INFINITY)?;
|
||||
let write_fp16 = params.get_one_bool("savefp16", true)?;
|
||||
let sensor = PixelSensor::create(params, colorspace.clone(), exposure_time, loc)?;
|
||||
|
|
|
|||
|
|
@ -5,27 +5,26 @@ use shared::core::geometry::{Point2f, Vector2f};
|
|||
use shared::filters::GaussianFilter;
|
||||
use shared::utils::math::gaussian;
|
||||
|
||||
pub trait GaussianFilterCreator {
|
||||
fn new(radius: Vector2f, sigma: Float) -> Self;
|
||||
#[derive(Clone, Debug, Device)]
|
||||
#[device(name = "GaussianFilter")]
|
||||
pub struct GaussianFilterHost {
|
||||
pub radius: Vector2f,
|
||||
pub sigma: Float,
|
||||
pub exp_x: Float,
|
||||
pub exp_y: Float,
|
||||
#[device(flatten)]
|
||||
pub sampler: FilterSampler,
|
||||
}
|
||||
|
||||
impl GaussianFilterCreator for GaussianFilter {
|
||||
fn new(radius: Vector2f, sigma: Float) -> Self {
|
||||
let exp_x = gaussian(radius.x(), 0., sigma);
|
||||
let exp_y = gaussian(radius.y(), 0., sigma);
|
||||
|
||||
impl GaussianFilterHost {
|
||||
pub fn new(radius: Vector2f, sigma: Float) -> Self {
|
||||
let exp_x = gaussian(radius.x(), 0.0, sigma);
|
||||
let exp_y = gaussian(radius.y(), 0.0, sigma);
|
||||
let sampler = FilterSampler::new(radius, move |p: Point2f| {
|
||||
let gx = (gaussian(p.x(), 0., sigma) - exp_x).max(0.0);
|
||||
let gy = (gaussian(p.y(), 0., sigma) - exp_y).max(0.0);
|
||||
let gx = (gaussian(p.x(), 0.0, sigma) - exp_x).max(0.0);
|
||||
let gy = (gaussian(p.y(), 0.0, sigma) - exp_y).max(0.0);
|
||||
gx * gy
|
||||
});
|
||||
|
||||
Self {
|
||||
radius,
|
||||
sigma,
|
||||
exp_x: gaussian(radius.x(), 0., sigma),
|
||||
exp_y: gaussian(radius.y(), 0., sigma),
|
||||
sampler,
|
||||
}
|
||||
Self { radius, sigma, exp_x, exp_y, sampler }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,43 +6,35 @@ use shared::core::geometry::{Point2f, Vector2f};
|
|||
use shared::filters::LanczosSincFilter;
|
||||
use shared::utils::math::{lerp, windowed_sinc};
|
||||
|
||||
pub trait LanczosFilterCreator {
|
||||
fn new(radius: Vector2f, tau: Float) -> Self;
|
||||
#[derive(Clone, Debug, Device)]
|
||||
#[device(name = "LanczosSincFilter")]
|
||||
pub struct LanczosSincFilterHost {
|
||||
pub radius: Vector2f,
|
||||
pub tau: Float,
|
||||
#[device(flatten)]
|
||||
pub sampler: FilterSampler,
|
||||
}
|
||||
|
||||
impl LanczosFilterCreator for LanczosSincFilter {
|
||||
fn new(radius: Vector2f, tau: Float) -> Self {
|
||||
let evaluate = |p: Point2f| -> Float {
|
||||
impl LanczosSincFilterHost {
|
||||
pub fn new(radius: Vector2f, tau: Float) -> Self {
|
||||
let sampler = FilterSampler::new(radius, move |p: Point2f| {
|
||||
windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau)
|
||||
};
|
||||
});
|
||||
Self { radius, tau, sampler }
|
||||
}
|
||||
}
|
||||
|
||||
let sampler = FilterSampler::new(radius, evaluate);
|
||||
let sqrt_samples = 64;
|
||||
let n_samples = sqrt_samples * sqrt_samples;
|
||||
let area = (2.0 * radius.x()) * (2.0 * radius.y());
|
||||
let mut sum = 0.0;
|
||||
let mut rng = rand::rng();
|
||||
|
||||
for y in 0..sqrt_samples {
|
||||
for x in 0..sqrt_samples {
|
||||
let u = Point2f::new(
|
||||
(x as Float + rng.random::<Float>()) / sqrt_samples as Float,
|
||||
(y as Float + rng.random::<Float>()) / sqrt_samples as Float,
|
||||
);
|
||||
let p = Point2f::new(
|
||||
lerp(u.x(), -radius.x(), radius.x()),
|
||||
lerp(u.y(), -radius.y(), radius.y()),
|
||||
);
|
||||
sum += evaluate(p);
|
||||
}
|
||||
}
|
||||
let integral = sum / n_samples as Float * area;
|
||||
|
||||
Self {
|
||||
radius,
|
||||
tau,
|
||||
sampler,
|
||||
integral,
|
||||
}
|
||||
fn windowed_sinc(x: Float, radius: Float, tau: Float) -> Float {
|
||||
use std::f32::consts::PI;
|
||||
let x = x.abs();
|
||||
if x > radius {
|
||||
return 0.0;
|
||||
}
|
||||
if x < 1e-5 {
|
||||
1.0
|
||||
} else {
|
||||
let xpi = x * PI;
|
||||
let xpit = xpi * tau;
|
||||
(xpi.sin() / xpi) * (xpit.sin() / xpit)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,23 +4,39 @@ use shared::core::filter::FilterSampler;
|
|||
use shared::core::geometry::{Point2f, Vector2f};
|
||||
use shared::filters::MitchellFilter;
|
||||
|
||||
pub trait MitchellFilterCreator {
|
||||
fn new(radius: Vector2f, b: Float, c: Float) -> Self;
|
||||
#[derive(Clone, Debug, Device)]
|
||||
#[device(name = "MitchellFilter")]
|
||||
pub struct MitchellFilterHost {
|
||||
pub radius: Vector2f,
|
||||
pub b: Float,
|
||||
pub c: Float,
|
||||
#[device(flatten)]
|
||||
pub sampler: FilterSampler,
|
||||
}
|
||||
|
||||
impl MitchellFilterCreator for MitchellFilter {
|
||||
fn new(radius: Vector2f, b: Float, c: Float) -> Self {
|
||||
impl MitchellFilterHost {
|
||||
pub fn new(radius: Vector2f, b: Float, c: Float) -> Self {
|
||||
let sampler = FilterSampler::new(radius, move |p: Point2f| {
|
||||
let nx = 2.0 * p.x() / radius.x();
|
||||
let ny = 2.0 * p.y() / radius.y();
|
||||
Self::mitchell_1d_eval(b, c, nx) * Self::mitchell_1d_eval(b, c, ny)
|
||||
mitchell_1d(p.x() / radius.x(), b, c) * mitchell_1d(p.y() / radius.y(), b, c)
|
||||
});
|
||||
Self { radius, b, c, sampler }
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
radius,
|
||||
b,
|
||||
c,
|
||||
sampler,
|
||||
}
|
||||
fn mitchell_1d(x: Float, b: Float, c: Float) -> Float {
|
||||
let x = (2.0 * x).abs();
|
||||
if x <= 1.0 {
|
||||
((12.0 - 9.0 * b - 6.0 * c) * x * x * x
|
||||
+ (-18.0 + 12.0 * b + 6.0 * c) * x * x
|
||||
+ (6.0 - 2.0 * b))
|
||||
/ 6.0
|
||||
} else if x <= 2.0 {
|
||||
((-b - 6.0 * c) * x * x * x
|
||||
+ (6.0 * b + 30.0 * c) * x * x
|
||||
+ (-12.0 * b - 48.0 * c) * x
|
||||
+ (8.0 * b + 24.0 * c))
|
||||
/ 6.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::core::color::RGBToSpectrumTableData;
|
||||
use bytemuck::cast_slice;
|
||||
use shared::core::color::RES;
|
||||
use once_cell::sync::Lazy;
|
||||
use shared::Float;
|
||||
use shared::PBRTOptions;
|
||||
|
|
@ -20,72 +20,53 @@ pub fn get_options() -> &'static PBRTOptions {
|
|||
})
|
||||
}
|
||||
|
||||
fn aligned_cast(bytes: &[u8]) -> &[Float] {
|
||||
match bytemuck::try_cast_slice(bytes) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
let v: Vec<Float> = bytemuck::pod_collect_to_vec(bytes);
|
||||
Box::leak(v.into_boxed_slice())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static SRGB_SCALE_BYTES: &[u8] = include_bytes!("../data/srgb_scale.dat");
|
||||
static SRGB_COEFFS_BYTES: &[u8] = include_bytes!("../data/srgb_coeffs.dat");
|
||||
|
||||
pub static SRGB_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(SRGB_SCALE_BYTES));
|
||||
|
||||
pub static SRGB_COEFFS: Lazy<&[Float]> =
|
||||
Lazy::new(|| match bytemuck::try_cast_slice(SRGB_COEFFS_BYTES) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
let v: Vec<Float> = bytemuck::pod_collect_to_vec(SRGB_COEFFS_BYTES);
|
||||
Box::leak(v.into_boxed_slice())
|
||||
}
|
||||
});
|
||||
|
||||
static DCI_P3_SCALE_BYTES: &[u8] = include_bytes!("../data/dcip3_scale.dat");
|
||||
static DCI_P3_COEFFS_BYTES: &[u8] = include_bytes!("../data/dcip3_coeffs.dat");
|
||||
pub static DCI_P3_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(DCI_P3_SCALE_BYTES));
|
||||
pub static DCI_P3_COEFFS: Lazy<&[Float]> =
|
||||
Lazy::new(|| match bytemuck::try_cast_slice(DCI_P3_COEFFS_BYTES) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
let v: Vec<Float> = bytemuck::pod_collect_to_vec(DCI_P3_COEFFS_BYTES);
|
||||
Box::leak(v.into_boxed_slice())
|
||||
}
|
||||
});
|
||||
|
||||
static ACES_SCALE_BYTES: &[u8] = include_bytes!("../data/aces_scale.dat");
|
||||
static ACES_COEFFS_BYTES: &[u8] = include_bytes!("../data/aces_coeffs.dat");
|
||||
|
||||
pub static ACES_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(ACES_SCALE_BYTES));
|
||||
|
||||
pub static ACES_COEFFS: Lazy<&[Float]> =
|
||||
Lazy::new(|| match bytemuck::try_cast_slice(ACES_COEFFS_BYTES) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
let v: Vec<Float> = bytemuck::pod_collect_to_vec(ACES_COEFFS_BYTES);
|
||||
Box::leak(v.into_boxed_slice())
|
||||
}
|
||||
});
|
||||
|
||||
static REC2020_SCALE_BYTES: &[u8] = include_bytes!("../data/rec2020_scale.dat");
|
||||
static REC2020_COEFFS_BYTES: &[u8] = include_bytes!("../data/rec2020_coeffs.dat");
|
||||
|
||||
pub static REC2020_SCALE: Lazy<&[Float]> = Lazy::new(|| cast_slice(REC2020_SCALE_BYTES));
|
||||
pub static REC2020_COEFFS: Lazy<&[Float]> =
|
||||
Lazy::new(|| match bytemuck::try_cast_slice(REC2020_COEFFS_BYTES) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
let v: Vec<Float> = bytemuck::pod_collect_to_vec(REC2020_COEFFS_BYTES);
|
||||
Box::leak(v.into_boxed_slice())
|
||||
|
||||
fn strip_to_len(bytes: &[u8], expected_len: usize) -> &'static [Float] {
|
||||
let all: Vec<Float> = bytemuck::pod_collect_to_vec(bytes);
|
||||
let skip = all.len() - expected_len;
|
||||
let data = all[skip..].to_vec();
|
||||
Box::leak(data.into_boxed_slice())
|
||||
}
|
||||
});
|
||||
|
||||
const COEFFS_LEN: usize = (RES * RES * RES) as usize * 3 * 3;
|
||||
|
||||
pub static SRGB_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(SRGB_SCALE_BYTES, RES as usize));
|
||||
pub static SRGB_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(SRGB_COEFFS_BYTES, COEFFS_LEN));
|
||||
|
||||
pub static DCI_P3_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(DCI_P3_SCALE_BYTES, RES as usize));
|
||||
pub static DCI_P3_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(DCI_P3_COEFFS_BYTES, COEFFS_LEN));
|
||||
|
||||
pub static ACES_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(ACES_SCALE_BYTES, RES as usize));
|
||||
pub static ACES_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(ACES_COEFFS_BYTES, COEFFS_LEN));
|
||||
|
||||
pub static REC2020_SCALE: Lazy<&[Float]> = Lazy::new(|| strip_to_len(REC2020_SCALE_BYTES, RES as usize));
|
||||
pub static REC2020_COEFFS: Lazy<&[Float]> = Lazy::new(|| strip_to_len(REC2020_COEFFS_BYTES, COEFFS_LEN));
|
||||
|
||||
pub static SRGB_TABLE: Lazy<RGBToSpectrumTableData> =
|
||||
Lazy::new(|| RGBToSpectrumTableData::new(SRGB_SCALE.to_vec(), SRGB_COEFFS.to_vec()));
|
||||
|
||||
pub static DCI_P3_TABLE: Lazy<RGBToSpectrumTableData> =
|
||||
Lazy::new(|| RGBToSpectrumTableData::new(DCI_P3_SCALE.to_vec(), DCI_P3_COEFFS.to_vec()));
|
||||
|
||||
pub static REC2020_TABLE: Lazy<RGBToSpectrumTableData> =
|
||||
Lazy::new(|| RGBToSpectrumTableData::new(REC2020_SCALE.to_vec(), REC2020_COEFFS.to_vec()));
|
||||
|
||||
pub static ACES_TABLE: Lazy<RGBToSpectrumTableData> =
|
||||
Lazy::new(|| RGBToSpectrumTableData::new(ACES_SCALE.to_vec(), ACES_COEFFS.to_vec()));
|
||||
|
||||
// pub static ACES_TABLE: Lazy<RGBToSpectrumTableData> = Lazy::new(|| {
|
||||
// RGBToSpectrumTableData::load(Path::new("data/"), "aces2065_1")
|
||||
// .expect("Failed to load ACES table")
|
||||
// });
|
||||
|
|
|
|||
|
|
@ -23,20 +23,52 @@ pub static UC_RHO: [Float; N_RHO_SAMPLES] = [
|
|||
];
|
||||
|
||||
pub static U_RHO: [Point2f; N_RHO_SAMPLES] = [
|
||||
Point2f { 0: [0.855985, 0.570367]},
|
||||
Point2f { 0: [0.381823, 0.851844]},
|
||||
Point2f { 0: [0.285328, 0.764262]},
|
||||
Point2f { 0: [0.733380, 0.114073]},
|
||||
Point2f { 0: [0.542663, 0.344465]},
|
||||
Point2f { 0: [0.127274, 0.414848]},
|
||||
Point2f { 0: [0.964700, 0.947162]},
|
||||
Point2f { 0: [0.594089, 0.643463]},
|
||||
Point2f { 0: [0.095109, 0.170369]},
|
||||
Point2f { 0: [0.825444, 0.263359]},
|
||||
Point2f { 0: [0.429467, 0.454469]},
|
||||
Point2f { 0: [0.244460, 0.816459]},
|
||||
Point2f { 0: [0.756135, 0.731258]},
|
||||
Point2f { 0: [0.516165, 0.152852]},
|
||||
Point2f { 0: [0.180888, 0.214174]},
|
||||
Point2f { 0: [0.898579, 0.503897]},
|
||||
Point2f {
|
||||
0: [0.855985, 0.570367],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.381823, 0.851844],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.285328, 0.764262],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.733380, 0.114073],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.542663, 0.344465],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.127274, 0.414848],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.964700, 0.947162],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.594089, 0.643463],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.095109, 0.170369],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.825444, 0.263359],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.429467, 0.454469],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.244460, 0.816459],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.756135, 0.731258],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.516165, 0.152852],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.180888, 0.214174],
|
||||
},
|
||||
Point2f {
|
||||
0: [0.898579, 0.503897],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
mod base;
|
||||
mod constants;
|
||||
mod path;
|
||||
mod pipeline;
|
||||
mod state;
|
||||
pub mod base;
|
||||
pub mod constants;
|
||||
pub mod path;
|
||||
pub mod pipeline;
|
||||
pub mod state;
|
||||
|
||||
pub use path::PathIntegrator;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use super::RayIntegratorTrait;
|
||||
use super::base::IntegratorBase;
|
||||
use super::constants::*;
|
||||
use super::state::PathState;
|
||||
use super::RayIntegratorTrait;
|
||||
use crate::core::interaction::InteractionGetter;
|
||||
use crate::Arena;
|
||||
use shared::core::bsdf::{BSDFSample, BSDF};
|
||||
use crate::core::interaction::InteractionGetter;
|
||||
use shared::core::bsdf::{BSDF, BSDFSample};
|
||||
use shared::core::bxdf::{BxDFFlags, FArgs, TransportMode};
|
||||
use shared::core::camera::Camera;
|
||||
use shared::core::film::VisibleSurface;
|
||||
|
|
@ -64,7 +64,7 @@ impl PathConfig {
|
|||
}
|
||||
|
||||
pub struct PathIntegrator {
|
||||
base: IntegratorBase,
|
||||
pub base: IntegratorBase,
|
||||
camera: Arc<Camera>,
|
||||
sampler: LightSampler,
|
||||
config: PathConfig,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::core::image::{Image, ImageIO};
|
||||
use crate::core::light::{CreateLight, lookup_spectrum};
|
||||
use crate::core::light::lookup_spectrum;
|
||||
use crate::core::spectrum::spectrum_to_photometric;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename};
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::light::{Light, LightBase, LightType};
|
||||
use shared::core::medium::Medium;
|
||||
use shared::core::medium::{Medium, MediumInterface};
|
||||
use shared::core::shape::{Shape, ShapeTrait};
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::GPUFloatTexture;
|
||||
|
|
@ -18,10 +18,9 @@ use shared::spectra::RGBColorSpace;
|
|||
use shared::utils::{Ptr, Transform};
|
||||
use shared::{Float, PI};
|
||||
|
||||
impl CreateLight for DiffuseAreaLight {
|
||||
fn create(
|
||||
pub fn create(
|
||||
render_from_light: Transform,
|
||||
medium: Medium,
|
||||
medium: Option<Medium>,
|
||||
params: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
shape: &Shape,
|
||||
|
|
@ -30,7 +29,9 @@ impl CreateLight for DiffuseAreaLight {
|
|||
arena: &Arena,
|
||||
) -> Result<Light> {
|
||||
let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant);
|
||||
let illum_spec = Spectrum::Dense(colorspace.unwrap().illuminant);
|
||||
let default_cs = crate::spectra::default_colorspace();
|
||||
let cs = colorspace.unwrap_or(&default_cs);
|
||||
let illum_spec = Spectrum::Dense(cs.illuminant);
|
||||
let mut scale = params.get_one_float("scale", 1.)?;
|
||||
let two_sided = params.get_one_bool("twosided", false)?;
|
||||
|
||||
|
|
@ -115,7 +116,18 @@ impl CreateLight for DiffuseAreaLight {
|
|||
(LightType::Area, Some(alpha))
|
||||
};
|
||||
|
||||
let base = LightBase::new(light_type, render_from_light, medium.into());
|
||||
let mi = match medium {
|
||||
Some(m) => {
|
||||
let ptr = arena.alloc(m);
|
||||
MediumInterface {
|
||||
inside: ptr,
|
||||
outside: ptr,
|
||||
}
|
||||
}
|
||||
None => MediumInterface::default(),
|
||||
};
|
||||
let base = LightBase::new(light_type, render_from_light, mi);
|
||||
|
||||
if let Some(ref img) = image {
|
||||
let desc = img
|
||||
.get_channel_desc(&["R", "G", "B"])
|
||||
|
|
@ -128,8 +140,7 @@ impl CreateLight for DiffuseAreaLight {
|
|||
);
|
||||
}
|
||||
|
||||
let is_triangle_or_bilinear =
|
||||
matches!(*shape, Shape::Triangle(_) | Shape::BilinearPatch(_));
|
||||
let is_triangle_or_bilinear = matches!(*shape, Shape::Triangle(_) | Shape::BilinearPatch(_));
|
||||
if render_from_light.has_scale(None) && !is_triangle_or_bilinear {
|
||||
println!(
|
||||
"Scaling detected in rendering to light space transformation! \
|
||||
|
|
@ -161,4 +172,3 @@ impl CreateLight for DiffuseAreaLight {
|
|||
|
||||
Ok(Light::DiffuseArea(specific))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::light::{CreateLight, lookup_spectrum};
|
||||
use crate::core::light::lookup_spectrum;
|
||||
use crate::core::spectrum::spectrum_to_photometric;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||
|
|
@ -36,10 +36,9 @@ impl CreateDistantLight for DistantLight {
|
|||
}
|
||||
}
|
||||
|
||||
impl CreateLight for DistantLight {
|
||||
fn create(
|
||||
pub fn create(
|
||||
render_from_light: Transform,
|
||||
_medium: Medium,
|
||||
_medium: Option<Medium>,
|
||||
parameters: &ParameterDictionary,
|
||||
_loc: &FileLoc,
|
||||
_shape: &Shape,
|
||||
|
|
@ -47,10 +46,12 @@ impl CreateLight for DistantLight {
|
|||
colorspace: Option<&RGBColorSpace>,
|
||||
_arena: &Arena,
|
||||
) -> Result<Light> {
|
||||
let default_cs = crate::spectra::default_colorspace();
|
||||
let cs = colorspace.unwrap_or(&default_cs);
|
||||
let l = parameters
|
||||
.get_one_spectrum(
|
||||
"L",
|
||||
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
||||
Some(Spectrum::Dense(cs.illuminant)),
|
||||
SpectrumType::Illuminant,
|
||||
)
|
||||
.unwrap();
|
||||
|
|
@ -91,4 +92,3 @@ impl CreateLight for DistantLight {
|
|||
|
||||
Ok(Light::Distant(specific))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::core::image::{Image, ImageIO};
|
||||
use crate::core::light::{CreateLight, lookup_spectrum};
|
||||
use crate::core::light::lookup_spectrum;
|
||||
use crate::core::spectrum::spectrum_to_photometric;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
|
|
@ -9,7 +9,7 @@ use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename
|
|||
use anyhow::{Result, anyhow};
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::light::{Light, LightBase, LightType};
|
||||
use shared::core::medium::Medium;
|
||||
use shared::core::medium::{Medium, MediumInterface};
|
||||
use shared::core::shape::Shape;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::SpectrumType;
|
||||
|
|
@ -18,10 +18,9 @@ use shared::spectra::RGBColorSpace;
|
|||
use shared::utils::{Ptr, Transform};
|
||||
use shared::{Float, PI};
|
||||
|
||||
impl CreateLight for GoniometricLight {
|
||||
fn create(
|
||||
pub fn create(
|
||||
render_from_light: Transform,
|
||||
medium: Medium,
|
||||
medium: Option<Medium>,
|
||||
params: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
_shape: &Shape,
|
||||
|
|
@ -29,10 +28,13 @@ impl CreateLight for GoniometricLight {
|
|||
colorspace: Option<&RGBColorSpace>,
|
||||
arena: &Arena,
|
||||
) -> Result<Light> {
|
||||
|
||||
let default_cs = crate::spectra::default_colorspace();
|
||||
let cs = colorspace.unwrap_or(&default_cs);
|
||||
let i = params
|
||||
.get_one_spectrum(
|
||||
"I",
|
||||
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
||||
Some(Spectrum::Dense(cs.illuminant)),
|
||||
SpectrumType::Illuminant,
|
||||
)
|
||||
.expect("Could not retrieve spectrum");
|
||||
|
|
@ -76,15 +78,21 @@ impl CreateLight for GoniometricLight {
|
|||
let swap_yz: [Float; 16] = [
|
||||
1., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 1.,
|
||||
];
|
||||
let t = Transform::from_flat(&swap_yz)
|
||||
.expect("Could not create transform for GoniometricLight");
|
||||
let t =
|
||||
Transform::from_flat(&swap_yz).expect("Could not create transform for GoniometricLight");
|
||||
let final_render_from_light = render_from_light * t;
|
||||
|
||||
let base = LightBase::new(
|
||||
LightType::DeltaPosition,
|
||||
final_render_from_light,
|
||||
medium.into(),
|
||||
);
|
||||
let mi = match medium {
|
||||
Some(m) => {
|
||||
let ptr = arena.alloc(m);
|
||||
MediumInterface {
|
||||
inside: ptr,
|
||||
outside: ptr,
|
||||
}
|
||||
}
|
||||
None => MediumInterface::default(),
|
||||
};
|
||||
let base = LightBase::new(LightType::DeltaPosition, render_from_light, mi);
|
||||
|
||||
let iemit = lookup_spectrum(&i);
|
||||
|
||||
|
|
@ -107,7 +115,6 @@ impl CreateLight for GoniometricLight {
|
|||
|
||||
Ok(Light::Goniometric(specific))
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_luminance_image(image: &Image, filename: &str, loc: &FileLoc) -> Result<Image> {
|
||||
let res = image.resolution();
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
use crate::Arena;
|
||||
use crate::core::image::{Image, ImageIO};
|
||||
use crate::core::light::lookup_spectrum;
|
||||
use crate::core::spectrum::spectrum_to_photometric;
|
||||
use crate::spectra::get_spectra_context;
|
||||
use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D};
|
||||
use crate::utils::{FileLoc, ParameterDictionary, Upload, resolve_filename};
|
||||
use anyhow::{Result, anyhow};
|
||||
use crate::utils::{resolve_filename, FileLoc, ParameterDictionary, Upload};
|
||||
use crate::Arena;
|
||||
use anyhow::{anyhow, Result};
|
||||
use rayon::prelude::*;
|
||||
use shared::core::camera::CameraTransform;
|
||||
use shared::core::geometry::{Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike, cos_theta};
|
||||
use shared::core::geometry::{cos_theta, Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike};
|
||||
use shared::core::image::{DeviceImage, WrapMode};
|
||||
use shared::core::light::{Light, LightBase, LightType};
|
||||
use shared::core::medium::MediumInterface;
|
||||
use shared::core::medium::{Medium, MediumInterface};
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::SpectrumType;
|
||||
use shared::lights::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLight};
|
||||
|
|
@ -124,13 +124,15 @@ impl CreateUniformInfiniteLight for UniformInfiniteLight {
|
|||
|
||||
pub fn create(
|
||||
render_from_light: Transform,
|
||||
_medium: MediumInterface,
|
||||
_medium: Option<Medium>,
|
||||
camera_transform: CameraTransform,
|
||||
parameters: &ParameterDictionary,
|
||||
colorspace: Option<&RGBColorSpace>,
|
||||
loc: &FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Light> {
|
||||
let default_cs = crate::spectra::default_colorspace();
|
||||
let cs = colorspace.unwrap_or(&default_cs);
|
||||
let l = parameters.get_spectrum_array("L", SpectrumType::Illuminant);
|
||||
let mut scale = parameters.get_one_float("scale", 1.0)?;
|
||||
let portal = parameters.get_point3f_array("portal")?;
|
||||
|
|
@ -154,7 +156,7 @@ pub fn create(
|
|||
scale /= spectrum_to_photometric(l[0]);
|
||||
l[0]
|
||||
} else {
|
||||
Spectrum::Dense(colorspace.unwrap().illuminant)
|
||||
Spectrum::Dense(cs.illuminant)
|
||||
};
|
||||
|
||||
if e_v > 0.0 {
|
||||
|
|
@ -167,7 +169,7 @@ pub fn create(
|
|||
}
|
||||
|
||||
// Image-based lights
|
||||
let (image, image_cs) = load_image(&filename, &l, colorspace.unwrap(), loc)?;
|
||||
let (image, image_cs) = load_image(&filename, &l, cs, loc)?;
|
||||
|
||||
scale /= spectrum_to_photometric(Spectrum::Dense(image_cs.illuminant));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::light::{CreateLight, lookup_spectrum};
|
||||
use crate::core::light::lookup_spectrum;
|
||||
use crate::core::spectrum::spectrum_to_photometric;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||
|
|
@ -42,21 +42,23 @@ impl CreatePointLight for PointLight {
|
|||
}
|
||||
}
|
||||
|
||||
impl CreateLight for PointLight {
|
||||
fn create(
|
||||
pub fn create(
|
||||
render_from_light: Transform,
|
||||
medium: Medium,
|
||||
medium: Option<Medium>,
|
||||
parameters: &ParameterDictionary,
|
||||
_loc: &FileLoc,
|
||||
_shape: &Shape,
|
||||
_alpha: &FloatTexture,
|
||||
colorspace: Option<&RGBColorSpace>,
|
||||
_arena: &Arena,
|
||||
arena: &Arena,
|
||||
) -> Result<Light> {
|
||||
let default_cs = crate::spectra::default_colorspace();
|
||||
let cs = colorspace.unwrap_or(&default_cs);
|
||||
|
||||
let l = parameters
|
||||
.get_one_spectrum(
|
||||
"L",
|
||||
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
||||
Some(Spectrum::Dense(cs.illuminant)),
|
||||
SpectrumType::Illuminant,
|
||||
)
|
||||
.unwrap();
|
||||
|
|
@ -71,7 +73,18 @@ impl CreateLight for PointLight {
|
|||
let from = parameters.get_one_point3f("from", Point3f::zero())?;
|
||||
let tf = Transform::translate(from.into());
|
||||
let final_render = render_from_light * tf;
|
||||
let specific = PointLight::new(final_render, medium.into(), l, scale);
|
||||
|
||||
let mi = match medium {
|
||||
Some(m) => {
|
||||
let ptr = arena.alloc(m);
|
||||
MediumInterface {
|
||||
inside: ptr,
|
||||
outside: ptr,
|
||||
}
|
||||
}
|
||||
None => MediumInterface::default(),
|
||||
};
|
||||
|
||||
let specific = PointLight::new(final_render, mi, l, scale);
|
||||
Ok(Light::Point(specific))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use crate::core::image::{Image, ImageIO};
|
||||
use crate::core::light::CreateLight;
|
||||
use crate::core::spectrum::spectrum_to_photometric;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
|
|
@ -10,7 +9,7 @@ use shared::core::geometry::{
|
|||
Bounds2f, Point2f, Point2i, Point3f, Vector3f, VectorLike, cos_theta,
|
||||
};
|
||||
use shared::core::light::{Light, LightBase, LightType};
|
||||
use shared::core::medium::Medium;
|
||||
use shared::core::medium::{Medium, MediumInterface};
|
||||
use shared::core::shape::Shape;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::lights::ProjectionLight;
|
||||
|
|
@ -19,10 +18,9 @@ use shared::utils::Transform;
|
|||
use shared::utils::math::{radians, square};
|
||||
use std::path::Path;
|
||||
|
||||
impl CreateLight for ProjectionLight {
|
||||
fn create(
|
||||
pub fn create(
|
||||
render_from_light: Transform,
|
||||
medium: Medium,
|
||||
medium: Option<Medium>,
|
||||
parameters: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
_shape: &Shape,
|
||||
|
|
@ -81,7 +79,17 @@ impl CreateLight for ProjectionLight {
|
|||
let flip = Transform::scale(1., -1., 1.);
|
||||
let render_from_light_flip = render_from_light * flip;
|
||||
|
||||
let base = LightBase::new(LightType::DeltaPosition, render_from_light, medium.into());
|
||||
let mi = match medium {
|
||||
Some(m) => {
|
||||
let ptr = arena.alloc(m);
|
||||
MediumInterface {
|
||||
inside: ptr,
|
||||
outside: ptr,
|
||||
}
|
||||
}
|
||||
None => MediumInterface::default(),
|
||||
};
|
||||
let base = LightBase::new(LightType::DeltaPosition, render_from_light, mi);
|
||||
|
||||
let opposite = (radians(fov) / 2.0).tan();
|
||||
let res = image.resolution();
|
||||
|
|
@ -102,8 +110,7 @@ impl CreateLight for ProjectionLight {
|
|||
let light_from_screen = screen_from_light.inverse();
|
||||
|
||||
let dwda = |p: Point2f| {
|
||||
let w =
|
||||
Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.0)));
|
||||
let w = Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.0)));
|
||||
cos_theta(w.normalize()).powi(3)
|
||||
};
|
||||
|
||||
|
|
@ -130,7 +137,6 @@ impl CreateLight for ProjectionLight {
|
|||
|
||||
Ok(Light::Projection(specific))
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_screen_bounds(aspect: Float) -> Bounds2f {
|
||||
if aspect > 1.0 {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use crate::utils::sampling::AliasTableHost;
|
||||
use crate::Arena;
|
||||
use shared::core::light::{Light, LightTrait};
|
||||
use shared::lights::sampler::PowerLightSampler;
|
||||
use shared::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -44,4 +46,16 @@ impl PowerSamplerHost {
|
|||
alias_table,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_device(&self, arena: &Arena) -> PowerLightSampler {
|
||||
let device_lights: Vec<Light> = self.lights.iter().map(|l| (**l).clone()).collect();
|
||||
let (lights_ptr, _) = arena.alloc_slice(&device_lights);
|
||||
let alias_device = self.alias_table.to_device(arena);
|
||||
|
||||
PowerLightSampler {
|
||||
lights: lights_ptr,
|
||||
lights_len: self.lights.len() as u32,
|
||||
alias_table: alias_device,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::light::{CreateLight, lookup_spectrum};
|
||||
use crate::core::light::lookup_spectrum;
|
||||
use crate::core::spectrum::spectrum_to_photometric;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||
|
|
@ -53,10 +53,9 @@ impl CreateSpotLight for SpotLight {
|
|||
}
|
||||
}
|
||||
|
||||
impl CreateLight for SpotLight {
|
||||
fn create(
|
||||
pub fn create(
|
||||
render_from_light: Transform,
|
||||
medium: Medium,
|
||||
medium: Option<Medium>,
|
||||
parameters: &ParameterDictionary,
|
||||
_loc: &FileLoc,
|
||||
_shape: &Shape,
|
||||
|
|
@ -64,10 +63,12 @@ impl CreateLight for SpotLight {
|
|||
colorspace: Option<&RGBColorSpace>,
|
||||
arena: &Arena,
|
||||
) -> Result<Light> {
|
||||
let default_cs = crate::spectra::default_colorspace();
|
||||
let cs = colorspace.unwrap_or(&default_cs);
|
||||
let i = parameters
|
||||
.get_one_spectrum(
|
||||
"I",
|
||||
Some(Spectrum::Dense(colorspace.unwrap().illuminant)),
|
||||
Some(Spectrum::Dense(cs.illuminant)),
|
||||
SpectrumType::Illuminant,
|
||||
)
|
||||
.expect("No spectrum");
|
||||
|
|
@ -85,20 +86,22 @@ impl CreateLight for SpotLight {
|
|||
if phi_v > 0. {
|
||||
let cos_falloff_end = radians(coneangle).cos();
|
||||
let cos_falloff_start = radians(coneangle - conedelta).cos();
|
||||
let k_e =
|
||||
2. * PI * ((1. - cos_falloff_start) + (cos_falloff_start - cos_falloff_end) / 2.);
|
||||
let k_e = 2. * PI * ((1. - cos_falloff_start) + (cos_falloff_start - cos_falloff_end) / 2.);
|
||||
scale *= phi_v / k_e;
|
||||
}
|
||||
|
||||
let specific = SpotLight::new(
|
||||
final_render,
|
||||
medium.into(),
|
||||
i,
|
||||
scale,
|
||||
coneangle,
|
||||
coneangle - conedelta,
|
||||
);
|
||||
let mi = match medium {
|
||||
Some(m) => {
|
||||
let ptr = arena.alloc(m);
|
||||
MediumInterface {
|
||||
inside: ptr,
|
||||
outside: ptr,
|
||||
}
|
||||
}
|
||||
None => MediumInterface::default(),
|
||||
};
|
||||
|
||||
let specific = SpotLight::new(final_render, mi, i, scale, coneangle, coneangle - conedelta);
|
||||
arena.alloc(specific);
|
||||
Ok(Light::Spot(specific))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -21,8 +21,8 @@ impl CreateShape for BilinearPatchShape {
|
|||
parameters: ParameterDictionary,
|
||||
_float_textures: &HashMap<String, Arc<FloatTexture>>,
|
||||
_loc: FileLoc,
|
||||
_arena: &Arena,
|
||||
) -> Result<Vec<Shape>> {
|
||||
arena: &Arena,
|
||||
) -> Result<Vec<Ptr<Shape>>> {
|
||||
let mut vertex_indices = parameters.get_int_array("indices")?;
|
||||
let p = parameters.get_point3f_array("P")?;
|
||||
let mut uv = parameters.get_point2f_array("uv")?;
|
||||
|
|
@ -121,14 +121,13 @@ impl CreateShape for BilinearPatchShape {
|
|||
let mesh_ptr = Ptr::from(&host_arc.device);
|
||||
let mut shapes = Vec::with_capacity(n_patches as usize);
|
||||
for i in 0..n_patches as i32 {
|
||||
shapes.push(Shape::BilinearPatch(BilinearPatchShape {
|
||||
shapes.push(arena.alloc(Shape::BilinearPatch(BilinearPatchShape {
|
||||
mesh: mesh_ptr,
|
||||
blp_index: i,
|
||||
area: 0.0,
|
||||
rectangle: false,
|
||||
}));
|
||||
})));
|
||||
}
|
||||
// arena.alloc(shapes);
|
||||
Ok(shapes)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use shared::utils::math::lerp;
|
|||
use shared::utils::splines::{
|
||||
cubic_bspline_to_bezier, elevate_quadratic_bezier_to_cubic, quadratic_bspline_to_bezier,
|
||||
};
|
||||
use shared::Ptr;
|
||||
|
||||
use log::warn;
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -27,7 +28,8 @@ pub fn create_curve(
|
|||
curve_type: CurveType,
|
||||
seg_normals: &[Normal3f],
|
||||
split_depth: usize,
|
||||
) -> Vec<Shape> {
|
||||
arena: &Arena,
|
||||
) -> Vec<Ptr<Shape>> {
|
||||
let curve_common = CurveCommon::new(
|
||||
seg_cp_bezier,
|
||||
w0,
|
||||
|
|
@ -39,7 +41,7 @@ pub fn create_curve(
|
|||
reverse_orientation,
|
||||
);
|
||||
let n_segments = 1 << split_depth;
|
||||
let mut segments: Vec<Shape> = Vec::with_capacity(n_segments);
|
||||
let mut segments: Vec<Ptr<Shape>> = Vec::with_capacity(n_segments);
|
||||
|
||||
for i in 0..n_segments {
|
||||
let u_min = i as Float / n_segments as Float;
|
||||
|
|
@ -51,7 +53,7 @@ pub fn create_curve(
|
|||
u_max,
|
||||
};
|
||||
|
||||
segments.push(Shape::Curve(curve));
|
||||
segments.push(arena.alloc(Shape::Curve(curve)));
|
||||
}
|
||||
segments
|
||||
}
|
||||
|
|
@ -64,8 +66,8 @@ impl CreateShape for CurveShape {
|
|||
parameters: ParameterDictionary,
|
||||
_float_textures: &HashMap<String, Arc<FloatTexture>>,
|
||||
_loc: FileLoc,
|
||||
_arena: &Arena,
|
||||
) -> Result<Vec<Shape>> {
|
||||
arena: &Arena,
|
||||
) -> Result<Vec<Ptr<Shape>>> {
|
||||
let width = parameters.get_one_float("width", 1.0)?;
|
||||
let width0 = parameters.get_one_float("width0", width)?;
|
||||
let width1 = parameters.get_one_float("width1", width)?;
|
||||
|
|
@ -150,7 +152,7 @@ impl CreateShape for CurveShape {
|
|||
parameters.get_one_int("splitdepth", 3)?
|
||||
};
|
||||
|
||||
let mut curves: Vec<Shape> = Vec::new();
|
||||
let mut curves: Vec<Ptr<Shape>> = Vec::new();
|
||||
let mut cp_offset = 0;
|
||||
|
||||
for seg in 0..n_segments {
|
||||
|
|
@ -197,11 +199,11 @@ impl CreateShape for CurveShape {
|
|||
curve_type,
|
||||
seg_normals.expect("Could not determine normals to curve segments"),
|
||||
split_depth.try_into().unwrap(),
|
||||
arena
|
||||
);
|
||||
|
||||
curves.extend(new_curves);
|
||||
}
|
||||
// arena.alloc(curves);
|
||||
Ok(curves)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use shared::shapes::CylinderShape;
|
|||
use shared::utils::Transform;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use shared::Ptr;
|
||||
|
||||
impl CreateShape for CylinderShape {
|
||||
fn create(
|
||||
|
|
@ -17,7 +18,7 @@ impl CreateShape for CylinderShape {
|
|||
_float_textures: &HashMap<String, Arc<FloatTexture>>,
|
||||
_loc: FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Vec<Shape>> {
|
||||
) -> Result<Vec<Ptr<Shape>>> {
|
||||
let radius = parameters.get_one_float("radius", 1.)?;
|
||||
let z_min = parameters.get_one_float("zmin", -1.)?;
|
||||
let z_max = parameters.get_one_float("zmax", 1.)?;
|
||||
|
|
@ -32,7 +33,6 @@ impl CreateShape for CylinderShape {
|
|||
phi_max,
|
||||
);
|
||||
|
||||
arena.alloc(Shape::Cylinder(shape));
|
||||
Ok(vec![Shape::Cylinder(shape)])
|
||||
Ok(vec![arena.alloc(Shape::Cylinder(shape))])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use shared::shapes::DiskShape;
|
|||
use shared::utils::Transform;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use shared::Ptr;
|
||||
|
||||
impl CreateShape for DiskShape {
|
||||
fn create(
|
||||
|
|
@ -17,7 +18,7 @@ impl CreateShape for DiskShape {
|
|||
_float_textures: &HashMap<String, Arc<FloatTexture>>,
|
||||
_loc: FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Vec<Shape>> {
|
||||
) -> Result<Vec<Ptr<Shape>>> {
|
||||
let height = parameters.get_one_float("height", 0.)?;
|
||||
let radius = parameters.get_one_float("radius", 1.)?;
|
||||
let inner_radius = parameters.get_one_float("innerradius", 0.)?;
|
||||
|
|
@ -32,7 +33,6 @@ impl CreateShape for DiskShape {
|
|||
reverse_orientation,
|
||||
);
|
||||
|
||||
arena.alloc(Shape::Disk(shape));
|
||||
Ok(vec![Shape::Disk(shape)])
|
||||
Ok(vec![arena.alloc(Shape::Disk(shape))])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// use crate::Arena;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use anyhow::{Context, Result as AnyResult, bail};
|
||||
use anyhow::{bail, Context, Result as AnyResult};
|
||||
use ply_rs::parser::Parser;
|
||||
use ply_rs::ply::{DefaultElement, Property};
|
||||
use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector3f, VectorLike};
|
||||
|
|
@ -21,16 +21,18 @@ pub struct TriQuadMesh {
|
|||
pub quad_indices: Vec<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TriangleMeshStorage {
|
||||
pub p: Vec<Point3f>,
|
||||
pub n: Vec<Normal3f>,
|
||||
pub s: Vec<Vector3f>,
|
||||
pub uv: Vec<Point2f>,
|
||||
pub vertex_indices: Vec<i32>,
|
||||
pub face_indices: Vec<i32>,
|
||||
#[derive(DeviceRepr)]
|
||||
#[device(name = "DeviceTriangleMesh")]
|
||||
pub struct TriangleMeshStorage {
|
||||
pub vertex_indices: Vec<i32>, // → Ptr<i32> + len (always present)
|
||||
pub p: Vec<Point3f>, // → Ptr<Point3f> + len (always present)
|
||||
pub n: Vec<Normal3f>, // → Ptr<Normal3f> + len (empty → null Ptr, len 0)
|
||||
pub s: Vec<Vector3f>, // → Ptr<Vector3f> + len
|
||||
pub uv: Vec<Point2f>, // → Ptr<Point2f> + len
|
||||
pub face_indices: Vec<i32>, // → Ptr<i32> + len
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BilinearMeshStorage {
|
||||
pub vertex_indices: Vec<i32>,
|
||||
|
|
@ -299,8 +301,14 @@ impl TriQuadMesh {
|
|||
.with_context(|| format!("Couldn't open PLY file \"{}\"", filename_display))?;
|
||||
|
||||
let p = Parser::<DefaultElement>::new();
|
||||
let ply = p
|
||||
.read_ply(&mut f)
|
||||
let ply = if path.extension().and_then(|e| e.to_str()) == Some("gz") {
|
||||
let decoder = flate2::read::GzDecoder::new(f);
|
||||
let mut buf = std::io::BufReader::new(decoder);
|
||||
p.read_ply(&mut buf)
|
||||
} else {
|
||||
let mut buf = std::io::BufReader::new(f);
|
||||
p.read_ply(&mut buf)
|
||||
}
|
||||
.with_context(|| format!("Unable to read/parse PLY file \"{}\"", filename_display))?;
|
||||
|
||||
let mut mesh = TriQuadMesh::default();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use shared::shapes::SphereShape;
|
|||
use shared::utils::Transform;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use shared::Ptr;
|
||||
|
||||
impl CreateShape for SphereShape {
|
||||
fn create(
|
||||
|
|
@ -17,7 +18,7 @@ impl CreateShape for SphereShape {
|
|||
_float_textures: &HashMap<String, Arc<FloatTexture>>,
|
||||
_loc: FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Vec<Shape>> {
|
||||
) -> Result<Vec<Ptr<Shape>>> {
|
||||
let radius = parameters.get_one_float("radius", 1.)?;
|
||||
let zmin = parameters.get_one_float("zmin", -radius)?;
|
||||
let zmax = parameters.get_one_float("zmax", radius)?;
|
||||
|
|
@ -31,7 +32,6 @@ impl CreateShape for SphereShape {
|
|||
zmax,
|
||||
phimax,
|
||||
);
|
||||
arena.alloc(vec![Shape::Sphere(shape)]);
|
||||
Ok(vec![Shape::Sphere(shape)])
|
||||
Ok(vec![arena.alloc(Shape::Sphere(shape))])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ impl CreateShape for TriangleShape {
|
|||
parameters: ParameterDictionary,
|
||||
_float_texture: &HashMap<String, Arc<FloatTexture>>,
|
||||
_loc: FileLoc,
|
||||
_arena: &Arena,
|
||||
) -> Result<Vec<Shape>> {
|
||||
arena: &Arena,
|
||||
) -> Result<Vec<Ptr<Shape>>> {
|
||||
let mut vertex_indices = parameters.get_int_array("indices")?;
|
||||
let p = parameters.get_point3f_array("P")?;
|
||||
let mut uvs = parameters.get_point2f_array("uv")?;
|
||||
|
|
@ -104,13 +104,12 @@ impl CreateShape for TriangleShape {
|
|||
let mesh_ptr = Ptr::from(&host_arc.device);
|
||||
let mut shapes = Vec::with_capacity(n_patches as usize);
|
||||
for i in 0..n_patches {
|
||||
shapes.push(Shape::Triangle(TriangleShape {
|
||||
shapes.push(arena.alloc(Shape::Triangle(TriangleShape {
|
||||
mesh: mesh_ptr,
|
||||
tri_index: i as i32,
|
||||
}));
|
||||
})));
|
||||
}
|
||||
|
||||
// arena.alloc(shapes);
|
||||
Ok(shapes)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,17 @@ use std::collections::HashMap;
|
|||
use std::sync::LazyLock;
|
||||
|
||||
pub fn create_cie_buffer(data: &[Float]) -> DenselySampledSpectrumBuffer {
|
||||
let buffer = PiecewiseLinearSpectrumBuffer::from_interleaved(data, false);
|
||||
let (start_lambda, step) = match data.len() {
|
||||
471 => (360.0, 1.0),
|
||||
95 => (300.0, 5.0),
|
||||
n => panic!("Unexpected CIE data length: {}", n),
|
||||
};
|
||||
|
||||
let lambdas: Vec<Float> = (0..data.len())
|
||||
.map(|i| start_lambda + i as Float * step)
|
||||
.collect();
|
||||
|
||||
let buffer = PiecewiseLinearSpectrumBuffer::new(lambdas, data.to_vec());
|
||||
let spec = Spectrum::Piecewise(buffer.device);
|
||||
DenselySampledSpectrumBuffer::from_spectrum(&spec)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use anyhow::{Result, anyhow};
|
|||
use shared::core::geometry::Point2f;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::spectrum::StandardSpectra;
|
||||
use shared::spectra::RGBColorSpace;
|
||||
use shared::spectra::DeviceStandardColorSpaces;
|
||||
use shared::spectra::cie::{CIE_D65, CIE_X, CIE_Y, CIE_Z};
|
||||
use shared::utils::Ptr;
|
||||
|
|
@ -60,7 +61,7 @@ pub static SRGB: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
|
|||
let r = Point2f::new(0.64, 0.33);
|
||||
let g = Point2f::new(0.3, 0.6);
|
||||
let b = Point2f::new(0.15, 0.06);
|
||||
let table_ptr = Ptr::from(&SRGB_TABLE.clone());
|
||||
let table_ptr = Ptr::from(&SRGB_TABLE.view);
|
||||
|
||||
Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr))
|
||||
});
|
||||
|
|
@ -71,7 +72,7 @@ pub static DCI_P3: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
|
|||
let g = Point2f::new(0.265, 0.690);
|
||||
let b = Point2f::new(0.150, 0.060);
|
||||
|
||||
let table_ptr = Ptr::from(&DCI_P3_TABLE.clone());
|
||||
let table_ptr = Ptr::from(&DCI_P3_TABLE.view);
|
||||
|
||||
Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr))
|
||||
});
|
||||
|
|
@ -82,7 +83,7 @@ pub static REC2020: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
|
|||
let g = Point2f::new(0.170, 0.797);
|
||||
let b = Point2f::new(0.131, 0.046);
|
||||
|
||||
let table_ptr = Ptr::from(&REC2020_TABLE.clone());
|
||||
let table_ptr = Ptr::from(&REC2020_TABLE.view);
|
||||
Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr))
|
||||
});
|
||||
|
||||
|
|
@ -92,7 +93,7 @@ pub static ACES: LazyLock<Arc<RGBColorSpaceData>> = LazyLock::new(|| {
|
|||
let g = Point2f::new(0.0000, 1.0000);
|
||||
let b = Point2f::new(0.0001, -0.0770);
|
||||
|
||||
let table_ptr = Ptr::from(&ACES_TABLE.clone());
|
||||
let table_ptr = Ptr::from(&ACES_TABLE.view);
|
||||
Arc::new(RGBColorSpaceData::new(r, g, b, illum, table_ptr))
|
||||
});
|
||||
|
||||
|
|
@ -133,3 +134,16 @@ pub fn get_colorspace_device() -> DeviceStandardColorSpaces {
|
|||
aces2065_1: Ptr::from(&ACES.view),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_colorspace() -> RGBColorSpace {
|
||||
let stdcs = get_colorspace_device();
|
||||
*stdcs.srgb
|
||||
}
|
||||
|
||||
pub fn default_colorspace_arc() -> Arc<RGBColorSpace> {
|
||||
Arc::new(default_colorspace())
|
||||
}
|
||||
|
||||
pub fn default_illuminant() -> Spectrum {
|
||||
Spectrum::Dense(default_colorspace().illuminant)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,11 +45,7 @@ impl CreateSpectrumTexture for SpectrumBilerpTexture {
|
|||
}
|
||||
|
||||
impl SpectrumTextureTrait for SpectrumBilerpTexture {
|
||||
fn evaluate(
|
||||
&self,
|
||||
_ctx: &TextureEvalContext,
|
||||
_lambda: &SampledWavelengths,
|
||||
) -> SampledSpectrum {
|
||||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
use crate::Arena;
|
||||
use crate::core::texture::{get_texture_cache, CreateTextureMapping, TexInfo};
|
||||
use crate::core::texture::{
|
||||
CreateFloatTexture, CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTexture,
|
||||
SpectrumTextureTrait,
|
||||
};
|
||||
use crate::core::texture::{TexInfo, get_texture_cache};
|
||||
use crate::utils::mipmap::{MIPMap, MIPMapFilterOptions};
|
||||
use crate::utils::{FileLoc, TextureParameterDictionary};
|
||||
use crate::Arena;
|
||||
use anyhow::Result;
|
||||
use shared::Float;
|
||||
use shared::core::color::ColorEncoding;
|
||||
use shared::core::color::RGB;
|
||||
use shared::core::color::{ColorEncoding, SRGBEncoding};
|
||||
use shared::core::geometry::Vector2f;
|
||||
use shared::core::image::WrapMode;
|
||||
use shared::core::spectrum::SpectrumTrait;
|
||||
|
|
@ -19,6 +18,7 @@ use shared::spectra::{
|
|||
SampledWavelengths,
|
||||
};
|
||||
use shared::utils::Transform;
|
||||
use shared::Float;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
// use crate::utils::{FileLoc, TextureParameterDictionary};
|
||||
|
|
@ -156,12 +156,40 @@ impl SpectrumTextureTrait for SpectrumImageTexture {
|
|||
|
||||
impl CreateSpectrumTexture for SpectrumImageTexture {
|
||||
fn create(
|
||||
_render_from_texture: Transform,
|
||||
_parameters: TextureParameterDictionary,
|
||||
_spectrum_type: SpectrumType,
|
||||
_loc: FileLoc,
|
||||
render_from_texture: Transform,
|
||||
parameters: TextureParameterDictionary,
|
||||
spectrum_type: SpectrumType,
|
||||
loc: FileLoc,
|
||||
) -> Result<SpectrumTexture> {
|
||||
todo!()
|
||||
let mapping = TextureMapping2D::create(¶meters, &render_from_texture, &loc)?;
|
||||
|
||||
let filename = crate::utils::resolve_filename(¶meters.get_one_string("filename", "")?);
|
||||
let scale = parameters.get_one_float("scale", 1.0)?;
|
||||
let invert = parameters.get_one_bool("invert", false)?;
|
||||
|
||||
let filter_options = MIPMapFilterOptions::default();
|
||||
let wrap_str = parameters.get_one_string("wrap", "repeat")?;
|
||||
let wrap_mode = match wrap_str.as_str() {
|
||||
"repeat" => WrapMode::Repeat,
|
||||
"clamp" => WrapMode::Clamp,
|
||||
"black" => WrapMode::Black,
|
||||
_ => WrapMode::Repeat,
|
||||
};
|
||||
|
||||
let encoding = ColorEncoding::SRGB(SRGBEncoding);
|
||||
|
||||
let tex = SpectrumImageTexture::new(
|
||||
mapping,
|
||||
filename,
|
||||
filter_options,
|
||||
wrap_mode,
|
||||
scale,
|
||||
invert,
|
||||
encoding,
|
||||
spectrum_type,
|
||||
);
|
||||
|
||||
Ok(SpectrumTexture::Image(tex))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,57 +1,145 @@
|
|||
use crate::core::image::Image;
|
||||
use crate::core::texture::{FloatTexture, SpectrumTexture};
|
||||
use crate::shapes::{BilinearPatchMesh, TriangleMesh};
|
||||
use crate::spectra::DenselySampledSpectrumBuffer;
|
||||
use crate::utils::backend::GpuAllocator;
|
||||
use crate::utils::mipmap::MIPMap;
|
||||
use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D};
|
||||
use parking_lot::Mutex;
|
||||
use shared::core::color::RGBToSpectrumTable;
|
||||
use shared::core::image::DeviceImage;
|
||||
use shared::core::light::Light;
|
||||
use shared::core::material::Material;
|
||||
use shared::core::shape::Shape;
|
||||
use shared::core::material::Material;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::{GPUFloatTexture, GPUSpectrumTexture};
|
||||
use shared::spectra::{DenselySampledSpectrum, DeviceStandardColorSpaces, RGBColorSpace};
|
||||
use shared::textures::*;
|
||||
use shared::utils::Ptr;
|
||||
use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh};
|
||||
use shared::utils::sampling::{
|
||||
DevicePiecewiseConstant1D, DevicePiecewiseConstant2D, DeviceWindowedPiecewiseConstant2D,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use shared::Ptr;
|
||||
use std::alloc::Layout;
|
||||
use std::collections::HashMap;
|
||||
use std::slice::from_raw_parts;
|
||||
use std::panic::Location;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct Arena<A: GpuAllocator> {
|
||||
allocator: A,
|
||||
inner: Mutex<ArenaInner>,
|
||||
struct Chunk {
|
||||
ptr: *mut u8,
|
||||
layout: Layout,
|
||||
}
|
||||
|
||||
struct ArenaInner {
|
||||
buffer: Vec<(*mut u8, Layout)>,
|
||||
texture_cache: HashMap<usize, u64>,
|
||||
struct GpuBump<A: GpuAllocator> {
|
||||
allocator: A,
|
||||
current: *mut u8,
|
||||
end: *mut u8,
|
||||
chunks: Vec<Chunk>,
|
||||
}
|
||||
|
||||
const CHUNK_SIZE: usize = 256 * 1024;
|
||||
|
||||
impl<A: GpuAllocator> GpuBump<A> {
|
||||
fn new(allocator: A) -> Self {
|
||||
Self {
|
||||
allocator,
|
||||
current: std::ptr::null_mut(),
|
||||
end: std::ptr::null_mut(),
|
||||
chunks: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc<T>(&mut self, value: T) -> *mut T {
|
||||
let layout = Layout::new::<T>();
|
||||
let ptr = self.alloc_layout(layout) as *mut T;
|
||||
unsafe { ptr.write(value) };
|
||||
ptr
|
||||
}
|
||||
|
||||
fn alloc_slice<T: Copy>(&mut self, values: &[T]) -> (*mut T, usize) {
|
||||
if values.is_empty() {
|
||||
return (std::ptr::null_mut(), 0);
|
||||
}
|
||||
let layout = Layout::array::<T>(values.len()).unwrap();
|
||||
let ptr = self.alloc_layout(layout) as *mut T;
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(values.as_ptr(), ptr, values.len());
|
||||
}
|
||||
(ptr, values.len())
|
||||
}
|
||||
|
||||
fn alloc_layout(&mut self, layout: Layout) -> *mut u8 {
|
||||
let size = layout.size();
|
||||
let align = layout.align();
|
||||
|
||||
if size == 0 {
|
||||
return align as *mut u8;
|
||||
}
|
||||
|
||||
// Fast path: bump from current chunk
|
||||
let start = self.current as usize;
|
||||
let aligned = (start + align - 1) & !(align - 1);
|
||||
let end = aligned + size;
|
||||
|
||||
if end <= self.end as usize {
|
||||
self.current = end as *mut u8;
|
||||
return aligned as *mut u8;
|
||||
}
|
||||
|
||||
let chunk_size = if size > CHUNK_SIZE {
|
||||
size.next_multiple_of(align.max(16))
|
||||
} else {
|
||||
CHUNK_SIZE.max(size.next_multiple_of(align.max(16)))
|
||||
};
|
||||
|
||||
let chunk_layout = Layout::from_size_align(chunk_size, align.max(16)).unwrap();
|
||||
let chunk = unsafe { self.allocator.alloc(chunk_layout) };
|
||||
|
||||
let caller = Location::caller();
|
||||
if chunk.is_null() {
|
||||
panic!(
|
||||
"GpuBump OOM {} {}: chunk_size={} align={} backend={}",
|
||||
caller.file(),
|
||||
caller.line(),
|
||||
chunk_size,
|
||||
chunk_layout.align(),
|
||||
std::any::type_name::<A>()
|
||||
);
|
||||
}
|
||||
|
||||
self.chunks.push(Chunk {
|
||||
ptr: chunk,
|
||||
layout: chunk_layout,
|
||||
});
|
||||
|
||||
// If this object alone fills the chunk, mark it consumed and return.
|
||||
if size > CHUNK_SIZE {
|
||||
self.current = unsafe { chunk.add(chunk_size) };
|
||||
self.end = self.current;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
// Set up bump pointers inside the new chunk.
|
||||
self.current = chunk;
|
||||
self.end = unsafe { chunk.add(chunk_size) };
|
||||
|
||||
let aligned = (self.current as usize + align - 1) & !(align - 1);
|
||||
self.current = (aligned + size) as *mut u8;
|
||||
aligned as *mut u8
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: GpuAllocator> Drop for GpuBump<A> {
|
||||
fn drop(&mut self) {
|
||||
for chunk in self.chunks.drain(..) {
|
||||
unsafe { self.allocator.dealloc(chunk.ptr, chunk.layout) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<A: GpuAllocator> Send for GpuBump<A> {}
|
||||
unsafe impl<A: GpuAllocator> Sync for GpuBump<A> {}
|
||||
|
||||
pub struct Arena<A: GpuAllocator> {
|
||||
bump: Mutex<GpuBump<A>>,
|
||||
texture_cache: Mutex<HashMap<usize, u64>>,
|
||||
}
|
||||
|
||||
impl<A: GpuAllocator> Arena<A> {
|
||||
pub fn new(allocator: A) -> Self {
|
||||
Self {
|
||||
allocator,
|
||||
inner: Mutex::new(ArenaInner {
|
||||
buffer: Vec::new(),
|
||||
texture_cache: HashMap::new(),
|
||||
}),
|
||||
bump: Mutex::new(GpuBump::new(allocator)),
|
||||
texture_cache: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alloc<T>(&self, value: T) -> Ptr<T> {
|
||||
let layout = Layout::new::<T>();
|
||||
let ptr = unsafe { self.allocator.alloc(layout) } as *mut T;
|
||||
unsafe { ptr.write(value) };
|
||||
|
||||
self.inner.lock().buffer.push((ptr as *mut u8, layout));
|
||||
let mut bump = self.bump.lock();
|
||||
let ptr = bump.alloc(value);
|
||||
Ptr::from_raw(ptr)
|
||||
}
|
||||
|
||||
|
|
@ -63,32 +151,19 @@ impl<A: GpuAllocator> Arena<A> {
|
|||
}
|
||||
|
||||
pub fn alloc_slice<T: Copy>(&self, values: &[T]) -> (Ptr<T>, usize) {
|
||||
if values.is_empty() {
|
||||
return (Ptr::null(), 0);
|
||||
}
|
||||
|
||||
let layout = Layout::array::<T>(values.len()).unwrap();
|
||||
let ptr = unsafe { self.allocator.alloc(layout) } as *mut T;
|
||||
unsafe { std::ptr::copy_nonoverlapping(values.as_ptr(), ptr, values.len()) };
|
||||
|
||||
self.inner.lock().buffer.push((ptr as *mut u8, layout));
|
||||
(Ptr::from_raw(ptr), values.len())
|
||||
let mut bump = self.bump.lock();
|
||||
let (ptr, len) = bump.alloc_slice(values);
|
||||
(Ptr::from_raw(ptr), len)
|
||||
}
|
||||
|
||||
pub fn get_texture_object(&self, mipmap: &Arc<MIPMap>) -> u64 {
|
||||
let key = Arc::as_ptr(mipmap) as usize;
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
if let Some(&tex_obj) = inner.texture_cache.get(&key) {
|
||||
let mut cache = self.texture_cache.lock();
|
||||
if let Some(&tex_obj) = cache.get(&key) {
|
||||
return tex_obj;
|
||||
}
|
||||
|
||||
// TODO: Backend-specific texture object creation.
|
||||
// CUDA: cudaCreateTextureObject
|
||||
// Vulkan: VkImageView + VkSampler -> descriptor index
|
||||
let tex_obj = 0u64;
|
||||
|
||||
inner.texture_cache.insert(key, tex_obj);
|
||||
let tex_obj = 0u64; // TODO: backend-specific creation
|
||||
cache.insert(key, tex_obj);
|
||||
tex_obj
|
||||
}
|
||||
}
|
||||
|
|
@ -99,371 +174,32 @@ impl<A: GpuAllocator + Default> Default for Arena<A> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<A: GpuAllocator> Drop for Arena<A> {
|
||||
fn drop(&mut self) {
|
||||
let inner = self.inner.get_mut();
|
||||
for (ptr, layout) in inner.buffer.drain(..) {
|
||||
unsafe { self.allocator.dealloc(ptr, layout) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<A: GpuAllocator> Send for Arena<A> {}
|
||||
unsafe impl<A: GpuAllocator> Sync for Arena<A> {}
|
||||
|
||||
pub trait Upload {
|
||||
pub trait DeviceRepr {
|
||||
/// The `#[repr(C)] Copy` device-side struct.
|
||||
type Target: Copy;
|
||||
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target>;
|
||||
}
|
||||
/// Upload into the arena and return the device struct by value.
|
||||
/// Use this when embedding the result inline in another device struct.
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> Self::Target;
|
||||
|
||||
impl Upload for Shape {
|
||||
type Target = Shape;
|
||||
/// Upload into the arena and return a Ptr to the device struct.
|
||||
/// This is the common entry point — allocates the Target in the arena.
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
arena.alloc(self.clone())
|
||||
let value = self.upload_value(arena);
|
||||
arena.alloc(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for Light {
|
||||
type Target = Light;
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
arena.alloc(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for Image {
|
||||
type Target = DeviceImage;
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
arena.alloc(*self.device())
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for Spectrum {
|
||||
type Target = Spectrum;
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
arena.alloc(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for Material {
|
||||
type Target = Material;
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
arena.alloc(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for DenselySampledSpectrumBuffer {
|
||||
type Target = DenselySampledSpectrum;
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
arena.alloc(*&self.device())
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for SpectrumTexture {
|
||||
type Target = GPUSpectrumTexture;
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
let gpu_variant = match self {
|
||||
SpectrumTexture::Constant(tex) => GPUSpectrumTexture::Constant(tex.clone()),
|
||||
SpectrumTexture::Checkerboard(tex) => GPUSpectrumTexture::Checkerboard(tex.clone()),
|
||||
SpectrumTexture::Dots(tex) => GPUSpectrumTexture::Dots(tex.clone()),
|
||||
SpectrumTexture::Image(tex) => {
|
||||
let tex_obj = arena.get_texture_object(&tex.base.mipmap);
|
||||
let gpu_img = GPUSpectrumImageTexture {
|
||||
mapping: tex.base.mapping,
|
||||
tex_obj,
|
||||
scale: tex.base.scale,
|
||||
invert: tex.base.invert,
|
||||
is_single_channel: tex.base.mipmap.is_single_channel(),
|
||||
color_space: tex.base.mipmap.color_space.clone().unwrap(),
|
||||
spectrum_type: tex.spectrum_type,
|
||||
};
|
||||
GPUSpectrumTexture::Image(gpu_img)
|
||||
}
|
||||
SpectrumTexture::Bilerp(tex) => GPUSpectrumTexture::Bilerp(tex.clone()),
|
||||
SpectrumTexture::Scaled(tex) => {
|
||||
let child_ptr = tex.tex.upload(arena);
|
||||
|
||||
let gpu_scaled = GPUSpectrumScaledTexture {
|
||||
tex: child_ptr,
|
||||
scale: tex.scale.upload(arena),
|
||||
};
|
||||
GPUSpectrumTexture::Scaled(gpu_scaled)
|
||||
}
|
||||
SpectrumTexture::Marble(tex) => GPUSpectrumTexture::Marble(tex.clone()),
|
||||
SpectrumTexture::Mix(tex) => {
|
||||
let tex1_ptr = tex.tex1.upload(arena);
|
||||
let tex2_ptr = tex.tex2.upload(arena);
|
||||
let amount_ptr = tex.amount.upload(arena);
|
||||
|
||||
let gpu_mix = GPUSpectrumMixTexture {
|
||||
tex1: tex1_ptr,
|
||||
tex2: tex2_ptr,
|
||||
amount: amount_ptr,
|
||||
};
|
||||
|
||||
GPUSpectrumTexture::Mix(gpu_mix)
|
||||
}
|
||||
SpectrumTexture::DirectionMix(tex) => {
|
||||
let tex1_ptr = tex.tex1.upload(arena);
|
||||
let tex2_ptr = tex.tex2.upload(arena);
|
||||
|
||||
let gpu_mix = GPUSpectrumDirectionMixTexture {
|
||||
tex1: tex1_ptr,
|
||||
tex2: tex2_ptr,
|
||||
dir: tex.dir,
|
||||
};
|
||||
|
||||
GPUSpectrumTexture::DirectionMix(gpu_mix)
|
||||
}
|
||||
};
|
||||
|
||||
arena.alloc(gpu_variant)
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for FloatTexture {
|
||||
type Target = GPUFloatTexture;
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
let gpu_variant = match self {
|
||||
FloatTexture::Constant(tex) => GPUFloatTexture::Constant(tex.clone()),
|
||||
FloatTexture::Checkerboard(tex) => GPUFloatTexture::Checkerboard(tex.clone()),
|
||||
FloatTexture::Dots(tex) => GPUFloatTexture::Dots(tex.clone()),
|
||||
FloatTexture::FBm(tex) => GPUFloatTexture::FBm(tex.clone()),
|
||||
FloatTexture::Windy(tex) => GPUFloatTexture::Windy(tex.clone()),
|
||||
FloatTexture::Wrinkled(tex) => GPUFloatTexture::Wrinkled(tex.clone()),
|
||||
FloatTexture::Scaled(tex) => {
|
||||
let child_ptr = tex.tex.upload(arena);
|
||||
|
||||
let gpu_scaled = GPUFloatScaledTexture {
|
||||
tex: child_ptr,
|
||||
scale: tex.scale.upload(arena),
|
||||
};
|
||||
GPUFloatTexture::Scaled(gpu_scaled)
|
||||
}
|
||||
|
||||
FloatTexture::Mix(tex) => {
|
||||
let tex1_ptr = tex.tex1.upload(arena);
|
||||
let tex2_ptr = tex.tex2.upload(arena);
|
||||
let amount_ptr = tex.amount.upload(arena);
|
||||
|
||||
let gpu_mix = GPUFloatMixTexture {
|
||||
tex1: tex1_ptr,
|
||||
tex2: tex2_ptr,
|
||||
amount: amount_ptr,
|
||||
};
|
||||
GPUFloatTexture::Mix(gpu_mix)
|
||||
}
|
||||
|
||||
FloatTexture::DirectionMix(tex) => {
|
||||
let tex1_ptr = tex.tex1.upload(arena);
|
||||
let tex2_ptr = tex.tex2.upload(arena);
|
||||
let gpu_dmix = GPUFloatDirectionMixTexture {
|
||||
tex1: tex1_ptr,
|
||||
tex2: tex2_ptr,
|
||||
dir: tex.dir,
|
||||
};
|
||||
GPUFloatTexture::DirectionMix(gpu_dmix)
|
||||
}
|
||||
|
||||
FloatTexture::Image(tex) => {
|
||||
let gpu_image_tex = GPUFloatImageTexture {
|
||||
mapping: tex.base.mapping,
|
||||
tex_obj: tex.base.mipmap.texture_object(),
|
||||
scale: tex.base.scale,
|
||||
invert: tex.base.invert,
|
||||
};
|
||||
GPUFloatTexture::Image(gpu_image_tex)
|
||||
}
|
||||
|
||||
FloatTexture::Bilerp(tex) => GPUFloatTexture::Bilerp(tex.clone()),
|
||||
};
|
||||
|
||||
arena.alloc(gpu_variant)
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for RGBToSpectrumTable {
|
||||
type Target = RGBToSpectrumTable;
|
||||
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
let n_nodes = self.n_nodes as usize;
|
||||
let z_slice = unsafe { from_raw_parts(self.z_nodes.as_raw(), n_nodes) };
|
||||
let coeffs_slice = unsafe { from_raw_parts(self.coeffs.as_raw(), n_nodes) };
|
||||
let (z_ptr, _) = arena.alloc_slice(z_slice);
|
||||
let (c_ptr, _) = arena.alloc_slice(coeffs_slice);
|
||||
|
||||
let shared_table = RGBToSpectrumTable {
|
||||
z_nodes: z_ptr,
|
||||
coeffs: c_ptr,
|
||||
n_nodes: self.n_nodes,
|
||||
};
|
||||
|
||||
arena.alloc(shared_table)
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for RGBColorSpace {
|
||||
type Target = RGBColorSpace;
|
||||
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
let table_ptr = self.rgb_to_spectrum_table.upload(arena);
|
||||
|
||||
let shared_space = RGBColorSpace {
|
||||
r: self.r,
|
||||
g: self.g,
|
||||
b: self.b,
|
||||
w: self.w,
|
||||
illuminant: self.illuminant.clone(),
|
||||
rgb_to_spectrum_table: table_ptr,
|
||||
xyz_from_rgb: self.xyz_from_rgb,
|
||||
rgb_from_xyz: self.rgb_from_xyz,
|
||||
};
|
||||
|
||||
arena.alloc(shared_space)
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for DeviceStandardColorSpaces {
|
||||
type Target = DeviceStandardColorSpaces;
|
||||
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
let srgb_ptr = self.srgb.upload(arena);
|
||||
let dci_ptr = self.dci_p3.upload(arena);
|
||||
let rec_ptr = self.rec2020.upload(arena);
|
||||
let aces_ptr = self.aces2065_1.upload(arena);
|
||||
|
||||
let registry = DeviceStandardColorSpaces {
|
||||
srgb: srgb_ptr,
|
||||
dci_p3: dci_ptr,
|
||||
rec2020: rec_ptr,
|
||||
aces2065_1: aces_ptr,
|
||||
};
|
||||
|
||||
arena.alloc(registry)
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for PiecewiseConstant2D {
|
||||
type Target = DevicePiecewiseConstant2D;
|
||||
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
let marginal_shared = self.marginal.to_shared(arena);
|
||||
|
||||
let conditionals_shared: Vec<DevicePiecewiseConstant1D> = self
|
||||
.conditionals
|
||||
.iter()
|
||||
.map(|c| c.to_shared(arena))
|
||||
.collect();
|
||||
|
||||
let (conditionals_ptr, _) = arena.alloc_slice(&conditionals_shared);
|
||||
|
||||
let shared_2d = DevicePiecewiseConstant2D {
|
||||
conditionals: conditionals_ptr,
|
||||
marginal: marginal_shared,
|
||||
..self.device
|
||||
};
|
||||
|
||||
arena.alloc(shared_2d)
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for WindowedPiecewiseConstant2D {
|
||||
type Target = DeviceWindowedPiecewiseConstant2D;
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
let specific = DeviceWindowedPiecewiseConstant2D {
|
||||
sat: self.sat,
|
||||
func: self.func,
|
||||
};
|
||||
arena.alloc(specific)
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for TriangleMesh {
|
||||
type Target = DeviceTriangleMesh;
|
||||
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
let storage = &self.storage;
|
||||
|
||||
// Upload all arrays to arena
|
||||
let (vertex_indices_ptr, _) = arena.alloc_slice(&storage.vertex_indices);
|
||||
let (p_ptr, _) = arena.alloc_slice(&storage.p);
|
||||
|
||||
let (n_ptr, _) = if storage.n.is_empty() {
|
||||
(Ptr::null(), 0)
|
||||
} else {
|
||||
arena.alloc_slice(&storage.n)
|
||||
};
|
||||
|
||||
let (s_ptr, _) = if storage.s.is_empty() {
|
||||
(Ptr::null(), 0)
|
||||
} else {
|
||||
arena.alloc_slice(&storage.s)
|
||||
};
|
||||
|
||||
let (uv_ptr, _) = if storage.uv.is_empty() {
|
||||
(Ptr::null(), 0)
|
||||
} else {
|
||||
arena.alloc_slice(&storage.uv)
|
||||
};
|
||||
|
||||
let (face_indices_ptr, _) = if storage.face_indices.is_empty() {
|
||||
(Ptr::null(), 0)
|
||||
} else {
|
||||
arena.alloc_slice(&storage.face_indices)
|
||||
};
|
||||
|
||||
let device = DeviceTriangleMesh {
|
||||
vertex_indices: vertex_indices_ptr,
|
||||
p: p_ptr,
|
||||
n: n_ptr,
|
||||
s: s_ptr,
|
||||
uv: uv_ptr,
|
||||
face_indices: face_indices_ptr,
|
||||
..self.device
|
||||
};
|
||||
|
||||
arena.alloc(device)
|
||||
}
|
||||
}
|
||||
|
||||
impl Upload for BilinearPatchMesh {
|
||||
type Target = DeviceBilinearPatchMesh;
|
||||
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
let storage = &self.storage;
|
||||
|
||||
let (vertex_indices_ptr, _) = arena.alloc_slice(&storage.vertex_indices);
|
||||
let (p_ptr, _) = arena.alloc_slice(&storage.p);
|
||||
|
||||
let (n_ptr, _) = if storage.n.is_empty() {
|
||||
(Ptr::null(), 0)
|
||||
} else {
|
||||
arena.alloc_slice(&storage.n)
|
||||
};
|
||||
|
||||
let (uv_ptr, _) = if storage.uv.is_empty() {
|
||||
(Ptr::null(), 0)
|
||||
} else {
|
||||
arena.alloc_slice(&storage.uv)
|
||||
};
|
||||
|
||||
let image_dist_ptr = storage.image_distribution.upload(arena);
|
||||
|
||||
let device = DeviceBilinearPatchMesh {
|
||||
vertex_indices: vertex_indices_ptr,
|
||||
p: p_ptr,
|
||||
n: n_ptr,
|
||||
uv: uv_ptr,
|
||||
image_distribution: image_dist_ptr,
|
||||
..self.device
|
||||
};
|
||||
|
||||
arena.alloc(device)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Upload> Upload for Option<T> {
|
||||
impl<T: DeviceRepr> DeviceRepr for Option<T> {
|
||||
type Target = T::Target;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> Self::Target {
|
||||
match self {
|
||||
Some(val) => val.upload_value(arena),
|
||||
None => panic!("Cannot upload_value on None — use upload() which returns Ptr::null()"),
|
||||
}
|
||||
}
|
||||
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
match self {
|
||||
Some(val) => val.upload(arena),
|
||||
|
|
@ -472,10 +208,343 @@ impl<T: Upload> Upload for Option<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Upload> Upload for Arc<T> {
|
||||
impl<T: DeviceRepr> DeviceRepr for std::sync::Arc<T> {
|
||||
type Target = T::Target;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> Self::Target {
|
||||
(**self).upload_value(arena)
|
||||
}
|
||||
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
(**self).upload(arena)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DeviceRepr> DeviceRepr for Box<T> {
|
||||
type Target = T::Target;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> Self::Target {
|
||||
(**self).upload_value(arena)
|
||||
}
|
||||
|
||||
fn upload<A: GpuAllocator>(&self, arena: &Arena<A>) -> Ptr<Self::Target> {
|
||||
(**self).upload(arena)
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceRepr for Shape {
|
||||
type Target = Shape;
|
||||
fn upload_value<A: GpuAllocator>(&self, _arena: &Arena<A>) -> Shape {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceRepr for Light {
|
||||
type Target = Light;
|
||||
fn upload_value<A: GpuAllocator>(&self, _arena: &Arena<A>) -> Light {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceRepr for Spectrum {
|
||||
type Target = Spectrum;
|
||||
fn upload_value<A: GpuAllocator>(&self, _arena: &Arena<A>) -> Spectrum {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceRepr for Material {
|
||||
type Target = Material;
|
||||
fn upload_value<A: GpuAllocator>(&self, _arena: &Arena<A>) -> Material {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Image → DeviceImage
|
||||
// =============================================================================
|
||||
|
||||
impl DeviceRepr for Image {
|
||||
type Target = DeviceImage;
|
||||
fn upload_value<A: GpuAllocator>(&self, _arena: &Arena<A>) -> DeviceImage {
|
||||
*self.device()
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DenselySampledSpectrumBuffer → DenselySampledSpectrum
|
||||
// =============================================================================
|
||||
|
||||
impl DeviceRepr for DenselySampledSpectrumBuffer {
|
||||
type Target = DenselySampledSpectrum;
|
||||
fn upload_value<A: GpuAllocator>(&self, _arena: &Arena<A>) -> DenselySampledSpectrum {
|
||||
self.device()
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RGBToSpectrumTable — re-uploads Ptr fields into arena
|
||||
// =============================================================================
|
||||
|
||||
impl DeviceRepr for RGBToSpectrumTable {
|
||||
type Target = RGBToSpectrumTable;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> RGBToSpectrumTable {
|
||||
let n_nodes = self.n_nodes as usize;
|
||||
|
||||
// Safety: these Ptrs point into static or previously-uploaded data;
|
||||
// we're copying the contents into the arena for a new lifetime.
|
||||
let z_slice = unsafe { from_raw_parts(self.z_nodes.as_raw(), n_nodes) };
|
||||
let (z_ptr, _) = arena.alloc_slice(z_slice);
|
||||
|
||||
let n_coeffs = 3 * n_nodes.pow(3);
|
||||
let coeffs_slice = unsafe { from_raw_parts(self.coeffs.as_raw(), n_coeffs) };
|
||||
let (c_ptr, _) = arena.alloc_slice(coeffs_slice);
|
||||
|
||||
RGBToSpectrumTable {
|
||||
z_nodes: z_ptr,
|
||||
coeffs: c_ptr,
|
||||
n_nodes: self.n_nodes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RGBColorSpace — nested upload of spectrum table
|
||||
// =============================================================================
|
||||
|
||||
impl DeviceRepr for RGBColorSpace {
|
||||
type Target = RGBColorSpace;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> RGBColorSpace {
|
||||
let table_ptr = self.rgb_to_spectrum_table.upload(arena);
|
||||
|
||||
RGBColorSpace {
|
||||
r: self.r,
|
||||
g: self.g,
|
||||
b: self.b,
|
||||
w: self.w,
|
||||
illuminant: self.illuminant.clone(),
|
||||
rgb_to_spectrum_table: table_ptr,
|
||||
xyz_from_rgb: self.xyz_from_rgb,
|
||||
rgb_from_xyz: self.rgb_from_xyz,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DeviceStandardColorSpaces — composition of color space uploads
|
||||
// =============================================================================
|
||||
|
||||
impl DeviceRepr for DeviceStandardColorSpaces {
|
||||
type Target = DeviceStandardColorSpaces;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> DeviceStandardColorSpaces {
|
||||
DeviceStandardColorSpaces {
|
||||
srgb: self.srgb.upload(arena),
|
||||
dci_p3: self.dci_p3.upload(arena),
|
||||
rec2020: self.rec2020.upload(arena),
|
||||
aces2065_1: self.aces2065_1.upload(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TriangleMesh → DeviceTriangleMesh
|
||||
// =============================================================================
|
||||
// TriangleMesh should own all its fields directly — no .device field.
|
||||
// The host struct holds Vec arrays + scalar metadata. derive(Device) handles it:
|
||||
//
|
||||
// #[derive(Device)]
|
||||
// #[device(name = "DeviceTriangleMesh")]
|
||||
// pub struct TriangleMesh {
|
||||
// pub vertex_indices: Vec<i32>,
|
||||
// pub p: Vec<Point3f>,
|
||||
// pub n: Vec<Normal3f>,
|
||||
// pub s: Vec<Vector3f>,
|
||||
// pub uv: Vec<Point2f>,
|
||||
// pub face_indices: Vec<i32>,
|
||||
// pub n_triangles: u32,
|
||||
// pub reverse_orientation: bool,
|
||||
// pub transform_swaps_handedness: bool,
|
||||
// }
|
||||
//
|
||||
// Until the mesh struct is refactored to remove .device, here's a manual
|
||||
// impl that lists every field. This is the MIGRATION TARGET — once TriangleMesh
|
||||
// drops its .device field and puts scalars at the top level, switch to derive(Device).
|
||||
|
||||
impl DeviceRepr for TriangleMesh {
|
||||
type Target = DeviceTriangleMesh;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> DeviceTriangleMesh {
|
||||
let s = &self.storage;
|
||||
|
||||
let (vertex_indices_ptr, _) = arena.alloc_slice(&s.vertex_indices);
|
||||
let (p_ptr, _) = arena.alloc_slice(&s.p);
|
||||
let (n_ptr, _) = arena.alloc_slice(&s.n);
|
||||
let (s_ptr, _) = arena.alloc_slice(&s.s);
|
||||
let (uv_ptr, _) = arena.alloc_slice(&s.uv);
|
||||
let (face_indices_ptr, _) = arena.alloc_slice(&s.face_indices);
|
||||
|
||||
DeviceTriangleMesh {
|
||||
vertex_indices: vertex_indices_ptr,
|
||||
p: p_ptr,
|
||||
n: n_ptr,
|
||||
s: s_ptr,
|
||||
uv: uv_ptr,
|
||||
face_indices: face_indices_ptr,
|
||||
n_triangles: self.n_triangles,
|
||||
reverse_orientation: self.reverse_orientation,
|
||||
transform_swaps_handedness: self.transform_swaps_handedness,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// BilinearPatchMesh → DeviceBilinearPatchMesh
|
||||
// =============================================================================
|
||||
// Same as TriangleMesh: once the host struct is refactored to own scalars
|
||||
// directly (no .device field), this switches to derive(Device).
|
||||
|
||||
impl DeviceRepr for BilinearPatchMesh {
|
||||
type Target = DeviceBilinearPatchMesh;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> DeviceBilinearPatchMesh {
|
||||
let s = &self.storage;
|
||||
|
||||
let (vertex_indices_ptr, _) = arena.alloc_slice(&s.vertex_indices);
|
||||
let (p_ptr, _) = arena.alloc_slice(&s.p);
|
||||
let (n_ptr, _) = arena.alloc_slice(&s.n);
|
||||
let (uv_ptr, _) = arena.alloc_slice(&s.uv);
|
||||
|
||||
let image_dist_ptr = s.image_distribution.upload(arena);
|
||||
|
||||
DeviceBilinearPatchMesh {
|
||||
vertex_indices: vertex_indices_ptr,
|
||||
p: p_ptr,
|
||||
n: n_ptr,
|
||||
uv: uv_ptr,
|
||||
image_distribution: image_dist_ptr,
|
||||
n_patches: self.n_patches,
|
||||
reverse_orientation: self.reverse_orientation,
|
||||
transform_swaps_handedness: self.transform_swaps_handedness,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SpectrumTexture → GPUSpectrumTexture
|
||||
// =============================================================================
|
||||
|
||||
impl DeviceRepr for SpectrumTexture {
|
||||
type Target = GPUSpectrumTexture;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> GPUSpectrumTexture {
|
||||
match self {
|
||||
SpectrumTexture::Constant(tex) => GPUSpectrumTexture::Constant(tex.clone()),
|
||||
SpectrumTexture::Checkerboard(tex) => GPUSpectrumTexture::Checkerboard(tex.clone()),
|
||||
SpectrumTexture::Dots(tex) => GPUSpectrumTexture::Dots(tex.clone()),
|
||||
SpectrumTexture::Image(tex) => {
|
||||
let tex_obj = arena.get_texture_object(&tex.base.mipmap);
|
||||
GPUSpectrumTexture::Image(GPUSpectrumImageTexture {
|
||||
mapping: tex.base.mapping,
|
||||
tex_obj,
|
||||
scale: tex.base.scale,
|
||||
invert: tex.base.invert,
|
||||
is_single_channel: tex.base.mipmap.is_single_channel(),
|
||||
color_space: tex
|
||||
.base
|
||||
.mipmap
|
||||
.color_space
|
||||
.clone()
|
||||
.unwrap_or_else(crate::spectra::default_colorspace),
|
||||
spectrum_type: tex.spectrum_type,
|
||||
})
|
||||
}
|
||||
SpectrumTexture::Bilerp(tex) => GPUSpectrumTexture::Bilerp(tex.clone()),
|
||||
SpectrumTexture::Scaled(tex) => {
|
||||
let child_ptr = tex.tex.upload(arena);
|
||||
let scale_ptr = tex.scale.upload(arena);
|
||||
GPUSpectrumTexture::Scaled(GPUSpectrumScaledTexture {
|
||||
tex: child_ptr,
|
||||
scale: scale_ptr,
|
||||
})
|
||||
}
|
||||
SpectrumTexture::Marble(tex) => GPUSpectrumTexture::Marble(tex.clone()),
|
||||
SpectrumTexture::Mix(tex) => {
|
||||
let tex1_ptr = tex.tex1.upload(arena);
|
||||
let tex2_ptr = tex.tex2.upload(arena);
|
||||
let amount_ptr = tex.amount.upload(arena);
|
||||
GPUSpectrumTexture::Mix(GPUSpectrumMixTexture {
|
||||
tex1: tex1_ptr,
|
||||
tex2: tex2_ptr,
|
||||
amount: amount_ptr,
|
||||
})
|
||||
}
|
||||
SpectrumTexture::DirectionMix(tex) => {
|
||||
let tex1_ptr = tex.tex1.upload(arena);
|
||||
let tex2_ptr = tex.tex2.upload(arena);
|
||||
GPUSpectrumTexture::DirectionMix(GPUSpectrumDirectionMixTexture {
|
||||
tex1: tex1_ptr,
|
||||
tex2: tex2_ptr,
|
||||
dir: tex.dir,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FloatTexture → GPUFloatTexture
|
||||
// =============================================================================
|
||||
|
||||
impl DeviceRepr for FloatTexture {
|
||||
type Target = GPUFloatTexture;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> GPUFloatTexture {
|
||||
match self {
|
||||
FloatTexture::Constant(tex) => GPUFloatTexture::Constant(tex.clone()),
|
||||
FloatTexture::Checkerboard(tex) => GPUFloatTexture::Checkerboard(tex.clone()),
|
||||
FloatTexture::Dots(tex) => GPUFloatTexture::Dots(tex.clone()),
|
||||
FloatTexture::FBm(tex) => GPUFloatTexture::FBm(tex.clone()),
|
||||
FloatTexture::Windy(tex) => GPUFloatTexture::Windy(tex.clone()),
|
||||
FloatTexture::Wrinkled(tex) => GPUFloatTexture::Wrinkled(tex.clone()),
|
||||
FloatTexture::Scaled(tex) => {
|
||||
let child_ptr = tex.tex.upload(arena);
|
||||
let scale_ptr = tex.scale.upload(arena);
|
||||
GPUFloatTexture::Scaled(GPUFloatScaledTexture {
|
||||
tex: child_ptr,
|
||||
scale: scale_ptr,
|
||||
})
|
||||
}
|
||||
FloatTexture::Mix(tex) => {
|
||||
let tex1_ptr = tex.tex1.upload(arena);
|
||||
let tex2_ptr = tex.tex2.upload(arena);
|
||||
let amount_ptr = tex.amount.upload(arena);
|
||||
GPUFloatTexture::Mix(GPUFloatMixTexture {
|
||||
tex1: tex1_ptr,
|
||||
tex2: tex2_ptr,
|
||||
amount: amount_ptr,
|
||||
})
|
||||
}
|
||||
FloatTexture::DirectionMix(tex) => {
|
||||
let tex1_ptr = tex.tex1.upload(arena);
|
||||
let tex2_ptr = tex.tex2.upload(arena);
|
||||
GPUFloatTexture::DirectionMix(GPUFloatDirectionMixTexture {
|
||||
tex1: tex1_ptr,
|
||||
tex2: tex2_ptr,
|
||||
dir: tex.dir,
|
||||
})
|
||||
}
|
||||
FloatTexture::Image(tex) => {
|
||||
GPUFloatTexture::Image(GPUFloatImageTexture {
|
||||
mapping: tex.base.mapping,
|
||||
tex_obj: tex.base.mipmap.texture_object(),
|
||||
scale: tex.base.scale,
|
||||
invert: tex.base.invert,
|
||||
})
|
||||
}
|
||||
FloatTexture::Bilerp(tex) => GPUFloatTexture::Bilerp(tex.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
use std::alloc::Layout;
|
||||
|
||||
pub trait GpuAllocator: Send + Sync {
|
||||
/// Allocate `size` bytes with given alignment.
|
||||
/// Returns a host-mapped pointer.
|
||||
pub trait GpuAllocator: Send + Sync + Clone {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8;
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout);
|
||||
}
|
||||
|
||||
/// CPU fallback — standard system allocator.
|
||||
/// CPU fallback
|
||||
#[derive(Clone)]
|
||||
pub struct SystemAllocator;
|
||||
|
||||
impl Default for SystemAllocator {
|
||||
fn default() -> Self {
|
||||
Self {}
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -21,101 +20,85 @@ impl GpuAllocator for SystemAllocator {
|
|||
if layout.size() == 0 {
|
||||
return layout.align() as *mut u8;
|
||||
}
|
||||
unsafe { std::alloc::alloc(layout) }
|
||||
std::alloc::alloc(layout)
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
if layout.size() > 0 {
|
||||
unsafe {
|
||||
std::alloc::dealloc(ptr, layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CUDA unified memory backend using CudaAllocator
|
||||
/// CUDA unified memory. Still using CudaAllocator, might move over to
|
||||
#[cfg(feature = "cuda")]
|
||||
pub mod cuda {
|
||||
use super::GpuAllocator;
|
||||
use cust::memory::{cuda_free_unified, cuda_malloc_unified, UnifiedPointer};
|
||||
use std::alloc::Layout;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CudaAllocator;
|
||||
|
||||
impl Default for CudaAllocator {
|
||||
fn default() -> Self {
|
||||
Self {}
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuAllocator for CudaAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
use cust::memory::cuda_malloc_unified;
|
||||
use cust_raw::driver_sys::*;
|
||||
|
||||
let size = layout.size().max(layout.align());
|
||||
let size = layout.size();
|
||||
if size == 0 {
|
||||
return layout.align() as *mut u8;
|
||||
}
|
||||
|
||||
let mut ctx: CUcontext = std::ptr::null_mut();
|
||||
cuCtxGetCurrent(&mut ctx);
|
||||
if ctx.is_null() {
|
||||
let mut primary: CUcontext = std::ptr::null_mut();
|
||||
cuDevicePrimaryCtxRetain(&mut primary, 0);
|
||||
cuCtxSetCurrent(primary);
|
||||
}
|
||||
let ptr = cuda_malloc_unified::<u8>(size)
|
||||
.expect("cuda_malloc_unified failed — is a CUDA context current?");
|
||||
|
||||
let mut unified_ptr =
|
||||
unsafe { cuda_malloc_unified::<u8>(size).expect("cuda_malloc_unified failed") };
|
||||
let raw = unified_ptr.as_raw_mut();
|
||||
std::mem::forget(unified_ptr);
|
||||
let raw = ptr.as_raw_mut();
|
||||
std::mem::forget(ptr); // Leak RAII wrapper; Arena owns the raw pointer
|
||||
raw
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
use cust::memory::{UnifiedPointer, cuda_free_unified};
|
||||
if layout.size() > 0 {
|
||||
let _ = unsafe { cuda_free_unified(UnifiedPointer::wrap(ptr)) };
|
||||
if layout.size() > 0 && !ptr.is_null() {
|
||||
let _ = cuda_free_unified(UnifiedPointer::wrap(ptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Vulkan backend (gpu-allocator for now, there might be a better solution)
|
||||
#[cfg(feature = "vulkan")]
|
||||
pub mod vulkan {
|
||||
use super::GpuAllocator;
|
||||
use ash::vk;
|
||||
use gpu_allocator::MemoryLocation;
|
||||
use gpu_allocator::vulkan::{
|
||||
Allocation, AllocationCreateDesc, AllocationScheme, Allocator, AllocatorCreateDesc,
|
||||
};
|
||||
use gpu_allocator::MemoryLocation;
|
||||
use parking_lot::Mutex;
|
||||
use std::alloc::Layout;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
// So, having a static allocator seems like a terrible idea
|
||||
// But I cant find a way to get a functioning generic Arena constructor
|
||||
// That might not even be a necessity, since rust-gpu/rust-cuda might actually handle that
|
||||
// differently
|
||||
static VK_ALLOCATOR: OnceLock<VulkanAllocatorInner> = OnceLock::new();
|
||||
|
||||
struct VulkanAllocatorInner {
|
||||
state: Mutex<VulkanState>,
|
||||
#[derive(Clone)]
|
||||
pub struct VulkanAllocator {
|
||||
inner: Arc<Mutex<VulkanInner>>,
|
||||
}
|
||||
|
||||
struct VulkanState {
|
||||
struct VulkanInner {
|
||||
device: ash::Device,
|
||||
allocator: Allocator,
|
||||
allocations: HashMap<usize, Allocation>,
|
||||
}
|
||||
|
||||
pub fn init_vulkan(
|
||||
impl VulkanAllocator {
|
||||
pub fn new(
|
||||
instance: &ash::Instance,
|
||||
device: &ash::Device,
|
||||
device: ash::Device,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
) {
|
||||
VK_ALLOCATOR.get_or_init(|| {
|
||||
) -> Self {
|
||||
let allocator = Allocator::new(&AllocatorCreateDesc {
|
||||
instance: instance.clone(),
|
||||
device: device.clone(),
|
||||
|
|
@ -126,52 +109,29 @@ pub mod vulkan {
|
|||
})
|
||||
.expect("Failed to create Vulkan allocator");
|
||||
|
||||
VulkanAllocatorInner {
|
||||
state: Mutex::new(VulkanState {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(VulkanInner {
|
||||
device,
|
||||
allocator,
|
||||
allocations: HashMap::new(),
|
||||
}),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn inner() -> &'static VulkanAllocatorInner {
|
||||
VK_ALLOCATOR
|
||||
.get()
|
||||
.expect("Vulkan not initialized — call init_vulkan() before arena creation")
|
||||
}
|
||||
|
||||
impl Default for VulkanAllocator {
|
||||
fn default() -> Self {
|
||||
let _ = inner();
|
||||
Self
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VulkanAllocator;
|
||||
|
||||
// impl VulkanAllocator {
|
||||
// pub fn new(allocator: Allocator) -> Self {
|
||||
// Self {
|
||||
// allocator: Mutex::new(allocator),
|
||||
// allocations: Mutex::new(HashMap::new()),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl GpuAllocator for VulkanAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
let size = layout.size().max(layout.align());
|
||||
let size = layout.size();
|
||||
if size == 0 {
|
||||
return layout.align() as *mut u8;
|
||||
}
|
||||
|
||||
let inner = inner();
|
||||
let mut state = inner.state.lock();
|
||||
let allocation = state
|
||||
let mut inner = self.inner.lock();
|
||||
|
||||
let allocation = inner
|
||||
.allocator
|
||||
.allocate(&AllocationCreateDesc {
|
||||
name: "arena",
|
||||
name: "arena_chunk",
|
||||
requirements: vk::MemoryRequirements {
|
||||
size: size as u64,
|
||||
alignment: layout.align() as u64,
|
||||
|
|
@ -188,18 +148,17 @@ pub mod vulkan {
|
|||
.expect("Vulkan allocation not host-mapped")
|
||||
.as_ptr() as *mut u8;
|
||||
|
||||
state.allocations.insert(ptr as usize, allocation);
|
||||
inner.allocations.insert(ptr as usize, allocation);
|
||||
ptr
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
if layout.size() == 0 {
|
||||
if layout.size() == 0 || ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
let inner = inner();
|
||||
let mut state = inner.state.lock();
|
||||
if let Some(allocation) = state.allocations.remove(&(ptr as usize)) {
|
||||
state
|
||||
let mut inner = self.inner.lock();
|
||||
if let Some(allocation) = inner.allocations.remove(&(ptr as usize)) {
|
||||
inner
|
||||
.allocator
|
||||
.free(allocation)
|
||||
.expect("Vulkan free failed");
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ where
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(DeviceRepr)]
|
||||
pub struct Array2D<T> {
|
||||
pub device: DeviceArray2D<T>,
|
||||
pub values: Vec<T>,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::sync::OnceLock;
|
|||
|
||||
static SEARCH_DIRECTORY: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
fn set_search_directory(filename: &str) {
|
||||
pub fn set_search_directory(filename: &str) {
|
||||
let path = Path::new(filename);
|
||||
let dir = if path.is_dir() {
|
||||
path.to_path_buf()
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
||||
515
src/utils/mod.rs
515
src/utils/mod.rs
|
|
@ -12,7 +12,7 @@ pub mod parser;
|
|||
pub mod sampling;
|
||||
pub mod strings;
|
||||
|
||||
pub use arena::Upload;
|
||||
pub use arena::DeviceRepr;
|
||||
pub use error::FileLoc;
|
||||
pub use file::{read_float_file, resolve_filename};
|
||||
pub use parameters::{
|
||||
|
|
@ -28,3 +28,516 @@ pub type Arena = arena::Arena<backend::cuda::CudaAllocator>;
|
|||
|
||||
#[cfg(not(any(feature = "cuda", feature = "vulkan")))]
|
||||
pub type Arena = arena::Arena<backend::SystemAllocator>;
|
||||
|
||||
/// # Enum variant attributes
|
||||
///
|
||||
/// | Attribute | Effect |
|
||||
/// |-----------|--------|
|
||||
/// | *(none)* | Inner type has `DeviceRepr`; auto-call `upload_value` |
|
||||
/// | `#[device(clone)]` | Same type on both sides, just clone |
|
||||
/// | `#[device(custom = "method")]` | You provide `fn method(inner: &T, arena) -> DeviceT` |
|
||||
/// | `#[device(variant_type = "T")]` | Override the device-side variant's inner type |
|
||||
///
|
||||
/// # Container attribute
|
||||
///
|
||||
/// `#[device(name = "DeviceFoo")]` — override the generated type name (default: `Device{Name}`).
|
||||
#[proc_macro_derive(Device, attributes(device))]
|
||||
pub fn derive_device(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
match derive_impl(input) {
|
||||
Ok(tokens) => tokens.into(),
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream2> {
|
||||
match &input.data {
|
||||
Data::Struct(_) => derive_struct(input),
|
||||
Data::Enum(_) => derive_enum(input),
|
||||
Data::Union(_) => Err(syn::Error::new_spanned(
|
||||
&input.ident,
|
||||
"Device derive does not support unions",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// Struct derivation
|
||||
|
||||
fn derive_struct(input: DeriveInput) -> syn::Result<TokenStream2> {
|
||||
let host_name = &input.ident;
|
||||
let vis = &input.vis;
|
||||
let device_name = get_device_name(&input.attrs, host_name)?;
|
||||
|
||||
let fields = match &input.data {
|
||||
Data::Struct(s) => match &s.fields {
|
||||
Fields::Named(named) => &named.named,
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
host_name,
|
||||
"Device derive only supports structs with named fields",
|
||||
))
|
||||
}
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut device_fields = Vec::new();
|
||||
let mut upload_stmts = Vec::new();
|
||||
let mut device_field_inits = Vec::new();
|
||||
let mut spread_expr: Option<Expr> = None;
|
||||
|
||||
for field in fields {
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
let attrs = parse_field_attrs(&field.attrs)?;
|
||||
|
||||
if attrs.skip {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(ref expr_str) = attrs.spread {
|
||||
spread_expr = Some(syn::parse_str(expr_str).map_err(|e| {
|
||||
syn::Error::new_spanned(field, format!("invalid device(spread): {}", e))
|
||||
})?);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(expr_str) = &attrs.expr {
|
||||
let expr: Expr = syn::parse_str(expr_str).map_err(|e| {
|
||||
syn::Error::new_spanned(field, format!("invalid device(expr): {}", e))
|
||||
})?;
|
||||
let ty = &field.ty;
|
||||
device_fields.push(quote! { pub #field_name: #ty });
|
||||
upload_stmts.push(quote! { let #field_name = #expr; });
|
||||
device_field_inits.push(quote! { #field_name });
|
||||
continue;
|
||||
}
|
||||
|
||||
match classify_type(&field.ty) {
|
||||
FieldClass::VecCopy(inner_ty) => {
|
||||
let len_name = format_ident!("{}_len", field_name);
|
||||
device_fields.push(quote! { pub #field_name: Ptr<#inner_ty> });
|
||||
device_fields.push(quote! { pub #len_name: usize });
|
||||
upload_stmts.push(quote! {
|
||||
let (#field_name, #len_name) = arena.alloc_slice(&self.#field_name);
|
||||
});
|
||||
device_field_inits.push(quote! { #field_name });
|
||||
device_field_inits.push(quote! { #len_name });
|
||||
}
|
||||
FieldClass::VecUploadable(inner_ty) => {
|
||||
let len_name = format_ident!("{}_len", field_name);
|
||||
device_fields.push(quote! {
|
||||
pub #field_name: Ptr<<#inner_ty as DeviceRepr>::Target>
|
||||
});
|
||||
device_fields.push(quote! { pub #len_name: usize });
|
||||
upload_stmts.push(quote! {
|
||||
let __up: Vec<<#inner_ty as DeviceRepr>::Target> = self.#field_name
|
||||
.iter()
|
||||
.map(|item| DeviceRepr::upload_value(item, arena))
|
||||
.collect();
|
||||
let (#field_name, #len_name) = arena.alloc_slice(&__up);
|
||||
});
|
||||
device_field_inits.push(quote! { #field_name });
|
||||
device_field_inits.push(quote! { #len_name });
|
||||
}
|
||||
FieldClass::Option(inner_ty) => {
|
||||
device_fields.push(quote! {
|
||||
pub #field_name: Ptr<<#inner_ty as DeviceRepr>::Target>
|
||||
});
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = match &self.#field_name {
|
||||
Some(val) => DeviceRepr::upload(val, arena),
|
||||
None => Ptr::null(),
|
||||
};
|
||||
});
|
||||
device_field_inits.push(quote! { #field_name });
|
||||
}
|
||||
FieldClass::Arc(inner_ty) => {
|
||||
if attrs.flatten {
|
||||
device_fields.push(quote! {
|
||||
pub #field_name: <#inner_ty as DeviceRepr>::Target
|
||||
});
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = DeviceRepr::upload_value(&*self.#field_name, arena);
|
||||
});
|
||||
} else {
|
||||
device_fields.push(quote! {
|
||||
pub #field_name: Ptr<<#inner_ty as DeviceRepr>::Target>
|
||||
});
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = DeviceRepr::upload(&*self.#field_name, arena);
|
||||
});
|
||||
}
|
||||
device_field_inits.push(quote! { #field_name });
|
||||
}
|
||||
FieldClass::Plain => {
|
||||
let ty = &field.ty;
|
||||
if attrs.copy_upload {
|
||||
device_fields.push(quote! { pub #field_name: #ty });
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = self.#field_name.clone();
|
||||
});
|
||||
} else if attrs.flatten {
|
||||
device_fields.push(quote! {
|
||||
pub #field_name: <#ty as DeviceRepr>::Target
|
||||
});
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = DeviceRepr::upload_value(&self.#field_name, arena);
|
||||
});
|
||||
} else if attrs.upload {
|
||||
device_fields.push(quote! {
|
||||
pub #field_name: Ptr<<#ty as DeviceRepr>::Target>
|
||||
});
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = DeviceRepr::upload(&self.#field_name, arena);
|
||||
});
|
||||
} else {
|
||||
device_fields.push(quote! { pub #field_name: #ty });
|
||||
upload_stmts.push(quote! {
|
||||
let #field_name = self.#field_name;
|
||||
});
|
||||
}
|
||||
device_field_inits.push(quote! { #field_name });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let constructor = if let Some(spread) = spread_expr {
|
||||
quote! {
|
||||
#device_name {
|
||||
#(#device_field_inits,)*
|
||||
..#spread
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#device_name {
|
||||
#(#device_field_inits,)*
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#vis struct #device_name {
|
||||
#(#device_fields,)*
|
||||
}
|
||||
|
||||
unsafe impl Send for #device_name {}
|
||||
unsafe impl Sync for #device_name {}
|
||||
|
||||
impl DeviceRepr for #host_name {
|
||||
type Target = #device_name;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> Self::Target {
|
||||
#(#upload_stmts)*
|
||||
#constructor
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Enum derivation
|
||||
fn derive_enum(input: DeriveInput) -> syn::Result<TokenStream2> {
|
||||
let host_name = &input.ident;
|
||||
let vis = &input.vis;
|
||||
let device_name = get_device_name(&input.attrs, host_name)?;
|
||||
|
||||
let variants = match &input.data {
|
||||
Data::Enum(e) => &e.variants,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let mut device_variants = Vec::new();
|
||||
let mut match_arms = Vec::new();
|
||||
|
||||
for variant in variants {
|
||||
let var_name = &variant.ident;
|
||||
let var_attrs = parse_variant_attrs(&variant.attrs)?;
|
||||
let inner_ty = get_variant_inner_type(variant)?;
|
||||
|
||||
// Determine the device-side inner type for this variant
|
||||
let device_inner: Type = if let Some(ref ty_str) = var_attrs.variant_type {
|
||||
syn::parse_str(ty_str).map_err(|e| {
|
||||
syn::Error::new_spanned(variant, format!("invalid variant_type: {}", e))
|
||||
})?
|
||||
} else if var_attrs.clone_variant {
|
||||
// clone: same type on both sides
|
||||
inner_ty.clone()
|
||||
} else {
|
||||
// auto-upload: use DeviceRepr::Target
|
||||
syn::parse_str(&format!("<{} as DeviceRepr>::Target", quote!(#inner_ty))).map_err(
|
||||
|e| {
|
||||
syn::Error::new_spanned(variant, format!("cannot construct Target type: {}", e))
|
||||
},
|
||||
)?
|
||||
};
|
||||
|
||||
device_variants.push(quote! { #var_name(#device_inner) });
|
||||
|
||||
if var_attrs.clone_variant {
|
||||
match_arms.push(quote! {
|
||||
#host_name::#var_name(inner) => #device_name::#var_name(inner.clone())
|
||||
});
|
||||
} else if let Some(ref method) = var_attrs.custom {
|
||||
let method_ident = format_ident!("{}", method);
|
||||
match_arms.push(quote! {
|
||||
#host_name::#var_name(inner) => {
|
||||
#device_name::#var_name(Self::#method_ident(inner, arena))
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Default: inner implements DeviceRepr
|
||||
match_arms.push(quote! {
|
||||
#host_name::#var_name(inner) => {
|
||||
#device_name::#var_name(DeviceRepr::upload_value(inner, arena))
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(quote! {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#vis enum #device_name {
|
||||
#(#device_variants,)*
|
||||
}
|
||||
|
||||
unsafe impl Send for #device_name {}
|
||||
unsafe impl Sync for #device_name {}
|
||||
|
||||
impl DeviceRepr for #host_name {
|
||||
type Target = #device_name;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> Self::Target {
|
||||
match self {
|
||||
#(#match_arms,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_variant_inner_type(variant: &Variant) -> syn::Result<Type> {
|
||||
match &variant.fields {
|
||||
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
|
||||
Ok(fields.unnamed.first().unwrap().ty.clone())
|
||||
}
|
||||
Fields::Unit => Err(syn::Error::new_spanned(
|
||||
variant,
|
||||
"Device derive: enum variants must have exactly one field, e.g. Variant(Type)",
|
||||
)),
|
||||
_ => Err(syn::Error::new_spanned(
|
||||
variant,
|
||||
"Device derive: only single-field tuple variants supported, e.g. Variant(Type)",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute parsing for variants
|
||||
struct VariantAttrs {
|
||||
clone_variant: bool,
|
||||
custom: Option<String>,
|
||||
variant_type: Option<String>,
|
||||
}
|
||||
|
||||
fn parse_variant_attrs(attrs: &[Attribute]) -> syn::Result<VariantAttrs> {
|
||||
let mut result = VariantAttrs {
|
||||
clone_variant: false,
|
||||
custom: None,
|
||||
variant_type: None,
|
||||
};
|
||||
|
||||
for attr in attrs {
|
||||
if !attr.path().is_ident("device") {
|
||||
continue;
|
||||
}
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("clone") {
|
||||
result.clone_variant = true;
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("custom") {
|
||||
let value = meta.value()?;
|
||||
let lit: Lit = value.parse()?;
|
||||
if let Lit::Str(s) = lit {
|
||||
result.custom = Some(s.value());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error("expected string literal"))
|
||||
}
|
||||
} else if meta.path.is_ident("variant_type") {
|
||||
let value = meta.value()?;
|
||||
let lit: Lit = value.parse()?;
|
||||
if let Lit::Str(s) = lit {
|
||||
result.variant_type = Some(s.value());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error("expected string literal"))
|
||||
}
|
||||
} else {
|
||||
Err(meta.error("unknown device variant attribute"))
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// Attribute parsing for fields
|
||||
struct FieldAttrs {
|
||||
skip: bool,
|
||||
expr: Option<String>,
|
||||
copy_upload: bool,
|
||||
flatten: bool,
|
||||
upload: bool,
|
||||
spread: Option<String>,
|
||||
}
|
||||
|
||||
fn parse_field_attrs(attrs: &[Attribute]) -> syn::Result<FieldAttrs> {
|
||||
let mut result = FieldAttrs {
|
||||
skip: false,
|
||||
expr: None,
|
||||
copy_upload: false,
|
||||
flatten: false,
|
||||
upload: false,
|
||||
spread: None,
|
||||
};
|
||||
|
||||
for attr in attrs {
|
||||
if !attr.path().is_ident("device") {
|
||||
continue;
|
||||
}
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("skip") {
|
||||
result.skip = true;
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("expr") {
|
||||
let value = meta.value()?;
|
||||
let lit: Lit = value.parse()?;
|
||||
if let Lit::Str(s) = lit {
|
||||
result.expr = Some(s.value());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error("expected string literal"))
|
||||
}
|
||||
} else if meta.path.is_ident("copy_upload") {
|
||||
result.copy_upload = true;
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("flatten") {
|
||||
result.flatten = true;
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("upload") {
|
||||
result.upload = true;
|
||||
Ok(())
|
||||
} else if meta.path.is_ident("spread") {
|
||||
let value = meta.value()?;
|
||||
let lit: Lit = value.parse()?;
|
||||
if let Lit::Str(s) = lit {
|
||||
result.spread = Some(s.value());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error("expected string literal"))
|
||||
}
|
||||
} else {
|
||||
Err(meta.error("unknown device attribute"))
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// Container-level name attribute
|
||||
fn get_device_name(attrs: &[Attribute], host_name: &Ident) -> syn::Result<Ident> {
|
||||
for attr in attrs {
|
||||
if !attr.path().is_ident("device") {
|
||||
continue;
|
||||
}
|
||||
let mut name = None;
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("name") {
|
||||
let value = meta.value()?;
|
||||
let lit: Lit = value.parse()?;
|
||||
if let Lit::Str(s) = lit {
|
||||
name = Some(format_ident!("{}", s.value()));
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error("expected string literal"))
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
if let Some(n) = name {
|
||||
return Ok(n);
|
||||
}
|
||||
}
|
||||
Ok(format_ident!("Device{}", host_name))
|
||||
}
|
||||
|
||||
// Type classification
|
||||
enum FieldClass {
|
||||
VecCopy(Type),
|
||||
VecUploadable(Type),
|
||||
Option(Type),
|
||||
Arc(Type),
|
||||
Plain,
|
||||
}
|
||||
|
||||
fn classify_type(ty: &Type) -> FieldClass {
|
||||
if let Some(inner) = extract_generic_arg(ty, "Vec") {
|
||||
if is_copy_primitive(&inner) {
|
||||
FieldClass::VecCopy(inner)
|
||||
} else {
|
||||
FieldClass::VecUploadable(inner)
|
||||
}
|
||||
} else if let Some(inner) = extract_generic_arg(ty, "Option") {
|
||||
FieldClass::Option(inner)
|
||||
} else if let Some(inner) = extract_generic_arg(ty, "Arc") {
|
||||
FieldClass::Arc(inner)
|
||||
} else {
|
||||
FieldClass::Plain
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_generic_arg(ty: &Type, wrapper: &str) -> Option<Type> {
|
||||
if let Type::Path(type_path) = ty {
|
||||
let seg = type_path.path.segments.last()?;
|
||||
if seg.ident != wrapper {
|
||||
return None;
|
||||
}
|
||||
if let PathArguments::AngleBracketed(args) = &seg.arguments {
|
||||
if let Some(GenericArgument::Type(inner)) = args.args.first() {
|
||||
return Some(inner.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_copy_primitive(ty: &Type) -> bool {
|
||||
if let Type::Path(type_path) = ty {
|
||||
if let Some(seg) = type_path.path.segments.last() {
|
||||
let name = seg.ident.to_string();
|
||||
return matches!(
|
||||
name.as_str(),
|
||||
"f32"
|
||||
| "f64"
|
||||
| "u8"
|
||||
| "u16"
|
||||
| "u32"
|
||||
| "u64"
|
||||
| "i8"
|
||||
| "i16"
|
||||
| "i32"
|
||||
| "i64"
|
||||
| "usize"
|
||||
| "isize"
|
||||
| "bool"
|
||||
| "Float"
|
||||
);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ use crate::core::texture::{FloatTexture, SpectrumTexture};
|
|||
use crate::spectra::data::get_named_spectrum;
|
||||
use crate::spectra::piecewise::PiecewiseLinearSpectrumBuffer;
|
||||
use crate::utils::FileLoc;
|
||||
use anyhow::{Result, bail};
|
||||
use shared::Float;
|
||||
use anyhow::{bail, Result};
|
||||
use shared::core::color::RGB;
|
||||
use shared::core::geometry::{Normal3f, Point2f, Point3f, Vector2f, Vector3f};
|
||||
use shared::core::spectrum::Spectrum;
|
||||
|
|
@ -13,11 +12,12 @@ use shared::spectra::{
|
|||
PiecewiseLinearSpectrum, RGBAlbedoSpectrum, RGBColorSpace, RGBIlluminantSpectrum,
|
||||
RGBUnboundedSpectrum,
|
||||
};
|
||||
use shared::Float;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
pub fn error_exit(loc: Option<&FileLoc>, message: &str) -> String {
|
||||
|
|
@ -350,9 +350,9 @@ impl ParameterDictionary {
|
|||
where
|
||||
T: PBRTParameter,
|
||||
{
|
||||
let param = self.params[0].clone();
|
||||
for param in &self.params {
|
||||
if param.name == name && param.type_name == T::TYPE_NAME {
|
||||
let values = T::get_values(¶m);
|
||||
let values = T::get_values(param);
|
||||
|
||||
if values.is_empty() {
|
||||
bail!(
|
||||
|
|
@ -375,6 +375,7 @@ impl ParameterDictionary {
|
|||
param.looked_up.store(true, Ordering::Relaxed);
|
||||
return Ok(T::convert(values));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(default_val)
|
||||
}
|
||||
|
|
@ -512,7 +513,7 @@ impl ParameterDictionary {
|
|||
|
||||
pub fn get_texture(&self, name: &str) -> String {
|
||||
for p in &self.params {
|
||||
if p.name == name || p.type_name != "texture" {
|
||||
if p.name == name && p.type_name == "texture" {
|
||||
if p.strings.len() != 1 {
|
||||
panic!(
|
||||
"[{:?}] Expected 1 texture name for {}, found {}",
|
||||
|
|
@ -525,7 +526,7 @@ impl ParameterDictionary {
|
|||
return p.strings[0].clone();
|
||||
}
|
||||
}
|
||||
return "".to_string();
|
||||
String::new()
|
||||
}
|
||||
|
||||
pub fn report_unused(&self) {
|
||||
|
|
|
|||
|
|
@ -385,6 +385,7 @@ impl Tokenizer {
|
|||
if first_char == '"' {
|
||||
self.advance();
|
||||
let mut val = String::new();
|
||||
val.push('"');
|
||||
|
||||
loop {
|
||||
let ch = self.advance().ok_or(ParserError::UnexpectedEof)?;
|
||||
|
|
@ -406,6 +407,8 @@ impl Tokenizer {
|
|||
val.push(ch);
|
||||
}
|
||||
}
|
||||
val.push('"');
|
||||
|
||||
return Ok(Some(Token {
|
||||
text: val,
|
||||
loc: start_loc,
|
||||
|
|
@ -785,6 +788,8 @@ impl<'a> SceneParser<'a> {
|
|||
.unwrap_or(Path::new("."))
|
||||
.to_path_buf();
|
||||
|
||||
crate::utils::file::set_search_directory(current_dir.to_str().unwrap_or("."));
|
||||
|
||||
Self {
|
||||
target,
|
||||
file_stack: vec![root],
|
||||
|
|
|
|||
|
|
@ -2,54 +2,36 @@ use crate::core::image::Image;
|
|||
use crate::utils::arena::Arena;
|
||||
use crate::utils::backend::GpuAllocator;
|
||||
use crate::utils::containers::Array2D;
|
||||
use shared::Float;
|
||||
use shared::core::geometry::{Bounds2f, Point2i, Vector2f, Vector2i};
|
||||
use shared::utils::sampling::{
|
||||
AliasTable, Bin, DevicePiecewiseConstant1D, DevicePiecewiseConstant2D, DeviceSummedAreaTable,
|
||||
DeviceWindowedPiecewiseConstant2D, PiecewiseLinear2D,
|
||||
};
|
||||
use shared::utils::{Ptr, gpu_array_from_fn};
|
||||
use shared::utils::{gpu_array_from_fn, Ptr};
|
||||
use shared::Float;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PiecewiseConstant1D {
|
||||
func: Box<[Float]>,
|
||||
cdf: Box<[Float]>,
|
||||
pub device: DevicePiecewiseConstant1D,
|
||||
func: Vec<Float>,
|
||||
cdf: Vec<Float>,
|
||||
pub min: Float,
|
||||
pub max: Float,
|
||||
}
|
||||
|
||||
impl PiecewiseConstant1D {
|
||||
// Constructors
|
||||
pub fn new(f: &[Float]) -> Self {
|
||||
Self::new_with_bounds(f.to_vec(), 0.0, 1.0)
|
||||
}
|
||||
|
||||
pub fn to_shared<A: GpuAllocator>(&self, arena: &Arena<A>) -> DevicePiecewiseConstant1D {
|
||||
let (func_ptr, _) = arena.alloc_slice(&self.func);
|
||||
let (cdf_ptr, _) = arena.alloc_slice(&self.cdf);
|
||||
|
||||
DevicePiecewiseConstant1D {
|
||||
func: func_ptr,
|
||||
cdf: cdf_ptr,
|
||||
func_integral: self.func_integral,
|
||||
n: self.func.len() as u32,
|
||||
min: self.min,
|
||||
max: self.max,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_func<F>(f: F, min: Float, max: Float, n: usize) -> Self
|
||||
where
|
||||
F: Fn(Float) -> Float,
|
||||
{
|
||||
let delta = (max - min) / n as Float;
|
||||
let values: Vec<Float> = (0..n)
|
||||
.map(|i| {
|
||||
let x = min + (i as Float + 0.5) * delta;
|
||||
f(x)
|
||||
})
|
||||
.map(|i| f(min + (i as Float + 0.5) * delta))
|
||||
.collect();
|
||||
|
||||
Self::new_with_bounds(values, min, max)
|
||||
}
|
||||
|
||||
|
|
@ -64,74 +46,92 @@ impl PiecewiseConstant1D {
|
|||
}
|
||||
|
||||
let func_integral = cdf[n];
|
||||
|
||||
if func_integral > 0.0 {
|
||||
for c in &mut cdf {
|
||||
*c /= func_integral;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to boxed slices (no more reallocation possible)
|
||||
let func: Box<[Float]> = f.into_boxed_slice();
|
||||
let cdf: Box<[Float]> = cdf.into_boxed_slice();
|
||||
|
||||
let device = DevicePiecewiseConstant1D {
|
||||
func: func.as_ptr().into(),
|
||||
cdf: cdf.as_ptr().into(),
|
||||
min,
|
||||
max,
|
||||
n: n as u32,
|
||||
func_integral,
|
||||
};
|
||||
|
||||
Self { func, cdf, device }
|
||||
Self { func: f, cdf, min, max }
|
||||
}
|
||||
|
||||
// Accessors
|
||||
pub fn min(&self) -> Float {
|
||||
self.device.min
|
||||
}
|
||||
pub fn max(&self) -> Float {
|
||||
self.device.max
|
||||
}
|
||||
pub fn n(&self) -> usize {
|
||||
self.device.n as usize
|
||||
}
|
||||
pub fn n(&self) -> usize { self.func.len() }
|
||||
pub fn func(&self) -> &[Float] { &self.func }
|
||||
pub fn cdf(&self) -> &[Float] { &self.cdf }
|
||||
|
||||
pub fn integral(&self) -> Float {
|
||||
self.device.func_integral
|
||||
// func_integral is the un-normalized sum. After normalization cdf[n] == 1.0,
|
||||
// so we reconstruct from the last CDF entry before normalization.
|
||||
// But since we normalized in-place, we need to store it. Let's compute it.
|
||||
let n = self.func.len();
|
||||
let delta = (self.max - self.min) / n as Float;
|
||||
self.func.iter().sum::<Float>() * delta
|
||||
}
|
||||
|
||||
pub fn func(&self) -> &[Float] {
|
||||
&self.func
|
||||
/// Host-side sampling (for scene construction, not rendering).
|
||||
/// During rendering, use the device struct via arena-uploaded Ptrs.
|
||||
pub fn sample_host(&self, u: Float) -> (Float, Float, usize) {
|
||||
let n = self.func.len();
|
||||
let offset = self.find_interval_host(u);
|
||||
let cdf_offset = self.cdf[offset];
|
||||
let cdf_next = self.cdf[offset + 1];
|
||||
let du = if cdf_next - cdf_offset > 0.0 {
|
||||
(u - cdf_offset) / (cdf_next - cdf_offset)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let delta = (self.max - self.min) / n as Float;
|
||||
let x = self.min + (offset as Float + du) * delta;
|
||||
let func_integral = self.integral();
|
||||
let pdf = if func_integral > 0.0 {
|
||||
self.func[offset] / func_integral
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
(x, pdf, offset)
|
||||
}
|
||||
pub fn cdf(&self) -> &[Float] {
|
||||
&self.cdf
|
||||
|
||||
fn find_interval_host(&self, u: Float) -> usize {
|
||||
let n = self.func.len();
|
||||
let mut size = n;
|
||||
let mut first = 0usize;
|
||||
while size > 0 {
|
||||
let half = size >> 1;
|
||||
let middle = first + half;
|
||||
if self.cdf[middle] <= u {
|
||||
first = middle + 1;
|
||||
size -= half + 1;
|
||||
} else {
|
||||
size = half;
|
||||
}
|
||||
}
|
||||
first.saturating_sub(1).min(n - 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for PiecewiseConstant1D {
|
||||
type Target = DevicePiecewiseConstant1D;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.device
|
||||
}
|
||||
#[derive(DeviceRepr)]
|
||||
#[device(name = "DevicePiecewiseConstant1D")]
|
||||
pub struct PiecewiseConstant1D {
|
||||
pub func: Vec<Float>,
|
||||
pub cdf: Vec<Float>,
|
||||
pub min: Float,
|
||||
pub max: Float,
|
||||
pub n: u32,
|
||||
#[device(expr = "self.integral()")]
|
||||
pub func_integral: Float,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone, Debug, DeviceRepr)]
|
||||
#[device(name = "DevicePiecewiseConstant2D")]
|
||||
pub struct PiecewiseConstant2D {
|
||||
pub conditionals: Vec<PiecewiseConstant1D>,
|
||||
#[device(flatten)]
|
||||
pub marginal: PiecewiseConstant1D,
|
||||
pub conditional_devices: Box<[DevicePiecewiseConstant1D]>,
|
||||
pub device: DevicePiecewiseConstant2D,
|
||||
pub n_u: u32,
|
||||
pub n_v: u32,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for PiecewiseConstant2D {
|
||||
type Target = DevicePiecewiseConstant2D;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl PiecewiseConstant2D {
|
||||
pub fn new(data: &Array2D<Float>) -> Self {
|
||||
|
|
@ -141,8 +141,8 @@ impl PiecewiseConstant2D {
|
|||
pub fn new_with_bounds(data: &Array2D<Float>, domain: Bounds2f) -> Self {
|
||||
Self::from_slice(
|
||||
data.as_slice(),
|
||||
data.x_size() as usize,
|
||||
data.y_size() as usize,
|
||||
data.x_size(),
|
||||
data.y_size(),
|
||||
domain,
|
||||
)
|
||||
}
|
||||
|
|
@ -154,36 +154,23 @@ impl PiecewiseConstant2D {
|
|||
let mut marginal_func = Vec::with_capacity(n_v);
|
||||
|
||||
for v in 0..n_v {
|
||||
let row_start = v * n_u;
|
||||
let row: Vec<Float> = data[row_start..row_start + n_u].to_vec();
|
||||
let conditional =
|
||||
PiecewiseConstant1D::new_with_bounds(row, domain.p_min.x(), domain.p_max.x());
|
||||
let row = data[v * n_u..(v + 1) * n_u].to_vec();
|
||||
let conditional = PiecewiseConstant1D::new_with_bounds(
|
||||
row,
|
||||
domain.p_min.x(),
|
||||
domain.p_max.x(),
|
||||
);
|
||||
marginal_func.push(conditional.integral());
|
||||
conditionals.push(conditional);
|
||||
}
|
||||
|
||||
let marginal =
|
||||
PiecewiseConstant1D::new_with_bounds(marginal_func, domain.p_min.y(), domain.p_max.y());
|
||||
let marginal = PiecewiseConstant1D::new_with_bounds(
|
||||
marginal_func,
|
||||
domain.p_min.y(),
|
||||
domain.p_max.y(),
|
||||
);
|
||||
|
||||
let conditional_devices: Box<[DevicePiecewiseConstant1D]> = conditionals
|
||||
.iter()
|
||||
.map(|c| c.device)
|
||||
.collect::<Vec<_>>()
|
||||
.into_boxed_slice();
|
||||
|
||||
let device = DevicePiecewiseConstant2D {
|
||||
conditionals: conditional_devices.as_ptr().into(),
|
||||
marginal: marginal.device,
|
||||
n_u: n_u as u32,
|
||||
n_v: n_v as u32,
|
||||
};
|
||||
|
||||
Self {
|
||||
conditionals,
|
||||
marginal,
|
||||
conditional_devices,
|
||||
device,
|
||||
}
|
||||
Self { conditionals, marginal, n_u, n_v }
|
||||
}
|
||||
|
||||
pub fn from_image(image: &Image) -> Self {
|
||||
|
|
@ -192,12 +179,9 @@ impl PiecewiseConstant2D {
|
|||
let n_v = res.y() as usize;
|
||||
|
||||
let mut data = Vec::with_capacity(n_u * n_v);
|
||||
|
||||
for v in 0..n_v {
|
||||
for u in 0..n_u {
|
||||
let p = Point2i::new(u as i32, v as i32);
|
||||
let luminance = image.get_channels(p).average();
|
||||
data.push(luminance);
|
||||
data.push(image.get_channels(Point2i::new(u as i32, v as i32)).average());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -217,10 +201,14 @@ struct PiecewiseLinear2DStorage<const N: usize> {
|
|||
}
|
||||
|
||||
pub struct PiecewiseLinear2DHost<const N: usize> {
|
||||
pub view: PiecewiseLinear2D<N>,
|
||||
_storage: Arc<PiecewiseLinear2DStorage<N>>,
|
||||
size: Vector2i,
|
||||
inv_patch_size: Vector2f,
|
||||
param_size: [u32; N],
|
||||
param_strides: [u32; N],
|
||||
storage: Arc<PiecewiseLinear2DStorage<N>>,
|
||||
}
|
||||
|
||||
|
||||
impl<const N: usize> PiecewiseLinear2DHost<N> {
|
||||
pub fn new(
|
||||
data: &[Float],
|
||||
|
|
@ -354,27 +342,49 @@ impl<const N: usize> PiecewiseLinear2DHost<N> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> DeviceRepr for PiecewiseLinear2DHost<N> {
|
||||
type Target = PiecewiseLinear2D<N>;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> PiecewiseLinear2D<N> {
|
||||
let s = &self.storage;
|
||||
|
||||
let (data_ptr, _) = arena.alloc_slice(&s.data);
|
||||
let (marginal_ptr, _) = arena.alloc_slice(&s.marginal_cdf);
|
||||
let (conditional_ptr, _) = arena.alloc_slice(&s.conditional_cdf);
|
||||
|
||||
let param_ptrs: [Ptr<Float>; N] = std::array::from_fn(|i| {
|
||||
let (ptr, _) = arena.alloc_slice(&s.param_values[i]);
|
||||
ptr
|
||||
});
|
||||
|
||||
PiecewiseLinear2D {
|
||||
size: self.size,
|
||||
inv_patch_size: self.inv_patch_size,
|
||||
param_size: self.param_size,
|
||||
param_strides: self.param_strides,
|
||||
param_values: param_ptrs,
|
||||
data: data_ptr,
|
||||
marginal_cdf: marginal_ptr,
|
||||
conditional_cdf: conditional_ptr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AliasTableHost {
|
||||
pub view: AliasTable,
|
||||
_storage: Vec<Bin>,
|
||||
bins: Vec<Bin>,
|
||||
}
|
||||
|
||||
impl AliasTableHost {
|
||||
pub fn new(weights: &[Float]) -> Self {
|
||||
let n = weights.len();
|
||||
if n == 0 {
|
||||
return Self {
|
||||
view: AliasTable {
|
||||
bins: Ptr::null(),
|
||||
size: 0,
|
||||
},
|
||||
_storage: Vec::new(),
|
||||
};
|
||||
return Self { bins: Vec::new() };
|
||||
}
|
||||
|
||||
let sum: f64 = weights.iter().map(|&w| w as f64).sum();
|
||||
assert!(sum > 0.0, "Sum of weights must be positive");
|
||||
|
||||
let mut bins = Vec::with_capacity(n);
|
||||
for &w in weights {
|
||||
bins.push(Bin {
|
||||
|
|
@ -384,10 +394,7 @@ impl AliasTableHost {
|
|||
});
|
||||
}
|
||||
|
||||
struct Outcome {
|
||||
p_hat: f64,
|
||||
index: usize,
|
||||
}
|
||||
struct Outcome { p_hat: f64, index: usize }
|
||||
|
||||
let mut under = Vec::with_capacity(n);
|
||||
let mut over = Vec::with_capacity(n);
|
||||
|
|
@ -409,17 +416,10 @@ impl AliasTableHost {
|
|||
bins[un.index].alias = ov.index as u32;
|
||||
|
||||
let p_excess = un.p_hat + ov.p_hat - 1.0;
|
||||
|
||||
if p_excess < 1.0 {
|
||||
under.push(Outcome {
|
||||
p_hat: p_excess,
|
||||
index: ov.index,
|
||||
});
|
||||
under.push(Outcome { p_hat: p_excess, index: ov.index });
|
||||
} else {
|
||||
over.push(Outcome {
|
||||
p_hat: p_excess,
|
||||
index: ov.index,
|
||||
});
|
||||
over.push(Outcome { p_hat: p_excess, index: ov.index });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -427,37 +427,40 @@ impl AliasTableHost {
|
|||
bins[ov.index].q = 1.0;
|
||||
bins[ov.index].alias = ov.index as u32;
|
||||
}
|
||||
|
||||
while let Some(un) = under.pop() {
|
||||
bins[un.index].q = 1.0;
|
||||
bins[un.index].alias = un.index as u32;
|
||||
}
|
||||
|
||||
let view = AliasTable {
|
||||
bins: bins.as_ptr().into(),
|
||||
size: bins.len() as u32,
|
||||
};
|
||||
Self { bins }
|
||||
}
|
||||
|
||||
Self {
|
||||
view,
|
||||
_storage: bins,
|
||||
pub fn size(&self) -> usize { self.bins.len() }
|
||||
pub fn is_empty(&self) -> bool { self.bins.is_empty() }
|
||||
}
|
||||
|
||||
impl DeviceRepr for AliasTableHost {
|
||||
type Target = AliasTable;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> AliasTable {
|
||||
if self.bins.is_empty() {
|
||||
return AliasTable { bins: Ptr::null(), size: 0 };
|
||||
}
|
||||
let (bins_ptr, _) = arena.alloc_slice(&self.bins);
|
||||
AliasTable {
|
||||
bins: bins_ptr,
|
||||
size: self.bins.len() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, DeviceRepr)]
|
||||
#[device(name = "DeviceSummedAreaTable")]
|
||||
pub struct SummedAreaTable {
|
||||
pub device: DeviceSummedAreaTable,
|
||||
#[device(flatten)]
|
||||
sum: Array2D<f64>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for SummedAreaTable {
|
||||
type Target = DeviceSummedAreaTable;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl SummedAreaTable {
|
||||
pub fn new(values: &Array2D<Float>) -> Self {
|
||||
let width = values.x_size() as i32;
|
||||
|
|
@ -469,46 +472,194 @@ impl SummedAreaTable {
|
|||
for x in 1..width {
|
||||
sum[(x, 0)] = values[(x, 0)] as f64 + sum[(x - 1, 0)];
|
||||
}
|
||||
|
||||
for y in 1..height {
|
||||
sum[(0, y)] = values[(0, y)] as f64 + sum[(0, y - 1)];
|
||||
}
|
||||
|
||||
for y in 1..height {
|
||||
for x in 1..width {
|
||||
let term = values[(x, y)] as f64;
|
||||
let left = sum[(x - 1, y)];
|
||||
let up = sum[(x, y - 1)];
|
||||
let diag = sum[(x - 1, y - 1)];
|
||||
|
||||
sum[(x, y)] = term + left + up - diag;
|
||||
sum[(x, y)] = values[(x, y)] as f64
|
||||
+ sum[(x - 1, y)]
|
||||
+ sum[(x, y - 1)]
|
||||
- sum[(x - 1, y - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
let device = DeviceSummedAreaTable { sum: *sum };
|
||||
Self { device, sum }
|
||||
Self { sum }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, DeviceRepr)]
|
||||
#[device(name = "DeviceWindowedPiecewiseConstant2D")]
|
||||
pub struct WindowedPiecewiseConstant2D {
|
||||
pub device: DeviceWindowedPiecewiseConstant2D,
|
||||
sat: DeviceSummedAreaTable,
|
||||
#[device(flatten)]
|
||||
sat: SummedAreaTable,
|
||||
#[device(flatten)]
|
||||
func: Array2D<Float>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for WindowedPiecewiseConstant2D {
|
||||
type Target = DeviceWindowedPiecewiseConstant2D;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowedPiecewiseConstant2D {
|
||||
pub fn new(func: Array2D<Float>) -> Self {
|
||||
let sat = *SummedAreaTable::new(&func);
|
||||
let device = DeviceWindowedPiecewiseConstant2D { sat, func: *func };
|
||||
let sat = SummedAreaTable::new(&func);
|
||||
Self { sat, func }
|
||||
}
|
||||
}
|
||||
|
||||
Self { sat, func, device }
|
||||
struct PiecewiseLinear2DStorage<const N: usize> {
|
||||
data: Vec<Float>,
|
||||
marginal_cdf: Vec<Float>,
|
||||
conditional_cdf: Vec<Float>,
|
||||
param_values: [Vec<Float>; N],
|
||||
}
|
||||
|
||||
pub struct PiecewiseLinear2DHost<const N: usize> {
|
||||
size: Vector2i,
|
||||
inv_patch_size: Vector2f,
|
||||
param_size: [u32; N],
|
||||
param_strides: [u32; N],
|
||||
storage: Arc<PiecewiseLinear2DStorage<N>>,
|
||||
}
|
||||
|
||||
impl<const N: usize> PiecewiseLinear2DHost<N> {
|
||||
pub fn new(
|
||||
data: &[Float],
|
||||
x_size: i32,
|
||||
y_size: i32,
|
||||
param_res: [usize; N],
|
||||
param_values: [&[Float]; N],
|
||||
normalize: bool,
|
||||
build_cdf: bool,
|
||||
) -> Self {
|
||||
if build_cdf && !normalize {
|
||||
panic!("PiecewiseLinear2D: build_cdf implies normalize=true");
|
||||
}
|
||||
|
||||
let size = Vector2i::new(x_size, y_size);
|
||||
let inv_patch_size = Vector2f::new(
|
||||
1.0 / (x_size - 1) as Float,
|
||||
1.0 / (y_size - 1) as Float,
|
||||
);
|
||||
|
||||
let mut param_size = [0u32; N];
|
||||
let mut param_strides = [0u32; N];
|
||||
let owned_param_values: [Vec<Float>; N] = gpu_array_from_fn(|i| param_values[i].to_vec());
|
||||
|
||||
let mut slices: u32 = 1;
|
||||
for i in (0..N).rev() {
|
||||
assert!(param_res[i] >= 1, "Parameter resolution must be >= 1");
|
||||
param_size[i] = param_res[i] as u32;
|
||||
param_strides[i] = if param_res[i] > 1 { slices } else { 0 };
|
||||
slices *= param_size[i];
|
||||
}
|
||||
|
||||
let n_values = (x_size * y_size) as usize;
|
||||
let mut new_data = vec![0.0; slices as usize * n_values];
|
||||
let mut marginal_cdf = if build_cdf {
|
||||
vec![0.0; slices as usize * y_size as usize]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let mut conditional_cdf = if build_cdf {
|
||||
vec![0.0; slices as usize * n_values]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let mut data_offset = 0;
|
||||
for slice in 0..slices as usize {
|
||||
let slice_offset = slice * n_values;
|
||||
let current_data = &data[data_offset..data_offset + n_values];
|
||||
let mut sum = 0.0_f64;
|
||||
|
||||
if normalize {
|
||||
for y in 0..(y_size - 1) {
|
||||
for x in 0..(x_size - 1) {
|
||||
let i = (y * x_size + x) as usize;
|
||||
let v00 = current_data[i] as f64;
|
||||
let v10 = current_data[i + 1] as f64;
|
||||
let v01 = current_data[i + x_size as usize] as f64;
|
||||
let v11 = current_data[i + 1 + x_size as usize] as f64;
|
||||
sum += 0.25 * (v00 + v10 + v01 + v11);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let normalization = if normalize && sum > 0.0 {
|
||||
1.0 / sum as Float
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
for k in 0..n_values {
|
||||
new_data[slice_offset + k] = current_data[k] * normalization;
|
||||
}
|
||||
|
||||
if build_cdf {
|
||||
let marginal_slice_offset = slice * y_size as usize;
|
||||
for y in 0..y_size as usize {
|
||||
let mut cdf_sum = 0.0;
|
||||
let i_base = y * x_size as usize;
|
||||
conditional_cdf[slice_offset + i_base] = 0.0;
|
||||
for x in 0..(x_size - 1) as usize {
|
||||
let i = i_base + x;
|
||||
cdf_sum += 0.5
|
||||
* (new_data[slice_offset + i] + new_data[slice_offset + i + 1]);
|
||||
conditional_cdf[slice_offset + i + 1] = cdf_sum;
|
||||
}
|
||||
}
|
||||
marginal_cdf[marginal_slice_offset] = 0.0;
|
||||
let mut marginal_sum = 0.0;
|
||||
for y in 0..(y_size - 1) as usize {
|
||||
let cdf1 =
|
||||
conditional_cdf[slice_offset + (y + 1) * x_size as usize - 1];
|
||||
let cdf2 =
|
||||
conditional_cdf[slice_offset + (y + 2) * x_size as usize - 1];
|
||||
marginal_sum += 0.5 * (cdf1 + cdf2);
|
||||
marginal_cdf[marginal_slice_offset + y + 1] = marginal_sum;
|
||||
}
|
||||
}
|
||||
data_offset += n_values;
|
||||
}
|
||||
|
||||
let storage = Arc::new(PiecewiseLinear2DStorage {
|
||||
data: new_data,
|
||||
marginal_cdf,
|
||||
conditional_cdf,
|
||||
param_values: owned_param_values,
|
||||
});
|
||||
|
||||
Self {
|
||||
size,
|
||||
inv_patch_size,
|
||||
param_size,
|
||||
param_strides,
|
||||
storage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> DeviceRepr for PiecewiseLinear2DHost<N> {
|
||||
type Target = PiecewiseLinear2D<N>;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> PiecewiseLinear2D<N> {
|
||||
let s = &self.storage;
|
||||
|
||||
let (data_ptr, _) = arena.alloc_slice(&s.data);
|
||||
let (marginal_ptr, _) = arena.alloc_slice(&s.marginal_cdf);
|
||||
let (conditional_ptr, _) = arena.alloc_slice(&s.conditional_cdf);
|
||||
|
||||
let param_ptrs: [Ptr<Float>; N] = std::array::from_fn(|i| {
|
||||
let (ptr, _) = arena.alloc_slice(&s.param_values[i]);
|
||||
ptr
|
||||
});
|
||||
|
||||
PiecewiseLinear2D {
|
||||
size: self.size,
|
||||
inv_patch_size: self.inv_patch_size,
|
||||
param_size: self.param_size,
|
||||
param_strides: self.param_strides,
|
||||
param_values: param_ptrs,
|
||||
data: data_ptr,
|
||||
marginal_cdf: marginal_ptr,
|
||||
conditional_cdf: conditional_ptr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue