Compare commits
2 commits
599683eaad
...
0c04eeb0f9
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c04eeb0f9 | |||
| 731a37abae |
37 changed files with 413 additions and 258 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -4,9 +4,11 @@ target/
|
|||
*.bak
|
||||
flip.rs
|
||||
.vscode
|
||||
rust-analyzer.json
|
||||
rust-analyzer.toml
|
||||
data/
|
||||
src/gpu/
|
||||
src/tests/
|
||||
tests/
|
||||
*.spv
|
||||
*.json
|
||||
*.txt
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use spirv_std::spirv;
|
||||
|
||||
/// Core logic — testable on CPU
|
||||
pub fn scale_kernel_logic(idx: usize, input: &[f32], output: &mut [f32], scale: f32) {
|
||||
if idx < input.len() {
|
||||
output[idx] = input[idx] * scale;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use crate::core::scattering::{
|
|||
refract,
|
||||
};
|
||||
use crate::spectra::{DeviceStandardColorSpaces, RGBUnboundedSpectrum, SampledSpectrum};
|
||||
use crate::utils::gpu_array_from_fn;
|
||||
use crate::utils::math::{
|
||||
clamp, fast_exp, i0, lerp, log_i0, radians, safe_acos, safe_asin, safe_sqrt, sample_discrete,
|
||||
square, trimmed_logistic,
|
||||
|
|
@ -82,7 +83,7 @@ impl HairBxDF {
|
|||
let ap0 = SampledSpectrum::new(f);
|
||||
let ap1 = t * (1.0 - f).powi(2);
|
||||
let tf = t * f;
|
||||
core::array::from_fn(|p| match p {
|
||||
gpu_array_from_fn(|p| match p {
|
||||
0 => ap0,
|
||||
1 => ap1,
|
||||
_ if p < P_MAX => ap1 * tf.pow_int(p - 1),
|
||||
|
|
@ -134,7 +135,7 @@ impl HairBxDF {
|
|||
let t = t_value.exp();
|
||||
let ap = Self::ap(cos_theta_o, self.eta, self.h, t);
|
||||
let sum_y: Float = ap.iter().map(|s| s.average()).sum();
|
||||
core::array::from_fn(|i| ap[i].average() / sum_y)
|
||||
gpu_array_from_fn(|i| ap[i].average() / sum_y)
|
||||
}
|
||||
|
||||
pub fn sigma_a_from_concentration(
|
||||
|
|
|
|||
|
|
@ -68,9 +68,7 @@ impl BxDFTrait for DiffuseBxDF {
|
|||
self
|
||||
}
|
||||
|
||||
fn regularize(&mut self) {
|
||||
return;
|
||||
}
|
||||
fn regularize(&mut self) {}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
|
|||
|
|
@ -32,9 +32,10 @@ use core::any::Any;
|
|||
use num_traits::Float as NumFloat;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum TopOrBottom<'a, T, B> {
|
||||
Top(&'a T),
|
||||
Bottom(&'a B),
|
||||
pub struct TopOrBottom<'a, T, B> {
|
||||
top: &'a T,
|
||||
bottom: &'a B,
|
||||
is_top: bool,
|
||||
}
|
||||
|
||||
impl<'a, T, B> TopOrBottom<'a, T, B>
|
||||
|
|
@ -42,13 +43,34 @@ where
|
|||
T: BxDFTrait,
|
||||
B: BxDFTrait,
|
||||
{
|
||||
pub fn f(&self, wo: Vector3f, wi: Vector3f, mode: TransportMode) -> SampledSpectrum {
|
||||
match self {
|
||||
Self::Top(t) => t.f(wo, wi, mode),
|
||||
Self::Bottom(b) => b.f(wo, wi, mode),
|
||||
#[inline]
|
||||
pub fn new_top(top: &'a T, bottom: &'a B) -> Self {
|
||||
Self {
|
||||
top,
|
||||
bottom,
|
||||
is_top: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_bottom(top: &'a T, bottom: &'a B) -> Self {
|
||||
Self {
|
||||
top,
|
||||
bottom,
|
||||
is_top: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn f(&self, wo: Vector3f, wi: Vector3f, mode: TransportMode) -> SampledSpectrum {
|
||||
if self.is_top {
|
||||
self.top.f(wo, wi, mode)
|
||||
} else {
|
||||
self.bottom.f(wo, wi, mode)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sample_f(
|
||||
&self,
|
||||
wo: Vector3f,
|
||||
|
|
@ -56,23 +78,28 @@ where
|
|||
u: Point2f,
|
||||
f_args: FArgs,
|
||||
) -> Option<BSDFSample> {
|
||||
match self {
|
||||
Self::Top(t) => t.sample_f(wo, uc, u, f_args),
|
||||
Self::Bottom(b) => b.sample_f(wo, uc, u, f_args),
|
||||
if self.is_top {
|
||||
self.top.sample_f(wo, uc, u, f_args)
|
||||
} else {
|
||||
self.bottom.sample_f(wo, uc, u, f_args)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pdf(&self, wo: Vector3f, wi: Vector3f, f_args: FArgs) -> Float {
|
||||
match self {
|
||||
Self::Top(t) => t.pdf(wo, wi, f_args),
|
||||
Self::Bottom(b) => b.pdf(wo, wi, f_args),
|
||||
if self.is_top {
|
||||
self.top.pdf(wo, wi, f_args)
|
||||
} else {
|
||||
self.bottom.pdf(wo, wi, f_args)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn flags(&self) -> BxDFFlags {
|
||||
match self {
|
||||
Self::Top(t) => t.flags(),
|
||||
Self::Bottom(b) => b.flags(),
|
||||
if self.is_top {
|
||||
self.top.flags()
|
||||
} else {
|
||||
self.bottom.flags()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -99,6 +126,7 @@ where
|
|||
T: BxDFTrait,
|
||||
B: BxDFTrait,
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
top: T,
|
||||
bottom: B,
|
||||
|
|
@ -136,11 +164,11 @@ where
|
|||
mode: TransportMode,
|
||||
entered_top: bool,
|
||||
exit_z: Float,
|
||||
interfaces: (TopOrBottom<T, B>, TopOrBottom<T, B>, TopOrBottom<T, B>),
|
||||
enter_interface: TopOrBottom<T, B>,
|
||||
exit_interface: TopOrBottom<T, B>,
|
||||
non_exit_interface: TopOrBottom<T, B>,
|
||||
rng: &mut Rng,
|
||||
) -> SampledSpectrum {
|
||||
let (enter_interface, exit_interface, non_exit_interface) = interfaces;
|
||||
|
||||
let trans_args = FArgs {
|
||||
mode,
|
||||
sample_flags: BxDFReflTransFlags::TRANSMISSION,
|
||||
|
|
@ -151,7 +179,7 @@ where
|
|||
};
|
||||
let mut r = || rng.uniform::<Float>().min(ONE_MINUS_EPSILON);
|
||||
|
||||
// 1. Sample Initial Directions (Standard NEE-like logic)
|
||||
// Sample Initial Directions
|
||||
let Some(wos) = enter_interface
|
||||
.sample_f(wo, r(), Point2f::new(r(), r()), trans_args)
|
||||
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
|
||||
|
|
@ -242,8 +270,7 @@ where
|
|||
}
|
||||
|
||||
if z == exit_z {
|
||||
// Account for reflection at exitInterface
|
||||
// Hitting the exit surface -> Transmission
|
||||
// Hitting the exit surface -> Reflection off exit interface
|
||||
let Some(bs) = exit_interface
|
||||
.sample_f(-w, r(), Point2f::new(r(), r()), refl_args)
|
||||
.filter(|s| !s.f.is_black() && s.pdf > 0.0 && s.wi.z() != 0.0)
|
||||
|
|
@ -345,20 +372,20 @@ where
|
|||
|
||||
let entered_top = TWO_SIDED || wo.z() > 0.;
|
||||
let enter_interface = if entered_top {
|
||||
TopOrBottom::Top(&self.top)
|
||||
TopOrBottom::new_top(&self.top, &self.bottom)
|
||||
} else {
|
||||
TopOrBottom::Bottom(&self.bottom)
|
||||
TopOrBottom::new_bottom(&self.top, &self.bottom)
|
||||
};
|
||||
|
||||
let (exit_interface, non_exit_interface) = if same_hemisphere(wo, wi) ^ entered_top {
|
||||
(
|
||||
TopOrBottom::Bottom(&self.bottom),
|
||||
TopOrBottom::Top(&self.top),
|
||||
TopOrBottom::new_bottom(&self.top, &self.bottom),
|
||||
TopOrBottom::new_top(&self.top, &self.bottom),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
TopOrBottom::Top(&self.top),
|
||||
TopOrBottom::Bottom(&self.bottom),
|
||||
TopOrBottom::new_top(&self.top, &self.bottom),
|
||||
TopOrBottom::new_bottom(&self.top, &self.bottom),
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -376,9 +403,18 @@ where
|
|||
let hash1 = hash_buffer(&[wi.x(), wi.y(), wi.z()], 0);
|
||||
let mut rng = Rng::new_with_offset(hash0, hash1);
|
||||
|
||||
let inters = (enter_interface, exit_interface, non_exit_interface);
|
||||
for _ in 0..self.n_samples {
|
||||
f += self.evaluate_sample(wo, wi, mode, entered_top, exit_z, inters.clone(), &mut rng)
|
||||
f += self.evaluate_sample(
|
||||
wo,
|
||||
wi,
|
||||
mode,
|
||||
entered_top,
|
||||
exit_z,
|
||||
enter_interface.clone(),
|
||||
exit_interface.clone(),
|
||||
non_exit_interface.clone(),
|
||||
&mut rng,
|
||||
);
|
||||
}
|
||||
|
||||
f / self.n_samples as Float
|
||||
|
|
@ -474,9 +510,9 @@ where
|
|||
}
|
||||
|
||||
let interface = if z == 0. {
|
||||
TopOrBottom::Bottom(&self.bottom)
|
||||
TopOrBottom::new_bottom(&self.top, &self.bottom)
|
||||
} else {
|
||||
TopOrBottom::Top(&self.top)
|
||||
TopOrBottom::new_top(&self.top, &self.bottom)
|
||||
};
|
||||
|
||||
// Sample interface BSDF to determine new path direction
|
||||
|
|
@ -553,13 +589,13 @@ where
|
|||
// Evaluate TRT term for PDF estimate
|
||||
let (r_interface, t_interface) = if entered_top {
|
||||
(
|
||||
TopOrBottom::Bottom(&self.bottom),
|
||||
TopOrBottom::Top(&self.top),
|
||||
TopOrBottom::new_bottom(&self.top, &self.bottom),
|
||||
TopOrBottom::new_top(&self.top, &self.bottom),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
TopOrBottom::Top(&self.top),
|
||||
TopOrBottom::Bottom(&self.bottom),
|
||||
TopOrBottom::new_top(&self.top, &self.bottom),
|
||||
TopOrBottom::new_bottom(&self.top, &self.bottom),
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -589,19 +625,19 @@ where
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// Evaluate TT term for PDF estimate>
|
||||
// Evaluate TT term for PDF estimate
|
||||
let valid = |s: &BSDFSample| {
|
||||
!s.f.is_black() && s.pdf > 0.0 && s.wi.z() > 0. || s.is_reflective()
|
||||
};
|
||||
let (to_interface, ti_interface) = if entered_top {
|
||||
(
|
||||
TopOrBottom::Top(&self.top),
|
||||
TopOrBottom::Bottom(&self.bottom),
|
||||
TopOrBottom::new_top(&self.top, &self.bottom),
|
||||
TopOrBottom::new_bottom(&self.top, &self.bottom),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
TopOrBottom::Bottom(&self.bottom),
|
||||
TopOrBottom::Top(&self.top),
|
||||
TopOrBottom::new_bottom(&self.top, &self.bottom),
|
||||
TopOrBottom::new_top(&self.top, &self.bottom),
|
||||
)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@ mod spherical;
|
|||
|
||||
pub use orthographic::OrthographicCamera;
|
||||
pub use perspective::PerspectiveCamera;
|
||||
pub use realistic::RealisticCamera;
|
||||
pub use realistic::{EXIT_PUPIL_SAMPLES, LensElementInterface, RealisticCamera};
|
||||
pub use spherical::{Mapping, SphericalCamera};
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ pub struct ExitPupilSample {
|
|||
pub pdf: Float,
|
||||
}
|
||||
|
||||
const EXIT_PUPIL_SAMPLES: usize = 64;
|
||||
pub const EXIT_PUPIL_SAMPLES: usize = 64;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
|
|
@ -107,7 +107,7 @@ impl RealisticCamera {
|
|||
Self::compute_exit_pupil_bounds(interface_array, film_x_0, film_x_1)
|
||||
}
|
||||
|
||||
fn compute_exit_pupil_bounds(
|
||||
pub fn compute_exit_pupil_bounds(
|
||||
elements: &[LensElementInterface],
|
||||
film_x_0: Float,
|
||||
film_x_1: Float,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use crate::utils::math::linear_least_squares;
|
|||
use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
|
||||
use crate::utils::sampling::VarianceEstimator;
|
||||
use crate::utils::transform::AnimatedTransform;
|
||||
use crate::utils::{AtomicFloat, Ptr};
|
||||
use crate::utils::{AtomicFloat, Ptr, gpu_array_from_fn};
|
||||
use num_traits::Float as NumFloat;
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -33,13 +33,23 @@ pub struct RGBFilm {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RGBPixel {
|
||||
rgb_sum: [AtomicFloat; 3],
|
||||
weight_sum: AtomicFloat,
|
||||
rgb_splat: [AtomicFloat; 3],
|
||||
}
|
||||
|
||||
impl Default for RGBPixel {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
rgb_sum: gpu_array_from_fn(|_| AtomicFloat::default()),
|
||||
weight_sum: AtomicFloat::default(),
|
||||
rgb_splat: gpu_array_from_fn(|_| AtomicFloat::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RGBFilm {
|
||||
pub fn base(&self) -> &FilmBase {
|
||||
&self.base
|
||||
|
|
@ -153,7 +163,7 @@ impl RGBFilm {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(target_os = "cuda", derive(Copy))]
|
||||
pub struct GBufferPixel {
|
||||
pub rgb_sum: [AtomicFloat; 3],
|
||||
|
|
@ -162,7 +172,7 @@ pub struct GBufferPixel {
|
|||
pub rgb_splat: [AtomicFloat; 3],
|
||||
pub p_sum: Point3f,
|
||||
pub dz_dx_sum: AtomicFloat,
|
||||
pub dz_dy_sum: Float,
|
||||
pub dz_dy_sum: AtomicFloat,
|
||||
pub n_sum: Normal3f,
|
||||
pub ns_sum: Normal3f,
|
||||
pub uv_sum: Point2f,
|
||||
|
|
@ -170,6 +180,25 @@ pub struct GBufferPixel {
|
|||
pub rgb_variance: VarianceEstimator,
|
||||
}
|
||||
|
||||
impl Default for GBufferPixel {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
rgb_sum: gpu_array_from_fn(|_| AtomicFloat::default()),
|
||||
weight_sum: AtomicFloat::default(),
|
||||
rgb_splat: gpu_array_from_fn(|_| AtomicFloat::default()),
|
||||
g_buffer_weight_sum: AtomicFloat::default(),
|
||||
p_sum: Point3f::default(),
|
||||
dz_dx_sum: AtomicFloat::default(),
|
||||
dz_dy_sum: AtomicFloat::default(),
|
||||
n_sum: Normal3f::default(),
|
||||
ns_sum: Normal3f::default(),
|
||||
uv_sum: Point2f::default(),
|
||||
rgb_albedo_sum: gpu_array_from_fn(|_| AtomicFloat::default()),
|
||||
rgb_variance: VarianceEstimator::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(target_os = "cuda", derive(Copy))]
|
||||
|
|
@ -299,9 +328,9 @@ pub struct SpectralPixel {
|
|||
impl Clone for SpectralPixel {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
rgb_sum: core::array::from_fn(|i| AtomicFloat::new(self.rgb_sum[i].get())),
|
||||
rgb_sum: gpu_array_from_fn(|i| AtomicFloat::new(self.rgb_sum[i].get())),
|
||||
rgb_weight_sum: AtomicFloat::new(self.rgb_weight_sum.get()),
|
||||
rgb_splat: core::array::from_fn(|i| AtomicFloat::new(self.rgb_splat[i].get())),
|
||||
rgb_splat: gpu_array_from_fn(|i| AtomicFloat::new(self.rgb_splat[i].get())),
|
||||
bucket_offset: self.bucket_offset,
|
||||
}
|
||||
}
|
||||
|
|
@ -310,9 +339,9 @@ impl Clone for SpectralPixel {
|
|||
impl Default for SpectralPixel {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
rgb_sum: core::array::from_fn(|_| AtomicFloat::new(0.0)),
|
||||
rgb_sum: gpu_array_from_fn(|_| AtomicFloat::new(0.0)),
|
||||
rgb_weight_sum: AtomicFloat::new(0.0),
|
||||
rgb_splat: core::array::from_fn(|_| AtomicFloat::new(0.0)),
|
||||
rgb_splat: gpu_array_from_fn(|_| AtomicFloat::new(0.0)),
|
||||
bucket_offset: 0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use super::{Float, NumFloat};
|
|||
use super::{Point, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f};
|
||||
use crate::core::geometry::traits::{SqrtExt, VectorLike};
|
||||
use crate::core::geometry::{max, min};
|
||||
use crate::utils::gpu_array_from_fn;
|
||||
use crate::utils::interval::Interval;
|
||||
use crate::utils::math::lerp;
|
||||
use core::mem;
|
||||
|
|
@ -137,7 +138,7 @@ where
|
|||
}
|
||||
|
||||
pub fn corner(&self, corner_index: usize) -> Point<T, N> {
|
||||
Point(core::array::from_fn(|i| {
|
||||
Point(gpu_array_from_fn(|i| {
|
||||
if (corner_index >> i) & 1 == 1 {
|
||||
self.p_max[i]
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::core::pbrt::Float;
|
||||
use crate::utils::gpu_array_from_fn;
|
||||
use crate::utils::interval::Interval;
|
||||
use crate::utils::math::{next_float_down, next_float_up};
|
||||
use core::ops::{Add, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub};
|
||||
|
|
@ -18,7 +19,7 @@ pub trait Tuple<T, const N: usize>:
|
|||
where
|
||||
T: Copy,
|
||||
{
|
||||
let new_data = p.map(|index| self[index]);
|
||||
let new_data = gpu_array_from_fn(|i| self[p[i]]);
|
||||
Self::from_array(new_data)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ impl SurfaceInteraction {
|
|||
}
|
||||
}
|
||||
|
||||
fn compute_bump_geom(
|
||||
pub fn compute_bump_geom(
|
||||
&mut self,
|
||||
tex_eval: &UniversalTextureEvaluator,
|
||||
displacement: Ptr<GPUFloatTexture>,
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ impl From<&SurfaceInteraction> for NormalBumpEvalContext {
|
|||
p: si.p(),
|
||||
uv: si.common.uv,
|
||||
n: si.n(),
|
||||
shading: si.shading.clone(),
|
||||
shading: si.shading,
|
||||
dudx: si.dudx,
|
||||
dudy: si.dudy,
|
||||
dvdx: si.dvdx,
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ pub enum RandomizeStrategy {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct HaltonSampler {
|
||||
pub samples_per_pixel: i32,
|
||||
pub randomize: RandomizeStrategy,
|
||||
|
|
@ -103,6 +103,22 @@ pub struct HaltonSampler {
|
|||
pub digit_permutations: Ptr<DeviceDigitPermutation>,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for HaltonSampler {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
samples_per_pixel: 0,
|
||||
randomize: RandomizeStrategy::default(),
|
||||
base_scales: [0; 2],
|
||||
base_exponents: [0; 2],
|
||||
mult_inverse: [0; 2],
|
||||
halton_index: 0,
|
||||
dim: 0,
|
||||
digit_permutations: Ptr::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HaltonSampler {
|
||||
pub fn sample_dimension(&self, dimension: u32) -> Float {
|
||||
if self.randomize == RandomizeStrategy::None {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
pub mod bxdfs;
|
||||
pub mod cameras;
|
||||
pub mod core;
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
pub mod data;
|
||||
pub mod filters;
|
||||
pub mod lights;
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ use crate::{Float, ONE_MINUS_EPSILON, PI};
|
|||
use enum_dispatch::enum_dispatch;
|
||||
use num_traits::Float as NumFloat;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CompactLightBounds {
|
||||
pub w: OctahedralVector,
|
||||
pub phi: Float,
|
||||
|
|
@ -27,6 +27,18 @@ pub struct CompactLightBounds {
|
|||
pub qb: [[u16; 3]; 2],
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for CompactLightBounds {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
w: OctahedralVector::default(),
|
||||
phi: Float::default(),
|
||||
packed_info: u32::default(),
|
||||
qb: [[u16::default(); 3]; 2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const _: () = assert!(core::mem::size_of::<CompactLightBounds>() == 24);
|
||||
|
||||
impl CompactLightBounds {
|
||||
|
|
@ -269,17 +281,13 @@ impl LightSamplerTrait for PowerLightSampler {
|
|||
}
|
||||
|
||||
fn pmf(&self, light: &Light) -> Float {
|
||||
let array_start = self.lights.as_raw();
|
||||
let target = light as *const Light as *mut Light;
|
||||
|
||||
unsafe {
|
||||
let index = target.offset_from(array_start);
|
||||
|
||||
if index >= 0 && index < self.lights_len as isize {
|
||||
return self.alias_table.pmf(index as u32);
|
||||
let target = light as *const Light;
|
||||
for i in 0..self.lights_len as usize {
|
||||
if unsafe { self.lights.add(i) }.as_raw() == target {
|
||||
return self.alias_table.pmf(i as u32);
|
||||
}
|
||||
}
|
||||
0.
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -353,12 +361,12 @@ impl LightBVHNode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct BVHLightSampler {
|
||||
pub nodes: *const LightBVHNode,
|
||||
pub lights: *const Light,
|
||||
pub infinite_lights: *const Light,
|
||||
pub bit_trails: *const u64,
|
||||
pub nodes: Ptr<LightBVHNode>,
|
||||
pub lights: Ptr<Light>,
|
||||
pub infinite_lights: Ptr<Light>,
|
||||
pub bit_trails: Ptr<u64>,
|
||||
pub nodes_len: u32,
|
||||
pub lights_len: u32,
|
||||
pub infinite_lights_len: u32,
|
||||
|
|
@ -371,22 +379,33 @@ unsafe impl Sync for BVHLightSampler {}
|
|||
impl BVHLightSampler {
|
||||
#[inline(always)]
|
||||
fn node(&self, idx: usize) -> &LightBVHNode {
|
||||
unsafe { &*self.nodes.add(idx) }
|
||||
unsafe { self.nodes.at(idx) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn light(&self, idx: usize) -> Light {
|
||||
unsafe { *self.lights.add(idx) }
|
||||
unsafe { *self.lights.at(idx) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn infinite_light(&self, idx: usize) -> Light {
|
||||
unsafe { *self.infinite_lights.add(idx) }
|
||||
unsafe { *self.infinite_lights.at(idx) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn bit_trail(&self, idx: usize) -> u64 {
|
||||
unsafe { *self.bit_trails.add(idx) }
|
||||
unsafe { *self.bit_trails.at(idx) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn light_index_in(&self, base: Ptr<Light>, len: u32, light: &Light) -> Option<usize> {
|
||||
let target = light as *const Light;
|
||||
for i in 0..len as usize {
|
||||
if unsafe { base.add(i) }.as_raw() == target {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn evaluate_cost(&self, b: &LightBounds, bounds: &Bounds3f, dim: usize) -> Float {
|
||||
|
|
@ -459,27 +478,20 @@ impl LightSamplerTrait for BVHLightSampler {
|
|||
}
|
||||
|
||||
fn pmf_with_context(&self, ctx: &LightSampleContext, light: &Light) -> Float {
|
||||
let light_ptr = light as *const Light;
|
||||
let empty_nodes = if self.nodes_len == 0 { 0. } else { 1. };
|
||||
let n_infinite = self.infinite_lights_len as Float;
|
||||
|
||||
let inf_start = self.infinite_lights;
|
||||
let inf_end = unsafe { self.infinite_lights.add(self.infinite_lights_len as usize) };
|
||||
if light_ptr >= inf_start && light_ptr < inf_end {
|
||||
if self
|
||||
.light_index_in(self.infinite_lights, self.infinite_lights_len, light)
|
||||
.is_some()
|
||||
{
|
||||
return 1.0 / (n_infinite + empty_nodes);
|
||||
}
|
||||
|
||||
let finite_start = self.lights;
|
||||
let finite_end = unsafe { self.lights.add(self.lights_len as usize) };
|
||||
|
||||
if light_ptr < finite_start || light_ptr >= finite_end {
|
||||
let Some(light_index) = self.light_index_in(self.lights, self.lights_len, light) else {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let light_index = unsafe { light_ptr.offset_from(finite_start) as usize };
|
||||
};
|
||||
|
||||
let mut bit_trail = self.bit_trail(light_index);
|
||||
|
||||
let p_inf = n_infinite / (n_infinite + empty_nodes);
|
||||
let mut pmf = 1.0 - p_inf;
|
||||
let mut node_ind = 0;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::core::geometry::{
|
|||
};
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||
use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
|
||||
use crate::utils::gpu_array_from_fn;
|
||||
use crate::utils::math::{clamp, lerp, square};
|
||||
use crate::utils::splines::{
|
||||
bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier,
|
||||
|
|
@ -52,7 +53,7 @@ impl CurveCommon {
|
|||
assert_eq!(c.len(), 4);
|
||||
let cp_obj: [Point3f; 4] = c[..4].try_into().unwrap();
|
||||
|
||||
let mut n = [Normal3f::default(); 2];
|
||||
let mut n: [Normal3f; 2] = gpu_array_from_fn(|_| Normal3f::default());
|
||||
let mut normal_angle: Float = 0.;
|
||||
let mut inv_sin_normal_angle: Float = 0.;
|
||||
if norm.len() == 2 {
|
||||
|
|
@ -115,7 +116,7 @@ impl CurveShape {
|
|||
}
|
||||
|
||||
let ray_from_object = look_at(ray.o, ray.o + ray.d, dx).expect("Inversion error");
|
||||
let cp = [0; 4].map(|i| ray_from_object.apply_to_point(cp_obj[i]));
|
||||
let cp: [Point3f; 4] = gpu_array_from_fn(|i| ray_from_object.apply_to_point(cp_obj[i]));
|
||||
|
||||
let max_width = lerp(self.u_min, self.common.width[0], self.common.width[1]).max(lerp(
|
||||
self.u_max,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::core::pbrt::Float;
|
||||
use crate::core::spectrum::{SpectrumTrait, StandardSpectra};
|
||||
use crate::utils::gpu_array_from_fn;
|
||||
use crate::utils::math::{clamp, lerp};
|
||||
use core::ops::{
|
||||
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
|
||||
|
|
@ -43,7 +44,7 @@ impl SampledSpectrum {
|
|||
F: FnMut(usize) -> Float,
|
||||
{
|
||||
Self {
|
||||
values: core::array::from_fn(cb),
|
||||
values: gpu_array_from_fn(cb),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::core::pbrt::{Float, FloatBitOps, FloatBits, ONE_MINUS_EPSILON, PI, PI
|
|||
use crate::utils::hash::{hash_buffer, mix_bits};
|
||||
use crate::utils::sobol::{SOBOL_MATRICES_32, VDC_SOBOL_MATRICES, VDC_SOBOL_MATRICES_INV};
|
||||
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::{Ptr, gpu_array_from_fn};
|
||||
use core::fmt::{self, Display, Write};
|
||||
use core::iter::{Product, Sum};
|
||||
use core::mem;
|
||||
|
|
@ -1090,7 +1090,7 @@ impl<T, const R: usize, const C: usize> Matrix<T, R, C> {
|
|||
where
|
||||
T: Clone + Zero,
|
||||
{
|
||||
let m: [[T; C]; R] = core::array::from_fn(|_| core::array::from_fn(|_| T::zero()));
|
||||
let m: [[T; C]; R] = gpu_array_from_fn(|_| gpu_array_from_fn(|_| T::zero()));
|
||||
Self { m }
|
||||
}
|
||||
|
||||
|
|
@ -1258,8 +1258,8 @@ impl<T, const N: usize> SquareMatrix<T, N> {
|
|||
T: Copy + Zero + One,
|
||||
{
|
||||
Self {
|
||||
m: core::array::from_fn(|i| {
|
||||
core::array::from_fn(|j| if i == j { T::one() } else { T::zero() })
|
||||
m: gpu_array_from_fn(|i| {
|
||||
gpu_array_from_fn(|j| if i == j { T::one() } else { T::zero() })
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
@ -1469,8 +1469,7 @@ where
|
|||
{
|
||||
type Output = Vector<T, N>;
|
||||
fn mul(self, rhs: Vector<T, N>) -> Self::Output {
|
||||
let arr =
|
||||
core::array::from_fn(|i| self.m[i].iter().zip(&rhs.0).map(|(m, v)| *m * *v).sum());
|
||||
let arr = gpu_array_from_fn(|i| self.m[i].iter().zip(&rhs.0).map(|(m, v)| *m * *v).sum());
|
||||
Vector(arr)
|
||||
}
|
||||
}
|
||||
|
|
@ -1481,8 +1480,7 @@ where
|
|||
{
|
||||
type Output = Point<T, N>;
|
||||
fn mul(self, rhs: Point<T, N>) -> Self::Output {
|
||||
let arr =
|
||||
core::array::from_fn(|i| self.m[i].iter().zip(&rhs.0).map(|(m, v)| *m * *v).sum());
|
||||
let arr = gpu_array_from_fn(|i| self.m[i].iter().zip(&rhs.0).map(|(m, v)| *m * *v).sum());
|
||||
Point(arr)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ impl AtomicFloat {
|
|||
}
|
||||
|
||||
/// Atomically adds `val` to the current value.
|
||||
/// Uses a Compare-And-Swap (CAS) loop.
|
||||
/// Compare-And-Swap loop.
|
||||
pub fn add(&self, val: f32) {
|
||||
let mut current_bits = self.bits.load(Ordering::Relaxed);
|
||||
loop {
|
||||
|
|
@ -115,37 +115,16 @@ impl AtomicFloat {
|
|||
}
|
||||
}
|
||||
|
||||
// pub struct AtomicDouble {
|
||||
// bits: AtomicU64,
|
||||
// }
|
||||
//
|
||||
// impl AtomicDouble {
|
||||
// pub fn new(val: f64) -> Self {
|
||||
// Self {
|
||||
// bits: AtomicU64::new(val.to_bits()),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// pub fn get(&self) -> f64 {
|
||||
// f64::from_bits(self.bits.load(Ordering::Relaxed))
|
||||
// }
|
||||
//
|
||||
// pub fn add(&self, val: f64) {
|
||||
// let mut current_bits = self.bits.load(Ordering::Relaxed);
|
||||
// loop {
|
||||
// let current_val = f64::from_bits(current_bits);
|
||||
// let new_val = current_val + val;
|
||||
// let new_bits = new_val.to_bits();
|
||||
//
|
||||
// match self.bits.compare_exchange_weak(
|
||||
// current_bits,
|
||||
// new_bits,
|
||||
// Ordering::Relaxed,
|
||||
// Ordering::Relaxed,
|
||||
// ) {
|
||||
// Ok(_) => break,
|
||||
// Err(x) => current_bits = x,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
#[inline(always)]
|
||||
pub fn gpu_array_from_fn<T, const N: usize>(mut f: impl FnMut(usize) -> T) -> [T; N] {
|
||||
unsafe {
|
||||
let mut arr: core::mem::MaybeUninit<[T; N]> = core::mem::MaybeUninit::uninit();
|
||||
let ptr = arr.as_mut_ptr() as *mut T;
|
||||
let mut i = 0;
|
||||
while i < N {
|
||||
ptr.add(i).write(f(i));
|
||||
i += 1;
|
||||
}
|
||||
arr.assume_init()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::core::geometry::{Bounds3f, Lerp, Point3f, Vector3f, VectorLike};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::utils::gpu_array_from_fn;
|
||||
use crate::utils::math::lerp;
|
||||
use core::ops::Sub;
|
||||
use num_traits::Num;
|
||||
|
|
@ -40,7 +41,7 @@ where
|
|||
}
|
||||
|
||||
pub fn subdivide_cubic_bezier(cp: &[Point3f]) -> [Point3f; 7] {
|
||||
let v: [Vector3f; 4] = core::array::from_fn(|i| Vector3f::from(cp[i]));
|
||||
let v: [Vector3f; 4] = gpu_array_from_fn(|i| Vector3f::from(cp[i]));
|
||||
let v01 = (v[0] + v[1]) / 2.0;
|
||||
let v12 = (v[1] + v[2]) / 2.0;
|
||||
let v23 = (v[2] + v[3]) / 2.0;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use crate::core::geometry::{
|
|||
use crate::core::interaction::{
|
||||
Interaction, InteractionBase, InteractionTrait, MediumInteraction, SurfaceInteraction,
|
||||
};
|
||||
use crate::utils::gpu_array_from_fn;
|
||||
use crate::{Float, gamma};
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -793,7 +794,7 @@ impl DerivativeTerm {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Default, Clone)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct AnimatedTransform {
|
||||
pub start_transform: Transform,
|
||||
pub end_transform: Transform,
|
||||
|
|
@ -811,6 +812,27 @@ pub struct AnimatedTransform {
|
|||
c5: [DerivativeTerm; 3],
|
||||
}
|
||||
|
||||
impl Default for AnimatedTransform {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
start_transform: Transform::default(),
|
||||
end_transform: Transform::default(),
|
||||
start_time: 0.0,
|
||||
end_time: 0.0,
|
||||
actually_animated: false,
|
||||
t: gpu_array_from_fn(|_| Vector3f::default()),
|
||||
r: gpu_array_from_fn(|_| Quaternion::default()),
|
||||
s: gpu_array_from_fn(|_| SquareMatrix::default()),
|
||||
has_rotation: false,
|
||||
c1: gpu_array_from_fn(|_| DerivativeTerm::default()),
|
||||
c2: gpu_array_from_fn(|_| DerivativeTerm::default()),
|
||||
c3: gpu_array_from_fn(|_| DerivativeTerm::default()),
|
||||
c4: gpu_array_from_fn(|_| DerivativeTerm::default()),
|
||||
c5: gpu_array_from_fn(|_| DerivativeTerm::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimatedTransform {
|
||||
pub fn from_transform(t: &Transform) -> Self {
|
||||
Self::new(t, 0., t, 1.)
|
||||
|
|
@ -825,22 +847,7 @@ impl AnimatedTransform {
|
|||
let actually_animated = start_transform != end_transform;
|
||||
|
||||
if !actually_animated {
|
||||
return Self {
|
||||
start_transform: *start_transform,
|
||||
end_transform: *end_transform,
|
||||
start_time,
|
||||
end_time,
|
||||
actually_animated: false,
|
||||
t: [Vector3f::default(); 2],
|
||||
r: [Quaternion::default(); 2],
|
||||
s: core::array::from_fn(|_| SquareMatrix::default()),
|
||||
has_rotation: false,
|
||||
c1: [DerivativeTerm::default(); 3],
|
||||
c2: [DerivativeTerm::default(); 3],
|
||||
c3: [DerivativeTerm::default(); 3],
|
||||
c4: [DerivativeTerm::default(); 3],
|
||||
c5: [DerivativeTerm::default(); 3],
|
||||
};
|
||||
return Self::default();
|
||||
}
|
||||
|
||||
let (t0, r_temp, s0) = start_transform.decompose();
|
||||
|
|
@ -865,11 +872,11 @@ impl AnimatedTransform {
|
|||
let s = [s0, s1];
|
||||
|
||||
let (c1, c2, c3, c4, c5) = if has_rotation {
|
||||
let mut c1: [DerivativeTerm; 3] = [Default::default(); 3];
|
||||
let mut c2: [DerivativeTerm; 3] = [Default::default(); 3];
|
||||
let mut c3: [DerivativeTerm; 3] = [Default::default(); 3];
|
||||
let mut c4: [DerivativeTerm; 3] = [Default::default(); 3];
|
||||
let mut c5: [DerivativeTerm; 3] = [Default::default(); 3];
|
||||
let mut c1: [DerivativeTerm; 3] = gpu_array_from_fn(|_| DerivativeTerm::default());
|
||||
let mut c2: [DerivativeTerm; 3] = gpu_array_from_fn(|_| DerivativeTerm::default());
|
||||
let mut c3: [DerivativeTerm; 3] = gpu_array_from_fn(|_| DerivativeTerm::default());
|
||||
let mut c4: [DerivativeTerm; 3] = gpu_array_from_fn(|_| DerivativeTerm::default());
|
||||
let mut c5: [DerivativeTerm; 3] = gpu_array_from_fn(|_| DerivativeTerm::default());
|
||||
let cos_theta = r0.dot(r1);
|
||||
let theta = safe_acos(cos_theta);
|
||||
let qperp: Quaternion = (r0 - r1 * cos_theta).normalize();
|
||||
|
|
@ -1965,11 +1972,11 @@ impl AnimatedTransform {
|
|||
(c1, c2, c3, c4, c5)
|
||||
} else {
|
||||
(
|
||||
core::array::from_fn(|_| DerivativeTerm::default()),
|
||||
core::array::from_fn(|_| DerivativeTerm::default()),
|
||||
core::array::from_fn(|_| DerivativeTerm::default()),
|
||||
core::array::from_fn(|_| DerivativeTerm::default()),
|
||||
core::array::from_fn(|_| DerivativeTerm::default()),
|
||||
gpu_array_from_fn(|_| DerivativeTerm::default()),
|
||||
gpu_array_from_fn(|_| DerivativeTerm::default()),
|
||||
gpu_array_from_fn(|_| DerivativeTerm::default()),
|
||||
gpu_array_from_fn(|_| DerivativeTerm::default()),
|
||||
gpu_array_from_fn(|_| DerivativeTerm::default()),
|
||||
)
|
||||
};
|
||||
AnimatedTransform {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
pub mod perspective;
|
||||
pub mod realistic;
|
||||
pub mod sherical;
|
||||
pub mod spherical;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,28 @@
|
|||
use ash::vk::Image;
|
||||
use shared::cameras::{Mapping, RealisticCamera, realistic::LensElementInterface};
|
||||
use crate::core::image::Image;
|
||||
use shared::cameras::{EXIT_PUPIL_SAMPLES, LensElementInterface, RealisticCamera};
|
||||
use shared::core::camera::CameraBase;
|
||||
use shared::core::geometry::{Bounds2f, Point2f};
|
||||
use shared::utils::math::square;
|
||||
use shared::{Float, Ptr};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RealisticCameraData {
|
||||
aperture_image: Arc<Image>,
|
||||
element_interfaces: Vec<LensElementInterface>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RealisticCameraHost {
|
||||
device: RealisticCamera,
|
||||
data: RealisticCameraData,
|
||||
}
|
||||
|
||||
impl RealisticCameraHost {
|
||||
pub fn device(&self) -> RealisticCamera {
|
||||
self.device
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
base: CameraBase,
|
||||
lens_params: &[Float],
|
||||
|
|
@ -31,7 +42,7 @@ impl RealisticCameraHost {
|
|||
let y = x * aspect;
|
||||
let physical_extent =
|
||||
Bounds2f::from_points(Point2f::new(-x / 2., -y / 2.), Point2f::new(x / 2., y / 2.));
|
||||
let mut element_interface: Vec<LensElementInterface> = Vec::new();
|
||||
let mut element_interfaces: Vec<LensElementInterface> = Vec::new();
|
||||
|
||||
for i in (0..lens_params.len()).step_by(4) {
|
||||
let curvature_radius = lens_params[i] / 1000.0;
|
||||
|
|
@ -53,7 +64,7 @@ impl RealisticCameraHost {
|
|||
eta,
|
||||
aperture_radius: aperture_diameter / 2.0,
|
||||
};
|
||||
element_interface.push(el_int);
|
||||
element_interfaces.push(el_int);
|
||||
}
|
||||
|
||||
let half_diag = film.diagonal() / 2.0;
|
||||
|
|
@ -62,26 +73,25 @@ impl RealisticCameraHost {
|
|||
for i in 0..EXIT_PUPIL_SAMPLES {
|
||||
let r0 = (i as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
|
||||
let r1 = ((i + 1) as Float / EXIT_PUPIL_SAMPLES as Float) * half_diag;
|
||||
exit_pupil_bounds[i] = Self::compute_exit_pupil_bounds(&element_interface, r0, r1);
|
||||
exit_pupil_bounds[i] =
|
||||
RealisticCamera::compute_exit_pupil_bounds(&element_interfaces, r0, r1);
|
||||
}
|
||||
|
||||
let n_elements = element_interface.len();
|
||||
let element_interfaces = element_interface.as_ptr();
|
||||
std::mem::forget(element_interface);
|
||||
let n_elements = element_interfaces.len();
|
||||
|
||||
let data = RealisticCameraData {
|
||||
element_interfaces,
|
||||
aperture_image,
|
||||
element_interfaces: element_interfaces.clone(),
|
||||
aperture_image: aperture_image.clone(),
|
||||
};
|
||||
|
||||
let device = RealisticCamera {
|
||||
base,
|
||||
focus_distance,
|
||||
element_interfaces: Ptr::from(element_interfaces),
|
||||
element_interfaces: Ptr::from(element_interfaces.as_ptr()),
|
||||
n_elements,
|
||||
physical_extent,
|
||||
set_aperture_diameter,
|
||||
aperture_image,
|
||||
aperture_image: Ptr::from(aperture_image.device()),
|
||||
exit_pupil_bounds,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use crate::cameras::realistic::RealisticCameraHost;
|
||||
use crate::core::image::ImageMetadata;
|
||||
use crate::core::image::{Image, ImageIO};
|
||||
use crate::globals::get_options;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||
use crate::utils::{Upload, read_float_file};
|
||||
use anyhow::{Result, anyhow};
|
||||
|
|
@ -11,7 +13,6 @@ use shared::core::film::Film;
|
|||
use shared::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f, Vector3f};
|
||||
use shared::core::image::PixelFormat;
|
||||
use shared::core::medium::Medium;
|
||||
use shared::core::options::get_options;
|
||||
use shared::utils::math::square;
|
||||
use shared::{Float, PI};
|
||||
use std::path::Path;
|
||||
|
|
@ -376,16 +377,16 @@ impl CameraFactory for Camera {
|
|||
}
|
||||
}
|
||||
|
||||
let camera = RealisticCamera::new(
|
||||
let camera = RealisticCameraHost::new(
|
||||
base,
|
||||
&lens_params,
|
||||
focal_distance,
|
||||
aperture_diameter,
|
||||
aperture_image.upload(arena),
|
||||
Arc::from(aperture_image.unwrap()),
|
||||
);
|
||||
|
||||
arena.alloc(camera);
|
||||
Ok(Camera::Realistic(camera))
|
||||
// arena.alloc(camera);
|
||||
Ok(Camera::Realistic(camera.device()))
|
||||
}
|
||||
"spherical" => {
|
||||
let full_res = film.full_resolution();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use anyhow::{Result, anyhow};
|
|||
use half::f16;
|
||||
use rayon::prelude::{IndexedParallelIterator, ParallelIterator, ParallelSliceMut};
|
||||
use shared::Float;
|
||||
use shared::Ptr;
|
||||
use shared::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
|
||||
use shared::core::geometry::{Bounds2f, Point2f, Point2i};
|
||||
use shared::core::image::{DeviceImage, ImageBase, PixelFormat, Pixels, WrapMode, WrapMode2D};
|
||||
|
|
@ -77,9 +78,9 @@ pub enum PixelStorage {
|
|||
impl PixelStorage {
|
||||
pub fn as_pixels(&self) -> Pixels {
|
||||
match self {
|
||||
PixelStorage::U8(data) => Pixels::U8(data.as_ptr().into()),
|
||||
PixelStorage::F16(data) => Pixels::F16((data.as_ptr() as *const f16).into()),
|
||||
PixelStorage::F32(data) => Pixels::F32(data.as_ptr().into()),
|
||||
PixelStorage::U8(vec) => Pixels::new_u8(Ptr::from_raw(vec.as_ptr())),
|
||||
PixelStorage::F16(vec) => Pixels::new_f16(Ptr::from_raw(vec.as_ptr() as *const u16)),
|
||||
PixelStorage::F32(vec) => Pixels::new_f32(Ptr::from_raw(vec.as_ptr())),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -565,9 +566,9 @@ impl Image {
|
|||
|
||||
pub fn update_view_pointers(&mut self) {
|
||||
self.device.pixels = match &self.pixels {
|
||||
PixelStorage::U8(vec) => Pixels::U8(vec.as_ptr().into()),
|
||||
PixelStorage::F16(vec) => Pixels::F16((vec.as_ptr() as *const f16).into()),
|
||||
PixelStorage::F32(vec) => Pixels::F32(vec.as_ptr().into()),
|
||||
PixelStorage::U8(vec) => Pixels::new_u8(Ptr::from_raw(vec.as_ptr())),
|
||||
PixelStorage::F16(vec) => Pixels::new_f16(Ptr::from_raw(vec.as_ptr() as *const u16)),
|
||||
PixelStorage::F32(vec) => Pixels::new_f32(Ptr::from_raw(vec.as_ptr())),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,35 @@
|
|||
use crate::globals::get_options;
|
||||
use shared::core::interaction::{
|
||||
InteractionBase, InteractionTrait, MediumInteraction, SimpleInteraction, SurfaceInteraction,
|
||||
};
|
||||
use shared::Ptr;
|
||||
use shared::bxdfs::DiffuseBxDF;
|
||||
use shared::core::bsdf::BSDF;
|
||||
use shared::core::bssrdf::BSSRDF;
|
||||
use shared::core::bxdf::BxDF;
|
||||
use shared::core::camera::Camera;
|
||||
use shared::core::geometry::Ray;
|
||||
use shared::core::interaction::{MediumInteraction, SimpleInteraction, SurfaceInteraction};
|
||||
use shared::core::material::{Material, MaterialEvalContext, MaterialTrait};
|
||||
use shared::core::sampler::{Sampler, SamplerTrait};
|
||||
use shared::core::texture::UniversalTextureEvaluator;
|
||||
use shared::spectra::SampledWavelengths;
|
||||
|
||||
pub trait InteractionGetter {
|
||||
fn get_bsdf(
|
||||
&mut self,
|
||||
r: &Ray,
|
||||
lambda: &SampledWavelengths,
|
||||
camera: &Camera,
|
||||
sampler: &mut Sampler,
|
||||
) -> Option<BSDF>;
|
||||
|
||||
fn get_bssrdf(
|
||||
&self,
|
||||
_ray: &Ray,
|
||||
lambda: &SampledWavelengths,
|
||||
_camera: &Camera,
|
||||
) -> Option<BSSRDF>;
|
||||
}
|
||||
|
||||
impl InteractionGetter for SurfaceInteraction {
|
||||
fn get_bsdf(
|
||||
&mut self,
|
||||
r: &Ray,
|
||||
|
|
@ -12,32 +38,29 @@ pub trait InteractionGetter {
|
|||
sampler: &mut Sampler,
|
||||
) -> Option<BSDF> {
|
||||
self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32);
|
||||
|
||||
let material = {
|
||||
let root_mat = self.material;
|
||||
let mut active_mat: &Material = &*root_mat;
|
||||
let mut active_mat = unsafe { self.material.as_ref() };
|
||||
let tex_eval = UniversalTextureEvaluator;
|
||||
while let Material::Mix(mix) = active_mat {
|
||||
// We need a context to evaluate the 'amount' texture
|
||||
let ctx = MaterialEvalContext::from(&*self);
|
||||
active_mat = mix.choose_material(&tex_eval, &ctx)?;
|
||||
}
|
||||
active_mat.clone()
|
||||
};
|
||||
|
||||
let ctx = MaterialEvalContext::from(&*self);
|
||||
let tex_eval = UniversalTextureEvaluator;
|
||||
let displacement = material.get_displacement();
|
||||
let normal_map = Ptr::from(material.get_normal_map().unwrap());
|
||||
let normal_map = material
|
||||
.get_normal_map()
|
||||
.map(Ptr::from)
|
||||
.unwrap_or(Ptr::null());
|
||||
if !displacement.is_null() || !normal_map.is_null() {
|
||||
// This calls the function defined above
|
||||
self.compute_bump_geom(&tex_eval, displacement, normal_map);
|
||||
}
|
||||
|
||||
let mut bsdf = material.get_bsdf(&tex_eval, &ctx, lambda);
|
||||
if get_options().force_diffuse {
|
||||
let r = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]);
|
||||
let diff_bxdf = BxDF::Diffuse(DiffuseBxDF::new(r));
|
||||
let rho = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]);
|
||||
let diff_bxdf = BxDF::Diffuse(DiffuseBxDF::new(rho));
|
||||
bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Ptr::from(&diff_bxdf));
|
||||
}
|
||||
Some(bsdf)
|
||||
|
|
@ -49,23 +72,56 @@ pub trait InteractionGetter {
|
|||
lambda: &SampledWavelengths,
|
||||
_camera: &Camera,
|
||||
) -> Option<BSSRDF> {
|
||||
let material = {
|
||||
let mut active_mat = unsafe { self.material.as_ref() };
|
||||
let tex_eval = UniversalTextureEvaluator;
|
||||
while let Material::Mix(mix) = active_mat {
|
||||
// We need a context to evaluate the 'amount' texture
|
||||
let ctx = MaterialEvalContext::from(self);
|
||||
active_mat = mix.choose_material(&tex_eval, &ctx)?;
|
||||
}
|
||||
active_mat.clone()
|
||||
};
|
||||
|
||||
let ctx = MaterialEvalContext::from(self);
|
||||
let mut active_mat = unsafe { self.material.as_ref() };
|
||||
let tex_eval = UniversalTextureEvaluator;
|
||||
while let Material::Mix(mix) = active_mat {
|
||||
let ctx = MaterialEvalContext::from(self);
|
||||
active_mat = mix.choose_material(&tex_eval, &ctx)?;
|
||||
}
|
||||
let material = active_mat.clone();
|
||||
let ctx = MaterialEvalContext::from(self);
|
||||
material.get_bssrdf(&tex_eval, &ctx, lambda)
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractionGetter for SurfaceInteraction {}
|
||||
impl InteractionGetter for MediumInteraction {}
|
||||
impl InteractionGetter for SimpleInteraction {}
|
||||
impl InteractionGetter for MediumInteraction {
|
||||
fn get_bsdf(
|
||||
&mut self,
|
||||
_r: &Ray,
|
||||
_lambda: &SampledWavelengths,
|
||||
_camera: &Camera,
|
||||
_sampler: &mut Sampler,
|
||||
) -> Option<BSDF> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_bssrdf(
|
||||
&self,
|
||||
_ray: &Ray,
|
||||
_lambda: &SampledWavelengths,
|
||||
_camera: &Camera,
|
||||
) -> Option<BSSRDF> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractionGetter for SimpleInteraction {
|
||||
fn get_bsdf(
|
||||
&mut self,
|
||||
_r: &Ray,
|
||||
_lambda: &SampledWavelengths,
|
||||
_camera: &Camera,
|
||||
_sampler: &mut Sampler,
|
||||
) -> Option<BSDF> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_bssrdf(
|
||||
&self,
|
||||
_ray: &Ray,
|
||||
_lambda: &SampledWavelengths,
|
||||
_camera: &Camera,
|
||||
) -> Option<BSSRDF> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ use crate::utils::parser::{ParserError, ParserTarget};
|
|||
use shared::Float;
|
||||
use shared::core::camera::CameraTransform;
|
||||
use shared::core::geometry::Vector3f;
|
||||
use shared::core::options::RenderingCoordinateSystem;
|
||||
use shared::spectra::RGBColorSpace;
|
||||
use shared::utils::options::RenderingCoordinateSystem;
|
||||
use shared::utils::transform;
|
||||
use shared::utils::transform::{AnimatedTransform, Transform};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
|
@ -215,10 +215,10 @@ impl ParserTarget for BasicSceneBuilder {
|
|||
fn color_space(&mut self, name: &str, loc: FileLoc) -> Result<(), ParserError> {
|
||||
let stdcs = get_colorspace_device();
|
||||
let _ = match stdcs.get_named(name) {
|
||||
Ok(cs) => {
|
||||
Some(cs) => {
|
||||
self.graphics_state.color_space = unsafe { Some(Arc::new(*cs.as_ref())) };
|
||||
}
|
||||
Err(_) => {
|
||||
None => {
|
||||
eprintln!("Error: Color space '{}' unknown at {}", name, loc);
|
||||
}
|
||||
};
|
||||
|
|
@ -276,11 +276,11 @@ impl ParserTarget for BasicSceneBuilder {
|
|||
) -> Result<(), ParserError> {
|
||||
let result = transform::look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz));
|
||||
match result {
|
||||
Ok(t) => {
|
||||
Some(t) => {
|
||||
self.for_active_transforms(|cur| cur * &t);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {} at {}", e, loc);
|
||||
None => {
|
||||
eprintln!("Error: Could not invert transform at {}", loc);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -289,11 +289,11 @@ impl ParserTarget for BasicSceneBuilder {
|
|||
fn concat_transform(&mut self, m: &[Float; 16], loc: FileLoc) -> Result<(), ParserError> {
|
||||
let result = Transform::from_flat(m);
|
||||
match result {
|
||||
Ok(t) => {
|
||||
Some(t) => {
|
||||
self.for_active_transforms(|cur| cur * &t);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {} at {}", e, loc);
|
||||
None => {
|
||||
eprintln!("Error: Could not invert transform at {}", loc);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -302,11 +302,11 @@ impl ParserTarget for BasicSceneBuilder {
|
|||
fn transform(&mut self, m: &[Float; 16], loc: FileLoc) -> Result<(), ParserError> {
|
||||
let result = Transform::from_flat(m);
|
||||
match result {
|
||||
Ok(t) => {
|
||||
Some(t) => {
|
||||
self.for_active_transforms(|_| t);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {} at {}", e, loc);
|
||||
None => {
|
||||
eprintln!("Error: Could not invert transform at {}", loc);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use crate::core::filter::CreateFilterSampler;
|
||||
use rand::Rng;
|
||||
use shared::Float;
|
||||
use shared::core::filter::FilterSampler;
|
||||
use shared::core::geometry::{Point2f, Vector2f};
|
||||
use shared::filters::LanczosSincFilter;
|
||||
use shared::utils::math::windowed_sinc;
|
||||
use shared::utils::math::{lerp, windowed_sinc};
|
||||
|
||||
pub trait LanczosFilterCreator {
|
||||
fn new(radius: Vector2f, tau: Float) -> Self;
|
||||
|
|
@ -11,13 +12,14 @@ pub trait LanczosFilterCreator {
|
|||
|
||||
impl LanczosFilterCreator for LanczosSincFilter {
|
||||
fn new(radius: Vector2f, tau: Float) -> Self {
|
||||
let sampler = FilterSampler::new(radius, move |p: Point2f| {
|
||||
let evaluate = |p: Point2f| -> Float {
|
||||
windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau)
|
||||
});
|
||||
};
|
||||
|
||||
let sampler = FilterSampler::new(radius, evaluate);
|
||||
let sqrt_samples = 64;
|
||||
let n_samples = sqrt_samples * sqrt_samples;
|
||||
let area = (2.0 * self.radius.x()) * (2.0 * self.radius.y());
|
||||
let area = (2.0 * radius.x()) * (2.0 * radius.y());
|
||||
let mut sum = 0.0;
|
||||
let mut rng = rand::rng();
|
||||
|
||||
|
|
@ -28,10 +30,10 @@ impl LanczosFilterCreator for LanczosSincFilter {
|
|||
(y as Float + rng.random::<Float>()) / sqrt_samples as Float,
|
||||
);
|
||||
let p = Point2f::new(
|
||||
lerp(u.x(), -self.radius.x(), self.radius.x()),
|
||||
lerp(u.y(), -self.radius.y(), self.radius.y()),
|
||||
lerp(u.x(), -radius.x(), radius.x()),
|
||||
lerp(u.y(), -radius.y(), radius.y()),
|
||||
);
|
||||
sum += self.evaluate(p);
|
||||
sum += evaluate(p);
|
||||
}
|
||||
}
|
||||
let integral = sum / n_samples as Float * area;
|
||||
|
|
|
|||
|
|
@ -1,9 +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::{BSDF, BSDFSample};
|
||||
use shared::core::bsdf::{BSDFSample, BSDF};
|
||||
use shared::core::bxdf::{BxDFFlags, FArgs, TransportMode};
|
||||
use shared::core::camera::Camera;
|
||||
use shared::core::film::VisibleSurface;
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ use crate::Arena;
|
|||
use crate::core::camera::InitMetadata;
|
||||
use crate::core::film::FilmTrait;
|
||||
use crate::core::image::{Image, ImageIO, ImageMetadata};
|
||||
use crate::globals::get_options;
|
||||
use crate::spectra::get_spectra_context;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use shared::Float;
|
||||
use shared::core::camera::{Camera, CameraTrait};
|
||||
use shared::core::geometry::{Bounds2i, Point2i, VectorLike};
|
||||
use shared::core::options::get_options;
|
||||
use shared::core::sampler::get_camera_sample;
|
||||
use shared::core::sampler::{Sampler, SamplerTrait};
|
||||
use shared::spectra::SampledSpectrum;
|
||||
|
|
@ -199,11 +199,9 @@ pub fn render<T>(
|
|||
camera.init_metadata(&mut metadata);
|
||||
|
||||
if let Some(out_path) = &options.mse_reference_output {
|
||||
camera.get_film().write_image(
|
||||
&metadata,
|
||||
1.0 / wave_start as Float,
|
||||
out_path.as_str(),
|
||||
);
|
||||
camera
|
||||
.get_film()
|
||||
.write_image(&metadata, 1.0 / wave_start as Float, out_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::core::image::Image;
|
||||
use crate::core::material::CreateMaterial;
|
||||
use crate::core::texture::SpectrumTexture;
|
||||
use crate::globals::get_options;
|
||||
use crate::spectra::data::get_named_spectrum;
|
||||
use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload};
|
||||
use anyhow::{Result, bail};
|
||||
|
|
@ -66,6 +67,7 @@ impl CreateMaterial for CoatedDiffuseMaterial {
|
|||
remap_roughness,
|
||||
max_depth as u32,
|
||||
n_samples as u32,
|
||||
get_options().seed,
|
||||
);
|
||||
|
||||
Ok(Material::CoatedDiffuse(specific))
|
||||
|
|
@ -167,6 +169,7 @@ impl CreateMaterial for CoatedConductorMaterial {
|
|||
max_depth as u32,
|
||||
n_samples as u32,
|
||||
remap_roughness,
|
||||
get_options().seed,
|
||||
);
|
||||
arena.alloc(material);
|
||||
Ok(Material::CoatedConductor(material))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use super::*;
|
||||
use crate::globals::get_options;
|
||||
use crate::utils::math::compute_radical_inverse_permutations;
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::options::get_options;
|
||||
use shared::core::sampler::{HaltonSampler, MAX_HALTON_RESOLUTION, RandomizeStrategy};
|
||||
|
||||
pub trait CreateHaltonSampler {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::*;
|
||||
use crate::globals::get_options;
|
||||
use anyhow::Result;
|
||||
use shared::core::options::get_options;
|
||||
use shared::core::sampler::IndependentSampler;
|
||||
|
||||
impl CreateSampler for IndependentSampler {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::*;
|
||||
use crate::globals::get_options;
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::options::get_options;
|
||||
use shared::core::sampler::{PaddedSobolSampler, RandomizeStrategy, SobolSampler, ZSobolSampler};
|
||||
|
||||
impl CreateSampler for SobolSampler {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::*;
|
||||
use crate::globals::get_options;
|
||||
use anyhow::Result;
|
||||
use shared::core::options::get_options;
|
||||
use shared::core::sampler::StratifiedSampler;
|
||||
|
||||
impl CreateSampler for StratifiedSampler {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use shared::utils::math::{
|
|||
|
||||
#[inline(always)]
|
||||
pub fn f16_to_f32(bits: u16) -> f32 {
|
||||
#[cfg(target_os = "cuda")]
|
||||
#[cfg(feature = "cuda")]
|
||||
{
|
||||
// Use hardware intrinsic on CUDA
|
||||
// Cast bits to cuda_f16, then cast to f32
|
||||
|
|
@ -15,7 +15,7 @@ pub fn f16_to_f32(bits: u16) -> f32 {
|
|||
half_val.to_f32()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "spirv")]
|
||||
#[cfg(feature = "vulkan")]
|
||||
{
|
||||
// Use shared logic or spirv-std intrinsics if available.
|
||||
// Sadly, f16 support in rust-gpu is still maturing.
|
||||
|
|
@ -23,7 +23,7 @@ pub fn f16_to_f32(bits: u16) -> f32 {
|
|||
f16_to_f32_software(bits)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "cuda", target_arch = "spirv")))]
|
||||
#[cfg(not(any(feature = "cuda", feature = "vulkan")))]
|
||||
{
|
||||
f16::from_bits(bits).to_f32()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ use crate::utils::backend::GpuAllocator;
|
|||
use crate::utils::containers::Array2D;
|
||||
use shared::Float;
|
||||
use shared::core::geometry::{Bounds2f, Point2i, Vector2f, Vector2i};
|
||||
use shared::utils::Ptr;
|
||||
use shared::utils::sampling::{
|
||||
AliasTable, Bin, DevicePiecewiseConstant1D, DevicePiecewiseConstant2D, DeviceSummedAreaTable,
|
||||
DeviceWindowedPiecewiseConstant2D, PiecewiseLinear2D,
|
||||
};
|
||||
use shared::utils::{Ptr, gpu_array_from_fn};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -240,7 +240,7 @@ impl<const N: usize> PiecewiseLinear2DHost<N> {
|
|||
|
||||
let mut param_size = [0u32; N];
|
||||
let mut param_strides = [0u32; N];
|
||||
let param_values = std::array::from_fn(|i| param_values[i].to_vec());
|
||||
let param_values = gpu_array_from_fn(|i| param_values[i].to_vec());
|
||||
|
||||
let mut slices: u32 = 1;
|
||||
for i in (0..N).rev() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue