Just changing var names
This commit is contained in:
parent
fa4692bfe6
commit
a4c751bbcd
80 changed files with 1213 additions and 3532 deletions
|
|
@ -54,6 +54,7 @@ cust_raw = { git = "https://github.com/Rust-GPU/Rust-CUDA", branch = "main", def
|
|||
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 }
|
||||
syn = "2.0.117"
|
||||
|
||||
[build-dependencies]
|
||||
spirv-builder = { git = "https://github.com/rust-gpu/rust-gpu", branch = "main", optional = true }
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
#![allow(unused)]
|
||||
|
||||
[package]
|
||||
name = "ptex-filter"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
pkg-config = "0.3"
|
||||
|
||||
[dependencies]
|
||||
ptex = "0.3.0"
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
fn main() {
|
||||
println!("cargo:rerun-if-changed=cpp/ptex_filter_wrapper.cpp");
|
||||
|
||||
let home = std::env::var("HOME").unwrap();
|
||||
let ptex_include = format!("{}/.local/include", home);
|
||||
let ptex_lib = format!("{}/.local/lib", home);
|
||||
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.file("cpp/ptex_filter_wrapper.cpp")
|
||||
.include(&ptex_include)
|
||||
.flag("-std=c++17")
|
||||
.compile("ptex_filter_wrapper");
|
||||
|
||||
println!("cargo:rustc-link-search=native={}", ptex_lib);
|
||||
println!("cargo:rustc-link-lib=Ptex");
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
#include "ptex_filter_wrapper.h"
|
||||
#include <Ptexture.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
PtexFilterHandle ptex_filter_create(PtexTextureHandle texture, const PtexFilterOptions* opts) {
|
||||
Ptex::PtexTexture* tex = static_cast<Ptex::PtexTexture*>(texture);
|
||||
if (!tex || !opts) return nullptr;
|
||||
|
||||
Ptex::PtexFilter::Options ptex_opts;
|
||||
ptex_opts.filter = static_cast<Ptex::PtexFilter::FilterType>(opts->filter);
|
||||
ptex_opts.lerp = opts->lerp;
|
||||
ptex_opts.sharpness = opts->sharpness;
|
||||
ptex_opts.noedgeblend = opts->noedgeblend != 0;
|
||||
|
||||
return Ptex::PtexFilter::getFilter(tex, ptex_opts);
|
||||
}
|
||||
|
||||
void ptex_filter_eval(
|
||||
PtexFilterHandle filter,
|
||||
float* result,
|
||||
int32_t first_channel,
|
||||
int32_t num_channels,
|
||||
int32_t face_id,
|
||||
float u, float v,
|
||||
float dudx, float dvdx,
|
||||
float dudy, float dvdy
|
||||
) {
|
||||
Ptex::PtexFilter* f = static_cast<Ptex::PtexFilter*>(filter);
|
||||
if (f && result) {
|
||||
f->eval(result, first_channel, num_channels, face_id, u, v, dudx, dvdx, dudy, dvdy);
|
||||
}
|
||||
}
|
||||
|
||||
void ptex_filter_release(PtexFilterHandle filter) {
|
||||
Ptex::PtexFilter* f = static_cast<Ptex::PtexFilter*>(filter);
|
||||
if (f) {
|
||||
f->release();
|
||||
}
|
||||
}
|
||||
|
||||
PtexTextureHandle ptex_texture_open(const char* filename, char** error_str) {
|
||||
Ptex::String error;
|
||||
Ptex::PtexTexture* tex = Ptex::PtexTexture::open(filename, error);
|
||||
|
||||
if (!tex && error_str) {
|
||||
*error_str = strdup(error.c_str());
|
||||
}
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
void ptex_texture_release(PtexTextureHandle texture) {
|
||||
Ptex::PtexTexture* tex = static_cast<Ptex::PtexTexture*>(texture);
|
||||
if (tex) {
|
||||
tex->release();
|
||||
}
|
||||
}
|
||||
|
||||
int32_t ptex_texture_num_channels(PtexTextureHandle texture) {
|
||||
Ptex::PtexTexture* tex = static_cast<Ptex::PtexTexture*>(texture);
|
||||
return tex ? tex->numChannels() : 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef void* PtexTextureHandle;
|
||||
typedef void* PtexFilterHandle;
|
||||
|
||||
typedef enum {
|
||||
PTEX_FILTER_POINT = 0,
|
||||
PTEX_FILTER_BILINEAR = 1,
|
||||
PTEX_FILTER_BOX = 2,
|
||||
PTEX_FILTER_GAUSSIAN = 3,
|
||||
PTEX_FILTER_BICUBIC = 4,
|
||||
PTEX_FILTER_BSPLINE = 5,
|
||||
PTEX_FILTER_CATMULLROM = 6,
|
||||
PTEX_FILTER_MITCHELL = 7
|
||||
} PtexFilterType;
|
||||
|
||||
typedef struct {
|
||||
PtexFilterType filter;
|
||||
int32_t lerp;
|
||||
float sharpness;
|
||||
int32_t noedgeblend;
|
||||
} PtexFilterOptions;
|
||||
|
||||
PtexTextureHandle ptex_texture_open(const char* filename, char** error_str);
|
||||
void ptex_filter_release(PtexFilterHandle filter);
|
||||
int32_t ptex_texture_num_channels(PtexTextureHandle texture);
|
||||
PtexFilterHandle ptex_filter_create(PtexTextureHandle texture, const PtexFilterOptions* opts);
|
||||
|
||||
|
||||
void ptex_filter_eval(
|
||||
PtexFilterHandle filter,
|
||||
float* result,
|
||||
int32_t first_channel,
|
||||
int32_t num_channels,
|
||||
int32_t face_id,
|
||||
float u, float v,
|
||||
float dudx, float dvdx,
|
||||
float dudy, float dvdy
|
||||
);
|
||||
void ptex_filter_release(PtexFilterHandle filter);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
use std::ffi::c_void;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PtexFilterType {
|
||||
Point = 0,
|
||||
Bilinear = 1,
|
||||
Box = 2,
|
||||
Gaussian = 3,
|
||||
Bicubic = 4,
|
||||
BSpline = 5,
|
||||
CatmullRom = 6,
|
||||
Mitchell = 7,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PtexFilterOptions {
|
||||
pub filter: PtexFilterType,
|
||||
pub lerp: i32,
|
||||
pub sharpness: f32,
|
||||
pub noedgeblend: i32,
|
||||
}
|
||||
|
||||
impl Default for PtexFilterOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
filter: PtexFilterType::BSpline,
|
||||
lerp: 1,
|
||||
sharpness: 0.0,
|
||||
noedgeblend: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn ptex_filter_create(texture: *mut c_void, opts: *const PtexFilterOptions) -> *mut c_void;
|
||||
|
||||
pub fn ptex_filter_eval(
|
||||
filter: *mut c_void,
|
||||
result: *mut f32,
|
||||
first_channel: i32,
|
||||
num_channels: i32,
|
||||
face_id: i32,
|
||||
u: f32,
|
||||
v: f32,
|
||||
dudx: f32,
|
||||
dvdx: f32,
|
||||
dudy: f32,
|
||||
dvdy: f32,
|
||||
);
|
||||
|
||||
pub fn ptex_filter_release(filter: *mut c_void);
|
||||
pub fn ptex_texture_open(
|
||||
filename: *const std::ffi::c_char,
|
||||
error_str: *mut *mut std::ffi::c_char,
|
||||
) -> *mut c_void;
|
||||
pub fn ptex_texture_release(texture: *mut c_void);
|
||||
pub fn ptex_texture_num_channels(texture: *mut c_void) -> i32;
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
#![allow(unused)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod ffi;
|
||||
|
||||
pub use ffi::{ptex_filter_create, PtexFilterOptions, PtexFilterType};
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
pub struct PtexFilter {
|
||||
handle: NonNull<c_void>,
|
||||
_marker: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl PtexFilter {
|
||||
/// Creates a new Ptex filter pointer
|
||||
///
|
||||
/// # Safety
|
||||
pub unsafe fn new(texture_ptr: *mut c_void, opts: &PtexFilterOptions) -> Option<Self> {
|
||||
let handle = ffi::ptex_filter_create(texture_ptr, opts);
|
||||
NonNull::new(handle).map(|h| Self {
|
||||
handle: h,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn eval(
|
||||
&self,
|
||||
face_id: i32,
|
||||
u: f32,
|
||||
v: f32,
|
||||
dudx: f32,
|
||||
dvdx: f32,
|
||||
dudy: f32,
|
||||
dvdy: f32,
|
||||
num_channels: i32,
|
||||
) -> [f32; 4] {
|
||||
let mut result = [0.0f32; 4];
|
||||
unsafe {
|
||||
ffi::ptex_filter_eval(
|
||||
self.handle.as_ptr(),
|
||||
result.as_mut_ptr(),
|
||||
0,
|
||||
num_channels.min(4),
|
||||
face_id,
|
||||
u,
|
||||
v,
|
||||
dudx,
|
||||
dvdx,
|
||||
dudy,
|
||||
dvdy,
|
||||
);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PtexFilter {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ffi::ptex_filter_release(self.handle.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,16 @@
|
|||
use crate::PI;
|
||||
use crate::core::camera::{CameraBase, CameraRay, CameraTrait, CameraTransform};
|
||||
use crate::core::color::SRGB;
|
||||
use crate::core::film::Film;
|
||||
use crate::core::geometry::{
|
||||
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector2i, Vector3f, VectorLike,
|
||||
};
|
||||
use crate::core::image::{DeviceImage, PixelFormat};
|
||||
use crate::core::image::{Image, PixelFormat};
|
||||
use crate::core::medium::Medium;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::sampler::CameraSample;
|
||||
use crate::core::scattering::refract;
|
||||
use crate::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::math::{lerp, quadratic, square};
|
||||
use crate::{Float, GVec, Ptr, PI};
|
||||
use num_traits::Float as NumFloat;
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -39,8 +37,8 @@ pub struct RealisticCamera {
|
|||
pub base: CameraBase,
|
||||
pub focus_distance: Float,
|
||||
pub set_aperture_diameter: Float,
|
||||
pub aperture_image: Ptr<DeviceImage>,
|
||||
pub element_interfaces: Ptr<LensElementInterface>,
|
||||
pub aperture_image: Ptr<Image>,
|
||||
pub element_interfaces: GVec<LensElementInterface>,
|
||||
pub n_elements: usize,
|
||||
pub physical_extent: Bounds2f,
|
||||
pub exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES],
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ 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::{Float, Ptr, GVec, gvec};
|
||||
use crate::utils::Ptr;
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -15,40 +16,40 @@ pub struct LinearBVHNode {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DeviceBVHAggregate {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BVHAggregate {
|
||||
pub max_prims_in_node: u32,
|
||||
pub primitives: Ptr<Primitive>,
|
||||
pub primitives: GVec<Primitive>,
|
||||
pub primitive_count: u32,
|
||||
pub nodes: Ptr<LinearBVHNode>,
|
||||
pub nodes: GVec<LinearBVHNode>,
|
||||
pub node_count: u32,
|
||||
}
|
||||
|
||||
impl DeviceBVHAggregate {
|
||||
pub const fn empty() -> Self {
|
||||
impl BVHAggregate {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
max_prims_in_node: 0,
|
||||
primitives: Ptr::null(),
|
||||
primitives: gvec(),
|
||||
primitive_count: 0,
|
||||
nodes: Ptr::null(),
|
||||
nodes: gvec(),
|
||||
node_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn node(&self, i: usize) -> &LinearBVHNode {
|
||||
unsafe { self.nodes.at(i) }
|
||||
unsafe { self.nodes.get_unchecked(i) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn primitive(&self, i: usize) -> &Primitive {
|
||||
unsafe { self.primitives.at(i) }
|
||||
unsafe { self.primitives.get_unchecked(i) }
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimitiveTrait for DeviceBVHAggregate {
|
||||
impl PrimitiveTrait for BVHAggregate {
|
||||
fn bounds(&self) -> Bounds3f {
|
||||
if self.nodes.is_null() || self.node_count == 0 {
|
||||
if self.nodes.is_empty() || self.node_count == 0 {
|
||||
Bounds3f::default()
|
||||
} else {
|
||||
self.node(0).bounds
|
||||
|
|
@ -56,7 +57,7 @@ impl PrimitiveTrait for DeviceBVHAggregate {
|
|||
}
|
||||
|
||||
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||
if self.nodes.is_null() {
|
||||
if self.nodes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +125,7 @@ impl PrimitiveTrait for DeviceBVHAggregate {
|
|||
}
|
||||
|
||||
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
|
||||
if self.nodes.is_null() || self.node_count == 0 {
|
||||
if self.nodes.is_empty() || self.node_count == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
use crate::core::camera::CameraTransform;
|
||||
use crate::core::color::{MatrixMulColor, RGB, SRGB, XYZ, white_balance};
|
||||
use crate::core::color::{white_balance, MatrixMulColor, RGB, SRGB, XYZ};
|
||||
use crate::core::filter::{Filter, FilterTrait};
|
||||
use crate::core::geometry::{
|
||||
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Tuple, Vector2f, Vector2fi,
|
||||
Vector2i, Vector3f,
|
||||
};
|
||||
use crate::core::image::{DeviceImage, PixelFormat};
|
||||
use crate::core::image::{Image, PixelFormat};
|
||||
use crate::core::interaction::SurfaceInteraction;
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait, StandardSpectra};
|
||||
use crate::spectra::{
|
||||
ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
|
||||
PiecewiseLinearSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, colorspace,
|
||||
colorspace, ConstantSpectrum, DenselySampledSpectrum, PiecewiseLinearSpectrum, RGBColorSpace,
|
||||
SampledSpectrum, SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
|
||||
};
|
||||
use crate::utils::containers::DeviceArray2D;
|
||||
use crate::utils::math::linear_least_squares;
|
||||
use crate::utils::math::{SquareMatrix, wrap_equal_area_square};
|
||||
use crate::utils::math::{wrap_equal_area_square, SquareMatrix};
|
||||
use crate::utils::sampling::VarianceEstimator;
|
||||
use crate::utils::transform::AnimatedTransform;
|
||||
use crate::utils::{AtomicFloat, Ptr, gpu_array_from_fn};
|
||||
use crate::utils::{gpu_array_from_fn, AtomicFloat};
|
||||
use crate::{Array2D, Float, Ptr};
|
||||
use num_traits::Float as NumFloat;
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -29,7 +28,7 @@ pub struct RGBFilm {
|
|||
pub write_fp16: bool,
|
||||
pub filter_integral: Float,
|
||||
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||
pub pixels: DeviceArray2D<RGBPixel>,
|
||||
pub pixels: Array2D<RGBPixel>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -59,7 +58,7 @@ impl RGBFilm {
|
|||
&mut self.base
|
||||
}
|
||||
|
||||
pub fn get_sensor(&self) -> &DevicePixelSensor {
|
||||
pub fn get_sensor(&self) -> &PixelSensor {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
{
|
||||
if self.base.sensor.is_null() {
|
||||
|
|
@ -206,7 +205,7 @@ pub struct GBufferFilm {
|
|||
pub base: FilmBase,
|
||||
pub output_from_render: AnimatedTransform,
|
||||
pub apply_inverse: bool,
|
||||
pub pixels: DeviceArray2D<GBufferPixel>,
|
||||
pub pixels: Array2D<GBufferPixel>,
|
||||
pub colorspace: RGBColorSpace,
|
||||
pub max_component_value: Float,
|
||||
pub write_fp16: bool,
|
||||
|
|
@ -223,7 +222,7 @@ impl GBufferFilm {
|
|||
&mut self.base
|
||||
}
|
||||
|
||||
pub fn get_sensor(&self) -> &DevicePixelSensor {
|
||||
pub fn get_sensor(&self) -> &PixelSensor {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
{
|
||||
if self.base.sensor.is_null() {
|
||||
|
|
@ -359,7 +358,7 @@ pub struct SpectralFilm {
|
|||
pub max_component_value: Float,
|
||||
pub write_fp16: bool,
|
||||
pub filter_integral: Float,
|
||||
pub pixels: DeviceArray2D<SpectralPixel>,
|
||||
pub pixels: Array2D<SpectralPixel>,
|
||||
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||
pub bucket_sums: *mut f64,
|
||||
pub weight_sums: *mut f64,
|
||||
|
|
@ -404,7 +403,7 @@ impl SpectralFilm {
|
|||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct DevicePixelSensor {
|
||||
pub struct PixelSensor {
|
||||
pub xyz_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||
pub r_bar: DenselySampledSpectrum,
|
||||
pub g_bar: DenselySampledSpectrum,
|
||||
|
|
@ -412,7 +411,7 @@ pub struct DevicePixelSensor {
|
|||
pub imaging_ratio: Float,
|
||||
}
|
||||
|
||||
impl DevicePixelSensor {
|
||||
impl PixelSensor {
|
||||
pub fn project_reflectance<T>(
|
||||
refl: &Spectrum,
|
||||
illum: &Spectrum,
|
||||
|
|
@ -492,7 +491,7 @@ pub struct FilmBase {
|
|||
pub pixel_bounds: Bounds2i,
|
||||
pub filter: Filter,
|
||||
pub diagonal: Float,
|
||||
pub sensor: Ptr<DevicePixelSensor>,
|
||||
pub sensor: Ptr<PixelSensor>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use crate::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::filters::*;
|
||||
use crate::utils::containers::DeviceArray2D;
|
||||
use crate::utils::math::{gaussian, gaussian_integral, lerp, sample_tent, windowed_sinc};
|
||||
use crate::utils::sampling::DevicePiecewiseConstant2D;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::{DeviceArray2D, Float};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
pub struct FilterSample {
|
||||
|
|
@ -13,22 +12,44 @@ pub struct FilterSample {
|
|||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct DeviceFilterSampler {
|
||||
pub struct FilterSampler {
|
||||
pub domain: Bounds2f,
|
||||
pub distrib: DevicePiecewiseConstant2D,
|
||||
pub f: DeviceArray2D<Float>,
|
||||
pub distrib: PiecewiseConstant2D,
|
||||
pub f: Array2D<Float>,
|
||||
}
|
||||
|
||||
impl FilterSampler {
|
||||
pub fn new<F>(radius: Vector2f, func: F) -> Self
|
||||
where
|
||||
F: Fn(Point2f) -> Float,
|
||||
{
|
||||
let domain = Bounds2f::from_points(
|
||||
Point2f::new(-radius.x(), -radius.y()),
|
||||
Point2f::new(radius.x(), radius.y()),
|
||||
);
|
||||
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() {
|
||||
let p = domain.lerp(Point2f::new(
|
||||
(x as Float + 0.5) / f.x_size() as Float,
|
||||
(y as Float + 0.5) / f.y_size() as Float,
|
||||
));
|
||||
f[(x as i32, y as i32)] = func(p);
|
||||
}
|
||||
}
|
||||
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
|
||||
Self { domain, distrib, f }
|
||||
}
|
||||
|
||||
impl DeviceFilterSampler {
|
||||
pub fn sample(&self, u: Point2f) -> FilterSample {
|
||||
let (p, pdf, pi) = self.distrib.sample(u);
|
||||
|
||||
if pdf == 0.0 {
|
||||
return FilterSample { p, weight: 0.0 };
|
||||
}
|
||||
|
||||
let idx = pi.x() as u32 + self.f.x_size();
|
||||
let weight = *self.f.get_linear(idx as usize) / pdf;
|
||||
let idx = pi.x() as usize + pi.y() as usize * self.f.x_size();
|
||||
let weight = self.f.as_slice()[idx] / pdf;
|
||||
FilterSample { p, weight }
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +59,7 @@ pub trait FilterTrait {
|
|||
fn radius(&self) -> Vector2f;
|
||||
fn evaluate(&self, p: Point2f) -> Float;
|
||||
fn integral(&self) -> Float;
|
||||
fn sample(&self, u: Point2f) -> DeviceFilterSample;
|
||||
fn sample(&self, u: Point2f) -> FilterSample;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use crate::Float;
|
||||
use crate::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
|
||||
use crate::core::geometry::{Bounds2f, Point2f, Point2fi, Point2i};
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::containers::DeviceArray2D;
|
||||
use crate::utils::math::{f16_to_f32_software, lerp, square};
|
||||
use crate::Float;
|
||||
use crate::GVec;
|
||||
use core::hash;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use num_traits::Float as NumFloat;
|
||||
|
|
@ -68,119 +68,305 @@ impl PixelFormat {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Pixels {
|
||||
ptr: Ptr<u8>,
|
||||
pixels: GVec<u8>,
|
||||
format: PixelFormat,
|
||||
}
|
||||
|
||||
impl Pixels {
|
||||
pub fn new_u8(ptr: Ptr<u8>) -> Self {
|
||||
pub fn new(data: GVec<u8>, format: PixelFormat) -> Self {
|
||||
Self { data, format }
|
||||
}
|
||||
|
||||
pub fn format(&self) -> PixelFormat {
|
||||
self.format
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *const u8 {
|
||||
self.data.as_ptr()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
pub fn texel_count(&self) -> usize {
|
||||
self.data.len() / self.format.texel_bytes()
|
||||
}
|
||||
|
||||
pub unsafe fn read_u8(&self, texel_offset: usize) -> u8 {
|
||||
*self.data.as_ptr().add(texel_offset)
|
||||
}
|
||||
|
||||
pub unsafe fn read_f16(&self, texel_offset: usize) -> u16 {
|
||||
let byte_offset = texel_offset * 2;
|
||||
*(self.data.as_ptr().add(byte_offset) as *const u16)
|
||||
}
|
||||
|
||||
pub unsafe fn read_f32(&self, texel_offset: usize) -> f32 {
|
||||
let byte_offset = texel_offset * 4;
|
||||
*(self.data.as_ptr().add(byte_offset) as *const f32)
|
||||
}
|
||||
|
||||
pub unsafe fn read(&self, texel_offset: usize, encoding: &ColorEncoding) -> Float {
|
||||
match self.format {
|
||||
PixelFormat::U8 => encoding.to_linear_scalar(self.read_u8(texel_offset)),
|
||||
PixelFormat::F16 => f16_to_f32_software(self.read_f16(texel_offset)),
|
||||
PixelFormat::F32 => self.read_f32(texel_offset),
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn write_u8(&mut self, texel_offset: usize, val: u8) {
|
||||
*self.data.as_mut_ptr().add(texel_offset) = val;
|
||||
}
|
||||
|
||||
pub unsafe fn write_f16(&mut self, texel_offset: usize, val: u16) {
|
||||
let byte_offset = texel_offset * 2;
|
||||
*(self.data.as_mut_ptr().add(byte_offset) as *mut u16) = val;
|
||||
}
|
||||
|
||||
pub unsafe fn write_f32(&mut self, texel_offset: usize, val: f32) {
|
||||
let byte_offset = texel_offset * 4;
|
||||
*(self.data.as_mut_ptr().add(byte_offset) as *mut f32) = val;
|
||||
}
|
||||
|
||||
pub fn empty(texel_count: usize, format: PixelFormat) -> Self {
|
||||
let byte_count = texel_count * format.texel_bytes();
|
||||
let mut data = gvec_with_capacity(byte_count);
|
||||
data.resize(byte_count, 0u8);
|
||||
Self { data, format }
|
||||
}
|
||||
|
||||
pub fn from_u8_slice(slice: &[u8]) -> Self {
|
||||
let mut data = gvec_with_capacity(slice.len());
|
||||
data.extend_from_slice(slice);
|
||||
Self {
|
||||
ptr,
|
||||
data,
|
||||
format: PixelFormat::U8,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_f16(ptr: Ptr<u16>) -> Self {
|
||||
pub fn from_f32_slice(slice: &[f32]) -> Self {
|
||||
let byte_len = slice.len() * 4;
|
||||
let mut data = gvec_with_capacity(byte_len);
|
||||
let bytes = unsafe { core::slice::from_raw_parts(slice.as_ptr() as *const u8, byte_len) };
|
||||
data.extend_from_slice(bytes);
|
||||
Self {
|
||||
ptr: Ptr::from_raw(ptr.as_raw() as *const u8),
|
||||
format: PixelFormat::F16,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_f32(ptr: Ptr<f32>) -> Self {
|
||||
Self {
|
||||
ptr: Ptr::from_raw(ptr.as_raw() as *const u8),
|
||||
data,
|
||||
format: PixelFormat::F32,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn read_u8(&self, byte_offset: usize) -> u8 {
|
||||
unsafe { *self.ptr.as_raw().add(byte_offset) }
|
||||
pub fn from_f16_slice(slice: &[u16]) -> Self {
|
||||
let byte_len = slice.len() * 2;
|
||||
let mut data = gvec_with_capacity(byte_len);
|
||||
let bytes = unsafe { core::slice::from_raw_parts(slice.as_ptr() as *const u8, byte_len) };
|
||||
data.extend_from_slice(bytes);
|
||||
Self {
|
||||
data,
|
||||
format: PixelFormat::F16,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn read_f16(&self, elem_offset: usize) -> u16 {
|
||||
let byte_offset = elem_offset * 2;
|
||||
unsafe { *(self.ptr.as_raw().add(byte_offset) as *const u16) }
|
||||
pub fn as_u8_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
unsafe fn read_f32(&self, elem_offset: usize) -> f32 {
|
||||
let byte_offset = elem_offset * 4;
|
||||
unsafe { *(self.ptr.as_raw().add(byte_offset) as *const f32) }
|
||||
pub fn as_f16_mut(&mut self) -> &mut [u16] {
|
||||
assert_eq!(self.format, PixelFormat::F16);
|
||||
unsafe {
|
||||
core::slice::from_raw_parts_mut(self.data.as_mut_ptr() as *mut u16, self.data.len() / 2)
|
||||
}
|
||||
}
|
||||
|
||||
// Unsure if ill need this
|
||||
// pub unsafe fn read(&self, offset: usize) -> Float {
|
||||
// match self.format {
|
||||
// PixelFormat::U8 => unsafe { self.read_u8(offset) as Float / 255.0 },
|
||||
// PixelFormat::F16 => unsafe { f16_to_f32_software(self.read_f16(offset)) },
|
||||
// PixelFormat::F32 => unsafe { self.read_f32(offset) },
|
||||
// }
|
||||
// }
|
||||
pub fn as_f32_slice(&self) -> &[f32] {
|
||||
assert_eq!(self.format, PixelFormat::F32);
|
||||
unsafe {
|
||||
core::slice::from_raw_parts(self.data.as_ptr() as *const f32, self.data.len() / 4)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ImageBase {
|
||||
pub fn as_f32_slice_mut(&mut self) -> &mut [f32] {
|
||||
assert_eq!(self.format, PixelFormat::F32);
|
||||
unsafe {
|
||||
core::slice::from_raw_parts_mut(self.data.as_mut_ptr() as *mut f32, self.data.len() / 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Image {
|
||||
pub format: PixelFormat,
|
||||
pub encoding: ColorEncoding,
|
||||
pub resolution: Point2i,
|
||||
pub n_channels: i32,
|
||||
pub pixels: Pixels,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn new(
|
||||
format: PixelFormat,
|
||||
resolution: Point2i,
|
||||
n_channels: i32,
|
||||
encoding: ColorEncoding,
|
||||
) -> Self {
|
||||
let texel_count = (resolution.x() * resolution.y()) as usize * n_channels as usize;
|
||||
Self {
|
||||
format,
|
||||
encoding,
|
||||
resolution,
|
||||
n_channels,
|
||||
pixels: Pixels::empty(texel_count, format),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_u8(
|
||||
data: &[u8],
|
||||
resolution: Point2i,
|
||||
n_channels: i32,
|
||||
encoding: ColorEncoding,
|
||||
) -> Self {
|
||||
let expected = (resolution.x() * resolution.y()) as usize * n_channels as usize;
|
||||
assert_eq!(data.len(), expected, "Pixel data size mismatch");
|
||||
Self {
|
||||
format: PixelFormat::U8,
|
||||
encoding,
|
||||
resolution,
|
||||
n_channels,
|
||||
pixels: Pixels::from_u8_slice(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_f32(data: &[f32], resolution: Point2i, n_channels: i32) -> Self {
|
||||
let expected = (resolution.x() * resolution.y()) as usize * n_channels as usize;
|
||||
assert_eq!(data.len(), expected, "Pixel data size mismatch");
|
||||
Self {
|
||||
format: PixelFormat::F32,
|
||||
encoding: LINEAR,
|
||||
resolution,
|
||||
n_channels,
|
||||
pixels: Pixels::from_f32_slice(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_f16(data: &[u16], resolution: Point2i, n_channels: i32) -> Self {
|
||||
let expected = (resolution.x() * resolution.y()) as usize * n_channels as usize;
|
||||
assert_eq!(data.len(), expected, "Pixel data size mismatch");
|
||||
Self {
|
||||
format: PixelFormat::F16,
|
||||
encoding: LINEAR,
|
||||
resolution,
|
||||
n_channels,
|
||||
pixels: Pixels::from_f16_slice(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolution(&self) -> Point2i {
|
||||
self.resolution
|
||||
}
|
||||
|
||||
pub fn n_channels(&self) -> i32 {
|
||||
self.n_channels
|
||||
}
|
||||
|
||||
pub fn format(&self) -> PixelFormat {
|
||||
self.format
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.resolution.x() > 0 && self.resolution.y() > 0
|
||||
}
|
||||
|
||||
pub fn pixel_offset(&self, p: Point2i) -> usize {
|
||||
let width = self.resolution.x() as usize;
|
||||
(p.y() as usize * width + p.x() as usize) * self.n_channels as usize
|
||||
}
|
||||
|
||||
impl ImageBase {
|
||||
pub fn remap_pixel_coords(&self, p: &mut Point2i, wrap_mode: WrapMode2D) -> bool {
|
||||
let resolution = self.resolution;
|
||||
for i in 0..2 {
|
||||
if p[i] >= 0 && p[i] < resolution[i] {
|
||||
if p[i] >= 0 && p[i] < self.resolution[i] {
|
||||
continue;
|
||||
}
|
||||
match wrap_mode.uv[i] {
|
||||
WrapMode::Black => return false,
|
||||
WrapMode::Clamp => p[i] = p[i].clamp(0, resolution[i] - 1),
|
||||
WrapMode::Repeat => p[i] = p[i].rem_euclid(resolution[i]),
|
||||
WrapMode::Clamp => p[i] = p[i].clamp(0, self.resolution[i] - 1),
|
||||
WrapMode::Repeat => p[i] = p[i].rem_euclid(self.resolution[i]),
|
||||
WrapMode::OctahedralSphere => {
|
||||
p[i] = p[i].clamp(0, resolution[i] - 1);
|
||||
p[i] = p[i].clamp(0, self.resolution[i] - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn get_channel(&self, p: Point2i, c: i32) -> Float {
|
||||
self.get_channel_with_wrap(p, c, WrapMode::Clamp.into())
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DeviceImage {
|
||||
pub base: ImageBase,
|
||||
pub pixels: Pixels,
|
||||
pub fn get_channel_with_wrap(&self, mut p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float {
|
||||
if !self.remap_pixel_coords(&mut p, wrap_mode) {
|
||||
return 0.0;
|
||||
}
|
||||
let offset = self.pixel_offset(p) + c as usize;
|
||||
unsafe { self.pixels.read(offset, &self.encoding) }
|
||||
}
|
||||
|
||||
impl DeviceImage {
|
||||
pub fn base(&self) -> ImageBase {
|
||||
self.base
|
||||
pub fn get_channels_array<const N: usize>(&self, p: Point2i) -> [Float; N] {
|
||||
self.get_channels_array_with_wrap(p, WrapMode::Clamp.into())
|
||||
}
|
||||
|
||||
pub fn resolution(&self) -> Point2i {
|
||||
self.base.resolution
|
||||
pub fn get_channels_array_with_wrap<const N: usize>(
|
||||
&self,
|
||||
mut p: Point2i,
|
||||
wrap_mode: WrapMode2D,
|
||||
) -> [Float; N] {
|
||||
debug_assert!(N <= self.n_channels as usize);
|
||||
let mut result = [0.0; N];
|
||||
if !self.remap_pixel_coords(&mut p, wrap_mode) {
|
||||
return result;
|
||||
}
|
||||
let offset = self.pixel_offset(p);
|
||||
for i in 0..N {
|
||||
result[i] = unsafe { self.pixels.read(offset + i, &self.encoding) };
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.resolution().x() > 0 && self.resolution().y() > 0
|
||||
pub fn get_channels_average(&self, p: Point2i) -> Float {
|
||||
let offset = self.pixel_offset(p);
|
||||
let nc = self.n_channels as usize;
|
||||
let mut sum = 0.0;
|
||||
for i in 0..nc {
|
||||
sum += unsafe { self.pixels.read(offset + i, &self.encoding) };
|
||||
}
|
||||
sum / nc as Float
|
||||
}
|
||||
|
||||
pub fn format(&self) -> PixelFormat {
|
||||
self.base().format
|
||||
pub fn set_channel(&mut self, p: Point2i, c: i32, mut value: Float) {
|
||||
if value.is_nan() {
|
||||
value = 0.0;
|
||||
}
|
||||
let res = self.resolution;
|
||||
if p.x() < 0 || p.x() >= res.x() || p.y() < 0 || p.y() >= res.y() {
|
||||
return;
|
||||
}
|
||||
let offset = self.pixel_offset(p) + c as usize;
|
||||
unsafe {
|
||||
match self.format {
|
||||
PixelFormat::U8 => {
|
||||
self.pixels
|
||||
.write_u8(offset, self.encoding.from_linear_scalar(value));
|
||||
}
|
||||
PixelFormat::F16 => {
|
||||
self.pixels
|
||||
.write_f16(offset, half::f16::from_f32(value).to_bits());
|
||||
}
|
||||
PixelFormat::F32 => {
|
||||
self.pixels.write_f32(offset, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn n_channels(&self) -> i32 {
|
||||
self.base().n_channels
|
||||
}
|
||||
|
||||
pub fn pixel_offset(&self, p: Point2i) -> u32 {
|
||||
let width = self.resolution().x() as u32;
|
||||
let idx = p.y() as u32 * width + p.x() as u32;
|
||||
idx * (self.n_channels() as u32)
|
||||
}
|
||||
|
||||
pub fn bilerp_channel(&self, p: Point2f, c: i32) -> Float {
|
||||
|
|
@ -188,8 +374,8 @@ impl DeviceImage {
|
|||
}
|
||||
|
||||
pub fn bilerp_channel_with_wrap(&self, p: Point2f, c: i32, wrap_mode: WrapMode2D) -> Float {
|
||||
let x = p.x() * self.resolution().x() as Float - 0.5;
|
||||
let y = p.y() * self.resolution().y() as Float - 0.5;
|
||||
let x = p.x() * self.resolution.x() as Float - 0.5;
|
||||
let y = p.y() * self.resolution.y() as Float - 0.5;
|
||||
let xi = x.floor() as i32;
|
||||
let yi = y.floor() as i32;
|
||||
let dx = x - xi as Float;
|
||||
|
|
@ -200,51 +386,30 @@ impl DeviceImage {
|
|||
let v11 = self.get_channel_with_wrap(Point2i::new(xi + 1, yi + 1), c, wrap_mode);
|
||||
lerp(dy, lerp(dx, v00, v10), lerp(dx, v01, v11))
|
||||
}
|
||||
|
||||
pub fn has_any_infinite_pixels(&self) -> bool {
|
||||
for y in 0..self.resolution.y() {
|
||||
for x in 0..self.resolution.x() {
|
||||
for c in 0..self.n_channels {
|
||||
if self.get_channel(Point2i::new(x, y), c).is_infinite() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub trait ImageAccess {
|
||||
fn get_channel_with_wrap(&self, p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float;
|
||||
fn get_channel(&self, p: Point2i, c: i32) -> Float;
|
||||
fn lookup_nearest_channel_with_wrap(&self, p: Point2f, c: i32, wrap_mode: WrapMode2D) -> Float;
|
||||
fn lookup_nearest_channel(&self, p: Point2f, c: i32) -> Float;
|
||||
}
|
||||
|
||||
impl ImageAccess for DeviceImage {
|
||||
fn get_channel_with_wrap(&self, mut p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float {
|
||||
if !self.base.remap_pixel_coords(&mut p, wrap_mode) {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
let offset = (self.pixel_offset(p) + c as u32) as usize;
|
||||
unsafe {
|
||||
match self.pixels.format {
|
||||
PixelFormat::U8 => {
|
||||
let raw_val = self.pixels.read_u8(offset);
|
||||
self.base.encoding.to_linear_scalar(raw_val)
|
||||
}
|
||||
PixelFormat::F16 => {
|
||||
let raw_val = self.pixels.read_f16(offset);
|
||||
f16_to_f32_software(raw_val)
|
||||
}
|
||||
PixelFormat::F32 => self.pixels.read_f32(offset),
|
||||
pub fn has_any_nan_pixels(&self) -> bool {
|
||||
for y in 0..self.resolution.y() {
|
||||
for x in 0..self.resolution.x() {
|
||||
for c in 0..self.n_channels {
|
||||
if self.get_channel(Point2i::new(x, y), c).is_nan() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_channel(&self, p: Point2i, c: i32) -> Float {
|
||||
self.get_channel_with_wrap(p, c, WrapMode::Clamp.into())
|
||||
}
|
||||
|
||||
fn lookup_nearest_channel_with_wrap(&self, p: Point2f, c: i32, wrap_mode: WrapMode2D) -> Float {
|
||||
let pi = Point2i::new(
|
||||
p.x() as i32 * self.resolution().x(),
|
||||
p.y() as i32 * self.resolution().y(),
|
||||
);
|
||||
|
||||
self.get_channel_with_wrap(pi, c, wrap_mode)
|
||||
}
|
||||
|
||||
fn lookup_nearest_channel(&self, p: Point2f, c: i32) -> Float {
|
||||
self.lookup_nearest_channel_with_wrap(p, c, WrapMode::Clamp.into())
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::core::camera::{Camera, CameraTrait};
|
|||
use crate::core::geometry::{
|
||||
Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike,
|
||||
};
|
||||
use crate::core::image::DeviceImage;
|
||||
use crate::core::image::Image;
|
||||
use crate::core::light::{Light, LightTrait};
|
||||
use crate::core::material::{
|
||||
Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map,
|
||||
|
|
@ -348,7 +348,7 @@ impl SurfaceInteraction {
|
|||
&mut self,
|
||||
tex_eval: &UniversalTextureEvaluator,
|
||||
displacement: Ptr<GPUFloatTexture>,
|
||||
normal_image: Ptr<DeviceImage>,
|
||||
normal_image: Ptr<Image>,
|
||||
) {
|
||||
let ctx = NormalBumpEvalContext::from(&*self);
|
||||
let (dpdu, dpdv) = if !displacement.is_null() {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use crate::core::geometry::{
|
|||
Bounds2f, Bounds3f, DirectionCone, Normal3f, Point2f, Point2i, Point3f, Point3fi, Ray,
|
||||
Vector3f, VectorLike, cos_theta,
|
||||
};
|
||||
use crate::core::image::DeviceImage;
|
||||
use crate::core::interaction::{
|
||||
Interaction, InteractionBase, InteractionTrait, MediumInteraction, SimpleInteraction,
|
||||
SurfaceInteraction,
|
||||
|
|
@ -17,7 +16,6 @@ use crate::spectra::{
|
|||
};
|
||||
use crate::utils::Transform;
|
||||
use crate::utils::math::{equal_area_sphere_to_square, radians, safe_sqrt, smooth_step, square};
|
||||
use crate::utils::sampling::DevicePiecewiseConstant2D;
|
||||
use crate::{Float, PI};
|
||||
use bitflags::bitflags;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use crate::core::bsdf::BSDF;
|
|||
use crate::core::bssrdf::BSSRDF;
|
||||
use crate::core::bxdf::BxDF;
|
||||
use crate::core::geometry::{Frame, Normal3f, Point2f, Point3f, Vector2f, Vector3f, VectorLike};
|
||||
use crate::core::image::{DeviceImage, WrapMode, WrapMode2D};
|
||||
use crate::core::image::{Image, WrapMode, WrapMode2D};
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, ShadingGeom, SurfaceInteraction};
|
||||
use crate::core::scattering::TrowbridgeReitzDistribution;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
|
|
@ -103,7 +103,7 @@ impl From<&NormalBumpEvalContext> for TextureEvalContext {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn normal_map(normal_map: &DeviceImage, ctx: &NormalBumpEvalContext) -> (Vector3f, Vector3f) {
|
||||
pub fn normal_map(normal_map: &Image, ctx: &NormalBumpEvalContext) -> (Vector3f, Vector3f) {
|
||||
let wrap = WrapMode2D::from(WrapMode::Repeat);
|
||||
let uv = Point2f::new(ctx.uv[0], 1. - ctx.uv[1]);
|
||||
let r = normal_map.bilerp_channel_with_wrap(uv, 0, wrap);
|
||||
|
|
@ -173,7 +173,7 @@ pub trait MaterialTrait {
|
|||
) -> Option<BSSRDF>;
|
||||
|
||||
fn can_evaluate_textures(&self, tex_eval: &dyn TextureEvaluator) -> bool;
|
||||
fn get_normal_map(&self) -> Option<&DeviceImage>;
|
||||
fn get_normal_map(&self) -> Option<&Image>;
|
||||
fn get_displacement(&self) -> Ptr<GPUFloatTexture>;
|
||||
fn has_subsurface_scattering(&self) -> bool;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use crate::core::geometry::Lerp;
|
|||
use core::ops::{Add, Mul};
|
||||
use num_traits::{Num, PrimInt};
|
||||
|
||||
use crate::core::image::DeviceImage;
|
||||
use crate::core::light::LightTrait;
|
||||
use crate::core::shape::Shape;
|
||||
use crate::core::texture::GPUFloatTexture;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::core::geometry::{Bounds3f, Ray};
|
||||
use crate::core::aggregates::DeviceBVHAggregate;
|
||||
use crate::core::aggregates::BVHAggregate;
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||
use crate::core::light::Light;
|
||||
use crate::core::material::Material;
|
||||
|
|
@ -213,6 +213,6 @@ pub enum Primitive {
|
|||
Geometric(GeometricPrimitive),
|
||||
Transformed(TransformedPrimitive),
|
||||
Animated(AnimatedPrimitive),
|
||||
BVH(DeviceBVHAggregate),
|
||||
BVH(BVHAggregate),
|
||||
KdTree(KdTreeAggregate),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
use crate::core::filter::FilterTrait;
|
||||
use crate::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
|
||||
use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4};
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::containers::DeviceArray2D;
|
||||
use crate::utils::math::{
|
||||
BinaryPermuteScrambler, DeviceDigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler,
|
||||
PRIME_TABLE_SIZE, Scrambler, clamp, encode_morton_2, inverse_radical_inverse, lerp, log2_int,
|
||||
clamp, encode_morton_2, inverse_radical_inverse, lerp, log2_int,
|
||||
owen_scrambled_radical_inverse, permutation_element, radical_inverse, round_up_pow2,
|
||||
scrambled_radical_inverse, sobol_interval_to_index, sobol_sample,
|
||||
scrambled_radical_inverse, sobol_interval_to_index, sobol_sample, BinaryPermuteScrambler,
|
||||
DeviceDigitPermutation, FastOwenScrambler, NoRandomizer, OwenScrambler, Scrambler,
|
||||
PRIME_TABLE_SIZE,
|
||||
};
|
||||
use crate::utils::rng::Rng;
|
||||
use crate::utils::sobol::N_SOBOL_DIMENSIONS;
|
||||
use crate::utils::{hash::*, sobol};
|
||||
use crate::{gvec, GVec, Ptr};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -100,7 +100,7 @@ pub struct HaltonSampler {
|
|||
pub mult_inverse: [u64; 2],
|
||||
pub halton_index: u64,
|
||||
pub dim: u32,
|
||||
pub digit_permutations: Ptr<DeviceDigitPermutation>,
|
||||
pub digit_permutations: GVec<DeviceDigitPermutation>,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
|
|
@ -114,7 +114,7 @@ impl Default for HaltonSampler {
|
|||
mult_inverse: [0; 2],
|
||||
halton_index: 0,
|
||||
dim: 0,
|
||||
digit_permutations: Ptr::default(),
|
||||
digit_permutations: gvec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Float;
|
||||
use crate::core::filter::{DeviceFilterSample, FilterTrait};
|
||||
use crate::core::filter::{FilterSample, 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) -> DeviceFilterSample {
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
let p = Point2f::new(
|
||||
lerp(u[0], -self.radius.x(), self.radius.x()),
|
||||
lerp(u[1], -self.radius.y(), self.radius.y()),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::Float;
|
||||
use crate::core::filter::{DeviceFilterSample, FilterSampler, FilterTrait};
|
||||
use crate::core::filter::{FilterSample, FilterSampler, FilterTrait};
|
||||
use crate::core::geometry::{Point2f, Vector2f};
|
||||
use crate::utils::math::{gaussian, gaussian_integral};
|
||||
use crate::Float;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
|
|
@ -13,6 +13,25 @@ pub struct GaussianFilter {
|
|||
pub sampler: FilterSampler,
|
||||
}
|
||||
|
||||
impl GaussianFilter {
|
||||
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.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,
|
||||
exp_y,
|
||||
sampler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterTrait for GaussianFilter {
|
||||
fn radius(&self) -> Vector2f {
|
||||
self.radius
|
||||
|
|
@ -30,7 +49,7 @@ impl FilterTrait for GaussianFilter {
|
|||
- 2.0 * self.radius.y() * self.exp_y)
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> DeviceFilterSample {
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
self.sampler.sample(u)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,30 @@
|
|||
use crate::Float;
|
||||
use crate::core::filter::{DeviceFilterSample, FilterSampler, FilterTrait};
|
||||
use crate::core::filter::{DeviceFilterSampler, FilterSample, FilterTrait};
|
||||
use crate::core::geometry::{Point2f, Vector2f};
|
||||
use crate::utils::math::{lerp, windowed_sinc};
|
||||
use crate::Float;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct LanczosSincFilter {
|
||||
pub radius: Vector2f,
|
||||
pub tau: Float,
|
||||
pub sampler: FilterSampler,
|
||||
pub sampler: DeviceFilterSampler,
|
||||
pub integral: Float,
|
||||
}
|
||||
|
||||
impl LanczosSincFilter {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterTrait for LanczosSincFilter {
|
||||
fn radius(&self) -> Vector2f {
|
||||
self.radius
|
||||
|
|
@ -26,7 +39,7 @@ impl FilterTrait for LanczosSincFilter {
|
|||
self.integral
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> DeviceFilterSample {
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
self.sampler.sample(u)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::Float;
|
||||
use crate::core::filter::{DeviceFilterSample, FilterSampler, FilterTrait};
|
||||
use crate::core::filter::{FilterSampler, FilterSample, FilterTrait};
|
||||
use crate::core::geometry::{Point2f, Vector2f};
|
||||
use crate::Float;
|
||||
use num_traits::Float as NumFloat;
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -9,7 +9,7 @@ pub struct MitchellFilter {
|
|||
pub radius: Vector2f,
|
||||
pub b: Float,
|
||||
pub c: Float,
|
||||
pub sampler: DeviceFilterSampler,
|
||||
pub sampler: FilterSampler,
|
||||
}
|
||||
|
||||
impl MitchellFilter {
|
||||
|
|
@ -34,6 +34,18 @@ impl MitchellFilter {
|
|||
fn mitchell_1d(&self, x: Float) -> Float {
|
||||
Self::mitchell_1d_eval(self.b, self.c, x)
|
||||
}
|
||||
|
||||
pub fn new(radius: Vector2f, b: Float, c: Float) -> Self {
|
||||
let sampler = FilterSampler::new(radius, move |p: Point2f| {
|
||||
mitchell_1d(p.x() / radius.x(), b, c) * mitchell_1d(p.y() / radius.y(), b, c)
|
||||
});
|
||||
Self {
|
||||
radius,
|
||||
b,
|
||||
c,
|
||||
sampler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterTrait for MitchellFilter {
|
||||
|
|
@ -50,7 +62,7 @@ impl FilterTrait for MitchellFilter {
|
|||
self.radius.x() * self.radius.y() / 4.0
|
||||
}
|
||||
|
||||
fn sample(&self, u: Point2f) -> DeviceFilterSample {
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
self.sampler.sample(u)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Float;
|
||||
use crate::core::filter::{DeviceFilterSample, FilterTrait};
|
||||
use crate::core::filter::{FilterSample, 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) -> DeviceFilterSample {
|
||||
fn sample(&self, u: Point2f) -> FilterSample {
|
||||
let p = Point2f::new(
|
||||
sample_tent(u[0], self.radius.x()),
|
||||
sample_tent(u[1], self.radius.y()),
|
||||
);
|
||||
DeviceFilterSample { p, weight: 1.0 }
|
||||
FilterSample { p, weight: 1.0 }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,5 +17,5 @@ pub mod textures;
|
|||
pub mod utils;
|
||||
|
||||
pub use core::pbrt::*;
|
||||
pub use utils::PBRTOptions;
|
||||
pub use utils::ptr::Ptr;
|
||||
pub use utils::alloc::{gbox, gvec, gvec_from_slice, gvec_with_capacity, GVec, Gbox};
|
||||
pub use utils::{Transform, Ptr, Array2D, PBRTOptions};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::core::color::{RGB, XYZ};
|
||||
use crate::core::geometry::*;
|
||||
use crate::core::image::{DeviceImage, ImageAccess};
|
||||
use crate::core::image::{Image, ImageAccess};
|
||||
use crate::core::interaction::{
|
||||
Interaction, InteractionTrait, MediumInteraction, SurfaceInteraction,
|
||||
};
|
||||
|
|
@ -27,7 +27,7 @@ pub struct DiffuseAreaLight {
|
|||
pub alpha: Ptr<GPUFloatTexture>,
|
||||
pub colorspace: Ptr<RGBColorSpace>,
|
||||
pub lemit: Ptr<DenselySampledSpectrum>,
|
||||
pub image: Ptr<DeviceImage>,
|
||||
pub image: Ptr<Image>,
|
||||
pub area: Float,
|
||||
pub two_sided: bool,
|
||||
pub scale: Float,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f};
|
||||
use crate::core::image::{DeviceImage, ImageAccess};
|
||||
use crate::core::image::{Image, ImageAccess};
|
||||
use crate::core::light::{
|
||||
LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType,
|
||||
};
|
||||
|
|
@ -7,7 +7,7 @@ use crate::core::medium::MediumInterface;
|
|||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths};
|
||||
use crate::utils::math::equal_area_sphere_to_square;
|
||||
use crate::utils::sampling::DevicePiecewiseConstant2D;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::utils::{Ptr, Transform};
|
||||
use crate::{Float, PI};
|
||||
|
||||
|
|
@ -16,8 +16,8 @@ pub struct GoniometricLight {
|
|||
pub base: LightBase,
|
||||
pub iemit: Ptr<DenselySampledSpectrum>,
|
||||
pub scale: Float,
|
||||
pub image: Ptr<DeviceImage>,
|
||||
pub distrib: Ptr<DevicePiecewiseConstant2D>,
|
||||
pub image: Ptr<Image>,
|
||||
pub distrib: Ptr<PiecewiseConstant2D>,
|
||||
}
|
||||
|
||||
impl GoniometricLight {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::core::geometry::{
|
|||
Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2f, Vector3f,
|
||||
};
|
||||
use crate::core::geometry::{Frame, VectorLike};
|
||||
use crate::core::image::{DeviceImage, ImageAccess, PixelFormat, WrapMode};
|
||||
use crate::core::image::{Image, ImageAccess, PixelFormat, WrapMode};
|
||||
use crate::core::interaction::InteractionBase;
|
||||
use crate::core::interaction::{Interaction, SimpleInteraction};
|
||||
use crate::core::light::{
|
||||
|
|
@ -15,7 +15,7 @@ use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths
|
|||
use crate::spectra::{RGBColorSpace, RGBIlluminantSpectrum};
|
||||
use crate::utils::math::{clamp, equal_area_sphere_to_square, equal_area_square_to_sphere, square};
|
||||
use crate::utils::sampling::{
|
||||
AliasTable, DevicePiecewiseConstant2D, DeviceWindowedPiecewiseConstant2D,
|
||||
AliasTable, DevicePiecewiseConstant2D, WindowedPiecewiseConstant2D,
|
||||
sample_uniform_sphere, uniform_sphere_pdf,
|
||||
};
|
||||
use crate::utils::{Ptr, Transform};
|
||||
|
|
@ -114,7 +114,7 @@ impl LightTrait for UniformInfiniteLight {
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ImageInfiniteLight {
|
||||
pub base: LightBase,
|
||||
pub image: Ptr<DeviceImage>,
|
||||
pub image: Ptr<Image>,
|
||||
pub image_color_space: Ptr<RGBColorSpace>,
|
||||
pub distrib: Ptr<DevicePiecewiseConstant2D>,
|
||||
pub compensated_distrib: Ptr<DevicePiecewiseConstant2D>,
|
||||
|
|
@ -250,12 +250,12 @@ impl LightTrait for ImageInfiniteLight {
|
|||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct PortalInfiniteLight {
|
||||
pub base: LightBase,
|
||||
pub image: Ptr<DeviceImage>,
|
||||
pub image: Ptr<Image>,
|
||||
pub image_color_space: Ptr<RGBColorSpace>,
|
||||
pub scale: Float,
|
||||
pub portal: [Point3f; 4],
|
||||
pub portal_frame: Frame,
|
||||
pub distribution: DeviceWindowedPiecewiseConstant2D,
|
||||
pub distribution: WindowedPiecewiseConstant2D,
|
||||
pub scene_center: Point3f,
|
||||
pub scene_radius: Float,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::bxdfs::{
|
|||
use crate::core::bsdf::BSDF;
|
||||
use crate::core::bssrdf::BSSRDF;
|
||||
use crate::core::bxdf::BxDF;
|
||||
use crate::core::image::DeviceImage;
|
||||
use crate::core::image::Image;
|
||||
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
|
||||
use crate::core::scattering::TrowbridgeReitzDistribution;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
|
|
@ -16,7 +16,7 @@ use crate::utils::math::clamp;
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CoatedDiffuseMaterial {
|
||||
pub normal_map: Ptr<DeviceImage>,
|
||||
pub normal_map: Ptr<Image>,
|
||||
pub displacement: Ptr<GPUFloatTexture>,
|
||||
pub reflectance: Ptr<GPUSpectrumTexture>,
|
||||
pub albedo: Ptr<GPUSpectrumTexture>,
|
||||
|
|
@ -42,7 +42,7 @@ impl CoatedDiffuseMaterial {
|
|||
g: Ptr<GPUFloatTexture>,
|
||||
eta: Ptr<Spectrum>,
|
||||
displacement: Ptr<GPUFloatTexture>,
|
||||
normal_map: Ptr<DeviceImage>,
|
||||
normal_map: Ptr<Image>,
|
||||
remap_roughness: bool,
|
||||
max_depth: u32,
|
||||
n_samples: u32,
|
||||
|
|
@ -137,7 +137,7 @@ impl MaterialTrait for CoatedDiffuseMaterial {
|
|||
)
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<&DeviceImage> {
|
||||
fn get_normal_map(&self) -> Option<&Image> {
|
||||
Some(&*self.normal_map)
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ impl MaterialTrait for CoatedDiffuseMaterial {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CoatedConductorMaterial {
|
||||
normal_map: Ptr<DeviceImage>,
|
||||
normal_map: Ptr<Image>,
|
||||
displacement: Ptr<GPUFloatTexture>,
|
||||
interface_uroughness: Ptr<GPUFloatTexture>,
|
||||
interface_vroughness: Ptr<GPUFloatTexture>,
|
||||
|
|
@ -175,7 +175,7 @@ pub struct CoatedConductorMaterial {
|
|||
impl CoatedConductorMaterial {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
normal_map: Ptr<DeviceImage>,
|
||||
normal_map: Ptr<Image>,
|
||||
displacement: Ptr<GPUFloatTexture>,
|
||||
interface_uroughness: Ptr<GPUFloatTexture>,
|
||||
interface_vroughness: Ptr<GPUFloatTexture>,
|
||||
|
|
@ -333,7 +333,7 @@ impl MaterialTrait for CoatedConductorMaterial {
|
|||
tex_eval.can_evaluate(&float_textures, &spectrum_textures)
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<&DeviceImage> {
|
||||
fn get_normal_map(&self) -> Option<&Image> {
|
||||
Some(&*self.normal_map)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::bxdfs::{
|
|||
use crate::core::bsdf::BSDF;
|
||||
use crate::core::bssrdf::{BSSRDF, BSSRDFTable};
|
||||
use crate::core::bxdf::BxDF;
|
||||
use crate::core::image::DeviceImage;
|
||||
use crate::core::image::Image;
|
||||
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
|
||||
use crate::core::scattering::TrowbridgeReitzDistribution;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
|
|
@ -77,7 +77,7 @@ impl MaterialTrait for HairMaterial {
|
|||
todo!()
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<&DeviceImage> {
|
||||
fn get_normal_map(&self) -> Option<&Image> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ impl MaterialTrait for HairMaterial {
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MeasuredMaterial {
|
||||
pub displacement: Ptr<GPUFloatTexture>,
|
||||
pub normal_map: Ptr<DeviceImage>,
|
||||
pub normal_map: Ptr<Image>,
|
||||
pub brdf: Ptr<MeasuredBxDFData>,
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ impl MaterialTrait for MeasuredMaterial {
|
|||
true
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<&DeviceImage> {
|
||||
fn get_normal_map(&self) -> Option<&Image> {
|
||||
Some(&*self.normal_map)
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ impl MaterialTrait for MeasuredMaterial {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SubsurfaceMaterial {
|
||||
pub normal_map: Ptr<DeviceImage>,
|
||||
pub normal_map: Ptr<Image>,
|
||||
pub displacement: Ptr<GPUFloatTexture>,
|
||||
pub sigma_a: Ptr<GPUSpectrumTexture>,
|
||||
pub sigma_s: Ptr<GPUSpectrumMixTexture>,
|
||||
|
|
@ -174,7 +174,7 @@ impl MaterialTrait for SubsurfaceMaterial {
|
|||
todo!()
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<&DeviceImage> {
|
||||
fn get_normal_map(&self) -> Option<&Image> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::bxdfs::{
|
|||
use crate::core::bsdf::BSDF;
|
||||
use crate::core::bssrdf::BSSRDF;
|
||||
use crate::core::bxdf::BxDF;
|
||||
use crate::core::image::DeviceImage;
|
||||
use crate::core::image::Image;
|
||||
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
|
||||
use crate::core::scattering::TrowbridgeReitzDistribution;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
|
|
@ -23,7 +23,7 @@ pub struct ConductorMaterial {
|
|||
pub u_roughness: Ptr<GPUFloatTexture>,
|
||||
pub v_roughness: Ptr<GPUFloatTexture>,
|
||||
pub remap_roughness: bool,
|
||||
pub normal_map: Ptr<DeviceImage>,
|
||||
pub normal_map: Ptr<Image>,
|
||||
}
|
||||
|
||||
impl MaterialTrait for ConductorMaterial {
|
||||
|
|
@ -50,7 +50,7 @@ impl MaterialTrait for ConductorMaterial {
|
|||
)
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<&DeviceImage> {
|
||||
fn get_normal_map(&self) -> Option<&Image> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::bxdfs::{
|
|||
use crate::core::bsdf::BSDF;
|
||||
use crate::core::bssrdf::BSSRDF;
|
||||
use crate::core::bxdf::BxDF;
|
||||
use crate::core::image::DeviceImage;
|
||||
use crate::core::image::Image;
|
||||
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
|
||||
use crate::core::scattering::TrowbridgeReitzDistribution;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
|
|
@ -16,7 +16,7 @@ use crate::utils::math::clamp;
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DielectricMaterial {
|
||||
pub normal_map: Ptr<DeviceImage>,
|
||||
pub normal_map: Ptr<Image>,
|
||||
pub displacement: Ptr<GPUFloatTexture>,
|
||||
pub u_roughness: Ptr<GPUFloatTexture>,
|
||||
pub v_roughness: Ptr<GPUFloatTexture>,
|
||||
|
|
@ -67,7 +67,7 @@ impl MaterialTrait for DielectricMaterial {
|
|||
tex_eval.can_evaluate(&[self.u_roughness, self.v_roughness], &[])
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<&DeviceImage> {
|
||||
fn get_normal_map(&self) -> Option<&Image> {
|
||||
Some(&*self.normal_map)
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ impl MaterialTrait for DielectricMaterial {
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ThinDielectricMaterial {
|
||||
pub displacement: Ptr<GPUFloatTexture>,
|
||||
pub normal_map: Ptr<DeviceImage>,
|
||||
pub normal_map: Ptr<Image>,
|
||||
pub eta: Ptr<Spectrum>,
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ impl MaterialTrait for ThinDielectricMaterial {
|
|||
true
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<&DeviceImage> {
|
||||
fn get_normal_map(&self) -> Option<&Image> {
|
||||
Some(&*self.normal_map)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::bxdfs::{
|
|||
use crate::core::bsdf::BSDF;
|
||||
use crate::core::bssrdf::BSSRDF;
|
||||
use crate::core::bxdf::BxDF;
|
||||
use crate::core::image::DeviceImage;
|
||||
use crate::core::image::Image;
|
||||
use crate::core::material::{Material, MaterialEvalContext, MaterialTrait};
|
||||
use crate::core::scattering::TrowbridgeReitzDistribution;
|
||||
use crate::core::spectrum::{Spectrum, SpectrumTrait};
|
||||
|
|
@ -69,7 +69,7 @@ impl MaterialTrait for MixMaterial {
|
|||
tex_eval.can_evaluate(&[self.amount], &[])
|
||||
}
|
||||
|
||||
fn get_normal_map(&self) -> Option<&DeviceImage> {
|
||||
fn get_normal_map(&self) -> Option<&Image> {
|
||||
None
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
use crate::core::geometry::{
|
||||
Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Tuple, Vector3f,
|
||||
VectorLike, spherical_quad_area,
|
||||
spherical_quad_area, Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi,
|
||||
Ray, Tuple, Vector3f, VectorLike,
|
||||
};
|
||||
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
||||
use crate::core::pbrt::{Float, gamma};
|
||||
use crate::core::pbrt::{gamma, Float};
|
||||
use crate::core::shape::{Shape, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::Transform;
|
||||
use crate::utils::math::{SquareMatrix, clamp, difference_of_products, lerp, quadratic};
|
||||
use crate::utils::mesh::DeviceBilinearPatchMesh;
|
||||
use crate::shapes::mesh::BilinearPatchMesh;
|
||||
use crate::utils::math::{clamp, difference_of_products, lerp, quadratic, SquareMatrix};
|
||||
use crate::utils::sampling::{
|
||||
bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle,
|
||||
};
|
||||
use crate::{GVec, Ptr, Transform};
|
||||
use core::ops::Add;
|
||||
|
||||
#[repr(C)]
|
||||
|
|
@ -47,7 +46,7 @@ impl BilinearIntersection {
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BilinearPatchShape {
|
||||
pub mesh: Ptr<DeviceBilinearPatchMesh>,
|
||||
pub mesh: Ptr<BilinearPatchMesh>,
|
||||
pub blp_index: i32,
|
||||
pub area: Float,
|
||||
pub rectangle: bool,
|
||||
|
|
@ -55,7 +54,7 @@ pub struct BilinearPatchShape {
|
|||
|
||||
impl BilinearPatchShape {
|
||||
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 1e-4;
|
||||
fn mesh(&self) -> Ptr<DeviceBilinearPatchMesh> {
|
||||
fn mesh(&self) -> Ptr<BilinearPatchMesh> {
|
||||
self.mesh
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +117,7 @@ impl BilinearPatchShape {
|
|||
}
|
||||
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new(mesh: Ptr<DeviceBilinearPatchMesh>, blp_index: i32) -> Self {
|
||||
pub fn new(mesh: Ptr<BilinearPatchMesh>, blp_index: i32) -> Self {
|
||||
let mut bp = BilinearPatchShape {
|
||||
mesh,
|
||||
blp_index,
|
||||
|
|
@ -456,7 +455,11 @@ impl BilinearPatchShape {
|
|||
|
||||
ss.pdf *= dist_sq / abs_dot;
|
||||
|
||||
if ss.pdf.is_infinite() { None } else { Some(ss) }
|
||||
if ss.pdf.is_infinite() {
|
||||
None
|
||||
} else {
|
||||
Some(ss)
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_parametric_coords(&self, corners: &[Point3f; 4], u: Point2f) -> (Point2f, Float) {
|
||||
|
|
@ -723,7 +726,11 @@ impl ShapeTrait for BilinearPatchShape {
|
|||
|
||||
let (_, dpdu, dpdv) = self.calculate_base_derivatives(&corners, uv);
|
||||
let cross = dpdu.cross(dpdv).norm();
|
||||
if cross == 0. { 0. } else { param_pdf / cross }
|
||||
if cross == 0. {
|
||||
0.
|
||||
} else {
|
||||
param_pdf / cross
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -754,7 +761,11 @@ impl ShapeTrait for BilinearPatchShape {
|
|||
return 0.;
|
||||
}
|
||||
let pdf = isect_pdf * distsq / absdot;
|
||||
if pdf.is_infinite() { 0. } else { pdf }
|
||||
if pdf.is_infinite() {
|
||||
0.
|
||||
} else {
|
||||
pdf
|
||||
}
|
||||
} else {
|
||||
let mut pdf = 1. / spherical_quad_area(v00, v10, v01, v11);
|
||||
if ctx.ns != Normal3f::zero() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::core::geometry::{Normal3f, Point2f, Point3f, Vector3f};
|
||||
use crate::utils::sampling::DevicePiecewiseConstant2D;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::utils::Transform;
|
||||
use crate::{Float, Gvec};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ pub mod cylinder;
|
|||
pub mod disk;
|
||||
pub mod sphere;
|
||||
pub mod triangle;
|
||||
pub mod mesh;
|
||||
|
||||
pub use bilinear::*;
|
||||
pub use curves::*;
|
||||
|
|
@ -11,3 +12,4 @@ pub use cylinder::*;
|
|||
pub use disk::*;
|
||||
pub use sphere::*;
|
||||
pub use triangle::*;
|
||||
pub use mesh::*;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,19 @@
|
|||
use crate::Float;
|
||||
use crate::core::geometry::{spherical_triangle_area, SqrtExt, Tuple, VectorLike};
|
||||
use crate::core::geometry::{
|
||||
Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3,
|
||||
Vector3f,
|
||||
};
|
||||
use crate::core::geometry::{SqrtExt, Tuple, VectorLike, spherical_triangle_area};
|
||||
use crate::core::interaction::{
|
||||
Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction,
|
||||
};
|
||||
use crate::core::pbrt::gamma;
|
||||
use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait};
|
||||
use crate::utils::Ptr;
|
||||
use crate::shapes::mesh::TriangleMesh;
|
||||
use crate::utils::math::{difference_of_products, square};
|
||||
use crate::utils::mesh::DeviceTriangleMesh;
|
||||
use crate::utils::sampling::{
|
||||
bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle,
|
||||
sample_uniform_triangle,
|
||||
};
|
||||
use crate::{gamma, Float, GVec, Ptr};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -35,7 +33,7 @@ impl TriangleIntersection {
|
|||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TriangleShape {
|
||||
pub mesh: Ptr<DeviceTriangleMesh>,
|
||||
pub mesh: Ptr<TriangleMesh>,
|
||||
pub tri_index: i32,
|
||||
}
|
||||
|
||||
|
|
@ -43,80 +41,45 @@ impl TriangleShape {
|
|||
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4;
|
||||
pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22;
|
||||
|
||||
#[inline(always)]
|
||||
fn get_vertex_indices(&self) -> [usize; 3] {
|
||||
unsafe {
|
||||
let base_ptr = self.mesh.vertex_indices.add((self.tri_index as usize) * 3);
|
||||
let mesh = unsafe { &*self.mesh };
|
||||
let base = (self.tri_index as usize) * 3;
|
||||
[
|
||||
*base_ptr.add(0) as usize,
|
||||
*base_ptr.add(1) as usize,
|
||||
*base_ptr.add(2) as usize,
|
||||
mesh.vertex_indices[base] as usize,
|
||||
mesh.vertex_indices[base + 1] as usize,
|
||||
mesh.vertex_indices[base + 2] as usize,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_points(&self) -> [Point3f; 3] {
|
||||
let mesh = unsafe { &*self.mesh };
|
||||
let [v0, v1, v2] = self.get_vertex_indices();
|
||||
unsafe {
|
||||
[
|
||||
*self.mesh.p.add(v0),
|
||||
*self.mesh.p.add(v1),
|
||||
*self.mesh.p.add(v2),
|
||||
]
|
||||
}
|
||||
[mesh.p[v0], mesh.p[v1], mesh.p[v2]]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_uvs(&self) -> Option<[Point2f; 3]> {
|
||||
if self.mesh.uv.is_null() {
|
||||
return None;
|
||||
}
|
||||
let [v0, v1, v2] = self.get_vertex_indices();
|
||||
unsafe {
|
||||
Some([
|
||||
*self.mesh.uv.add(v0),
|
||||
*self.mesh.uv.add(v1),
|
||||
*self.mesh.uv.add(v2),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_tangents(&self) -> Option<[Vector3f; 3]> {
|
||||
if self.mesh.s.is_null() {
|
||||
return None;
|
||||
}
|
||||
let [v0, v1, v2] = self.get_vertex_indices();
|
||||
unsafe {
|
||||
Some([
|
||||
*self.mesh.s.add(v0),
|
||||
*self.mesh.s.add(v1),
|
||||
*self.mesh.s.add(v2),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_shading_normals(&self) -> Option<[Normal3f; 3]> {
|
||||
if self.mesh.n.is_null() {
|
||||
let mesh = unsafe { &*self.mesh };
|
||||
if mesh.n.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let [v0, v1, v2] = self.get_vertex_indices();
|
||||
unsafe {
|
||||
Some([
|
||||
*self.mesh.n.add(v0),
|
||||
*self.mesh.n.add(v1),
|
||||
*self.mesh.n.add(v2),
|
||||
])
|
||||
}
|
||||
Some([mesh.n[v0], mesh.n[v1], mesh.n[v2]])
|
||||
}
|
||||
|
||||
pub fn new(mesh: Ptr<DeviceTriangleMesh>, tri_index: i32) -> Self {
|
||||
fn get_tangents(&self) -> Option<[Vector3f; 3]> {
|
||||
let mesh = unsafe { &*self.mesh };
|
||||
if mesh.s.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let [v0, v1, v2] = self.get_vertex_indices();
|
||||
Some([mesh.s[v0], mesh.s[v1], mesh.s[v2]])
|
||||
}
|
||||
|
||||
pub fn new(mesh: Ptr<TriangleMesh>, tri_index: i32) -> Self {
|
||||
Self { mesh, tri_index }
|
||||
}
|
||||
|
||||
pub fn get_mesh(&self) -> Ptr<DeviceTriangleMesh> {
|
||||
pub fn get_mesh(&self) -> Ptr<TriangleMesh> {
|
||||
self.mesh
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use crate::core::geometry::{
|
||||
Bounds2i, Bounds3f, Bounds3i, Point2i, Point3f, Point3i, Vector2i, Vector3f, Vector3i,
|
||||
};
|
||||
use crate::core::pbrt::Float;
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::math::lerp;
|
||||
use crate::{gvec, gvec_from_slice, Float, GVec};
|
||||
use core::ops::{Add, Index, IndexMut, Mul, Sub};
|
||||
|
||||
pub trait Interpolatable:
|
||||
|
|
@ -16,107 +15,88 @@ impl<T> Interpolatable for T where
|
|||
{
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DeviceArray2D<T> {
|
||||
pub values: *mut T,
|
||||
pub extent: Bounds2i,
|
||||
pub stride: i32,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Array2D<T> {
|
||||
extent: Bounds2i,
|
||||
values: GVec<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send for DeviceArray2D<T> {}
|
||||
unsafe impl<T: Sync> Sync for DeviceArray2D<T> {}
|
||||
|
||||
impl<T> DeviceArray2D<T> {
|
||||
#[inline]
|
||||
pub fn x_size(&self) -> u32 {
|
||||
(self.extent.p_max.x() - self.extent.p_min.x()) as u32
|
||||
impl<T> Array2D<T> {
|
||||
pub fn extent(&self) -> Bounds2i {
|
||||
self.extent
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn y_size(&self) -> u32 {
|
||||
(self.extent.p_max.y() - self.extent.p_min.y()) as u32
|
||||
pub fn stride(&self) -> i32 {
|
||||
self.extent.p_max.x() - self.extent.p_min.x()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> u32 {
|
||||
self.extent.area() as u32
|
||||
pub fn x_size(&self) -> usize {
|
||||
self.stride() as usize
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn offset(&self, p: Point2i) -> isize {
|
||||
let ox = p.x() - self.extent.p_min.x();
|
||||
let oy = p.y() - self.extent.p_min.y();
|
||||
(ox + oy * self.stride) as isize
|
||||
pub fn y_size(&self) -> usize {
|
||||
(self.extent.p_max.y() - self.extent.p_min.y()) as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn index(&self, x: i32, y: i32) -> u32 {
|
||||
let nx = x - self.extent.p_min.x();
|
||||
let ny = y - self.extent.p_min.y();
|
||||
nx as u32 + self.x_size() * ny as u32
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get(&self, p: Point2i) -> &T {
|
||||
unsafe { &*self.values.offset(self.offset(p)) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_mut(&mut self, p: Point2i) -> &mut T {
|
||||
unsafe { &mut *self.values.offset(self.offset(p)) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_linear(&self, index: usize) -> &T {
|
||||
unsafe { &*self.values.add(index) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_linear_mut(&mut self, index: usize) -> &mut T {
|
||||
unsafe { &mut *self.values.add(index) }
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
unsafe { core::slice::from_raw_parts(self.values, self.size() as usize) }
|
||||
&self.values
|
||||
}
|
||||
|
||||
pub fn as_mut_slice(&mut self) -> &mut [T] {
|
||||
unsafe { core::slice::from_raw_parts_mut(self.values, self.size() as usize) }
|
||||
&mut self.values
|
||||
}
|
||||
pub fn as_ptr(&self) -> *const T {
|
||||
self.values.as_ptr()
|
||||
}
|
||||
pub fn as_mut_ptr(&mut self) -> *mut T {
|
||||
self.values.as_mut_ptr()
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.values.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<Point2i> for DeviceArray2D<T> {
|
||||
unsafe impl<T: Send> Send for Array2D<T> {}
|
||||
unsafe impl<T: Sync> Sync for Array2D<T> {}
|
||||
|
||||
impl<T: Default + Clone> Array2D<T> {
|
||||
pub fn new(extent: Bounds2i) -> Self {
|
||||
let n = extent.area() as usize;
|
||||
let mut values = gvec();
|
||||
values.resize(n, T::default());
|
||||
Self { extent, values }
|
||||
}
|
||||
|
||||
pub fn new_dims(nx: i32, ny: i32) -> Self {
|
||||
Self::new(Bounds2i::from_points(
|
||||
Point2i::new(0, 0),
|
||||
Point2i::new(nx, ny),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_filled(nx: i32, ny: i32, val: T) -> Self {
|
||||
let extent = Bounds2i::from_points(Point2i::new(0, 0), Point2i::new(nx, ny));
|
||||
let n = (nx * ny) as usize;
|
||||
let mut values = gvec();
|
||||
values.resize(n, val);
|
||||
Self { extent, values }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<(i32, i32)> for Array2D<T> {
|
||||
type Output = T;
|
||||
#[inline(always)]
|
||||
fn index(&self, p: Point2i) -> &Self::Output {
|
||||
self.get(p)
|
||||
fn index(&self, (x, y): (i32, i32)) -> &T {
|
||||
let offset = (y - self.extent.p_min.y()) * self.stride() + (x - self.extent.p_min.x());
|
||||
&self.values[offset as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<Point2i> for DeviceArray2D<T> {
|
||||
fn index_mut(&mut self, p: Point2i) -> &mut Self::Output {
|
||||
self.get_mut(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<(i32, i32)> for DeviceArray2D<T> {
|
||||
type Output = T;
|
||||
fn index(&self, (x, y): (i32, i32)) -> &Self::Output {
|
||||
&self[Point2i::new(x, y)]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<(i32, i32)> for DeviceArray2D<T> {
|
||||
fn index_mut(&mut self, (x, y): (i32, i32)) -> &mut Self::Output {
|
||||
&mut self[Point2i::new(x, y)]
|
||||
impl<T> IndexMut<(i32, i32)> for Array2D<T> {
|
||||
fn index_mut(&mut self, (x, y): (i32, i32)) -> &mut T {
|
||||
let offset = (y - self.extent.p_min.y()) * self.stride() + (x - self.extent.p_min.x());
|
||||
&mut self.values[offset as usize]
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SampledGrid<T> {
|
||||
pub values: Ptr<T>,
|
||||
pub values: GVec<T>,
|
||||
pub values_len: u32,
|
||||
pub nx: i32,
|
||||
pub ny: i32,
|
||||
|
|
@ -127,11 +107,10 @@ unsafe impl<T: Sync> Sync for SampledGrid<T> {}
|
|||
unsafe impl<T: Send> Send for SampledGrid<T> {}
|
||||
|
||||
impl<T> SampledGrid<T> {
|
||||
#[cfg(not(target_os = "cuda"))]
|
||||
pub fn new(slice: &[T], nx: i32, ny: i32, nz: i32) -> Self {
|
||||
assert_eq!(slice.len(), (nx * ny * nz) as usize);
|
||||
Self {
|
||||
values: Ptr::from(slice),
|
||||
values: gvec_from_slice(slice),
|
||||
values_len: (nx * ny * nz) as u32,
|
||||
nx,
|
||||
ny,
|
||||
|
|
@ -141,7 +120,7 @@ impl<T> SampledGrid<T> {
|
|||
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
values: Ptr::null(),
|
||||
values: gvec(),
|
||||
values_len: 0,
|
||||
nx: 0,
|
||||
ny: 0,
|
||||
|
|
|
|||
|
|
@ -137,8 +137,13 @@ pub fn windowed_sinc(x: Float, radius: Float, tau: Float) -> Float {
|
|||
if x.abs() > radius {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
if x < 1e-5 {
|
||||
1.0
|
||||
} else {
|
||||
sinc(x) * sinc(x / tau)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fast_exp(x: Float) -> Float {
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
use crate::Float;
|
||||
use crate::core::geometry::{Normal3f, Point2f, Point3f, Vector3f};
|
||||
use crate::utils::Ptr;
|
||||
use crate::utils::Transform;
|
||||
use crate::utils::sampling::DevicePiecewiseConstant2D;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct DeviceTriangleMesh {
|
||||
pub p: Ptr<Point3f>,
|
||||
pub n: Ptr<Normal3f>,
|
||||
pub s: Ptr<Vector3f>,
|
||||
pub uv: Ptr<Point2f>,
|
||||
pub vertex_indices: Ptr<i32>,
|
||||
pub face_indices: Ptr<i32>,
|
||||
pub n_triangles: u32,
|
||||
pub n_vertices: u32,
|
||||
pub reverse_orientation: bool,
|
||||
pub transform_swaps_handedness: bool,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DeviceBilinearPatchMesh {
|
||||
pub image_distribution: Ptr<DevicePiecewiseConstant2D>,
|
||||
pub p: Ptr<Point3f>,
|
||||
pub n: Ptr<Normal3f>,
|
||||
pub uv: Ptr<Point2f>,
|
||||
pub vertex_indices: Ptr<i32>,
|
||||
pub n_patches: u32,
|
||||
pub n_vertices: u32,
|
||||
pub reverse_orientation: bool,
|
||||
pub transform_swaps_handedness: bool,
|
||||
}
|
||||
|
||||
unsafe impl Send for DeviceTriangleMesh {}
|
||||
unsafe impl Sync for DeviceTriangleMesh {}
|
||||
unsafe impl Send for DeviceBilinearPatchMesh {}
|
||||
unsafe impl Sync for DeviceBilinearPatchMesh {}
|
||||
|
|
@ -3,7 +3,6 @@ pub mod containers;
|
|||
pub mod hash;
|
||||
pub mod interval;
|
||||
pub mod math;
|
||||
pub mod mesh;
|
||||
pub mod noise;
|
||||
pub mod options;
|
||||
pub mod ptr;
|
||||
|
|
@ -18,15 +17,6 @@ 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};
|
||||
|
||||
|
|
@ -138,514 +128,3 @@ pub fn gpu_array_from_fn<T, const N: usize>(mut f: impl FnMut(usize) -> T) -> [T
|
|||
}
|
||||
}
|
||||
|
||||
/// # 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::core::geometry::{
|
||||
Bounds2f, Frame, Point2f, Point2i, Point3f, Vector2f, Vector2i, Vector3f, VectorLike,
|
||||
};
|
||||
use crate::utils::containers::DeviceArray2D;
|
||||
use crate::{GVec, gvec_with_capacity, gvec_from_slice, Array2D};
|
||||
use crate::utils::find_interval;
|
||||
use crate::utils::math::{
|
||||
catmull_rom_weights, clamp, difference_of_products, evaluate_polynomial, lerp, logistic,
|
||||
|
|
@ -14,7 +14,7 @@ use num_traits::Float as NumFloat;
|
|||
use num_traits::Num;
|
||||
|
||||
#[cfg(feature = "cpu_debug")]
|
||||
use crate::{RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS, check_rare};
|
||||
use crate::{check_rare, RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS};
|
||||
|
||||
pub fn linear_pdf<T>(x: T, a: T, b: T) -> T
|
||||
where
|
||||
|
|
@ -703,128 +703,177 @@ pub struct PLSample {
|
|||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct DevicePiecewiseConstant1D {
|
||||
pub func: Ptr<Float>,
|
||||
pub cdf: Ptr<Float>,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PiecewiseConstant1D {
|
||||
pub func: GVec<Float>,
|
||||
pub cdf: GVec<Float>,
|
||||
pub min: Float,
|
||||
pub max: Float,
|
||||
pub n: u32,
|
||||
pub func_integral: Float,
|
||||
}
|
||||
|
||||
unsafe impl Send for DevicePiecewiseConstant1D {}
|
||||
unsafe impl Sync for DevicePiecewiseConstant1D {}
|
||||
unsafe impl Send for PiecewiseConstant1D {}
|
||||
unsafe impl Sync for PiecewiseConstant1D {}
|
||||
|
||||
impl DevicePiecewiseConstant1D {
|
||||
impl PiecewiseConstant1D {
|
||||
pub fn new(f: &[Float]) -> Self {
|
||||
Self::new_with_bounds(f, 0.0, 1.0)
|
||||
}
|
||||
|
||||
pub fn new_with_bounds(f: &[Float], min: Float, max: Float) -> Self {
|
||||
let n = f.len();
|
||||
let mut cdf = gvec_with_capacity(n + 1);
|
||||
cdf.push(0.0);
|
||||
|
||||
let delta = (max - min) / n as Float;
|
||||
for i in 0..n {
|
||||
cdf.push(cdf[i] + f[i] * delta);
|
||||
}
|
||||
|
||||
let func_integral = cdf[n];
|
||||
if func_integral > 0.0 {
|
||||
for c in &mut cdf {
|
||||
*c /= func_integral;
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
func: gvec_from_slice(f),
|
||||
cdf,
|
||||
min,
|
||||
max,
|
||||
func_integral,
|
||||
}
|
||||
}
|
||||
|
||||
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| f(min + (i as Float + 0.5) * delta))
|
||||
.collect();
|
||||
Self::new_with_bounds(&values, min, max)
|
||||
}
|
||||
|
||||
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.func_integral
|
||||
}
|
||||
|
||||
pub fn size(&self) -> u32 {
|
||||
self.n
|
||||
}
|
||||
|
||||
fn find_interval(&self, u: Float) -> usize {
|
||||
let mut size = self.n as usize;
|
||||
let mut first = 0;
|
||||
|
||||
pub fn find_interval(&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;
|
||||
|
||||
let cdf_val = unsafe { *self.cdf.add(middle) };
|
||||
|
||||
if cdf_val <= u {
|
||||
if self.cdf[middle] <= u {
|
||||
first = middle + 1;
|
||||
size -= half + 1;
|
||||
} else {
|
||||
size = half;
|
||||
}
|
||||
}
|
||||
|
||||
(first - 1).clamp(0, self.n as usize - 1)
|
||||
first.saturating_sub(1).min(n - 1)
|
||||
}
|
||||
|
||||
pub fn sample(&self, u: Float) -> (Float, Float, usize) {
|
||||
// Find offset via binary search on CDF
|
||||
let offset = self.find_interval(u);
|
||||
|
||||
let cdf_offset = unsafe { *self.cdf.add(offset) };
|
||||
let cdf_next = unsafe { *self.cdf.add(offset + 1) };
|
||||
|
||||
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) / self.n as Float;
|
||||
let n = self.func.len();
|
||||
let delta = (self.max - self.min) / n as Float;
|
||||
let x = self.min + (offset as Float + du) * delta;
|
||||
|
||||
let pdf = if self.func_integral > 0.0 {
|
||||
(unsafe { *self.func.add(offset) }) / self.func_integral
|
||||
self.func[offset] / self.func_integral
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
(x, pdf, offset)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct DevicePiecewiseConstant2D {
|
||||
pub conditionals: Ptr<DevicePiecewiseConstant1D>, // Array of n_v conditionals
|
||||
pub marginal: DevicePiecewiseConstant1D,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PiecewiseConstant2D {
|
||||
pub conditionals: GVec<PiecewiseConstant1D>,
|
||||
pub marginal: PiecewiseConstant1D,
|
||||
pub n_u: u32,
|
||||
pub n_v: u32,
|
||||
}
|
||||
|
||||
unsafe impl Send for DevicePiecewiseConstant2D {}
|
||||
unsafe impl Sync for DevicePiecewiseConstant2D {}
|
||||
impl PiecewiseConstant2D {
|
||||
pub fn new(data: &Array2D<Float>) -> Self {
|
||||
Self::new_with_bounds(data, Bounds2f::unit())
|
||||
}
|
||||
|
||||
impl DevicePiecewiseConstant2D {
|
||||
// pub fn resolution(&self) -> Point2i {
|
||||
// Point2i::new(
|
||||
// self.p_conditional_v[0u32].size() as i32,
|
||||
// self.p_conditional_v[1u32].size() as i32,
|
||||
// )
|
||||
// }
|
||||
pub fn new_with_bounds(data: &Array2D<Float>, domain: Bounds2f) -> Self {
|
||||
Self::from_slice(data.as_slice(), data.x_size(), data.y_size(), domain)
|
||||
}
|
||||
|
||||
pub fn integral(&self) -> f32 {
|
||||
pub fn from_slice(data: &[Float], n_u: usize, n_v: usize, domain: Bounds2f) -> Self {
|
||||
assert_eq!(data.len(), n_u * n_v);
|
||||
|
||||
let mut conditionals = gvec_with_capacity(n_v);
|
||||
let mut marginal_func = GVec::with_capacity(n_v);
|
||||
|
||||
for v in 0..n_v {
|
||||
let row = &data[v * n_u..(v + 1) * n_u];
|
||||
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(),
|
||||
);
|
||||
|
||||
Self {
|
||||
conditionals,
|
||||
marginal,
|
||||
n_u: n_u as u32,
|
||||
n_v: n_v as u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn integral(&self) -> Float {
|
||||
self.marginal.integral()
|
||||
}
|
||||
|
||||
pub fn sample(&self, u: Point2f) -> (Point2f, f32, Point2i) {
|
||||
pub fn sample(&self, u: Point2f) -> (Point2f, Float, Point2i) {
|
||||
let (d1, pdf1, off_y) = self.marginal.sample(u.y());
|
||||
let (d0, pdf0, off_x) = (unsafe { self.conditionals.add(off_y) }).sample(u.x());
|
||||
let (d0, pdf0, off_x) = self.conditionals[off_y].sample(u.x());
|
||||
let pdf = pdf0 * pdf1;
|
||||
let offset = Point2i::new(off_x as i32, off_y as i32);
|
||||
(Point2f::new(d0, d1), pdf, offset)
|
||||
}
|
||||
|
||||
pub fn pdf(&self, p: Point2f) -> Float {
|
||||
// Find which row
|
||||
// let delta_v = 1.0 / self.n_v as Float;
|
||||
let v_offset = ((p.y() * self.n_v as Float) as usize).min(self.n_v as usize - 1);
|
||||
|
||||
let conditional = unsafe { &*self.conditionals.add(v_offset) };
|
||||
|
||||
// Find which column
|
||||
// let delta_u = 1.0 / self.n_u as Float;
|
||||
let u_offset = ((p.x() * self.n_u as Float) as usize).min(self.n_u as usize - 1);
|
||||
|
||||
let func_val = unsafe { *conditional.func.add(u_offset) };
|
||||
|
||||
func_val / self.marginal.func_integral
|
||||
(
|
||||
Point2f::new(d0, d1),
|
||||
pdf,
|
||||
Point2i::new(off_x as i32, off_y as i32),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct DeviceSummedAreaTable {
|
||||
pub sum: DeviceArray2D<f64>,
|
||||
pub sum: Array2D<f64>,
|
||||
}
|
||||
|
||||
impl DeviceSummedAreaTable {
|
||||
|
|
@ -877,7 +926,7 @@ impl DeviceSummedAreaTable {
|
|||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct DeviceWindowedPiecewiseConstant2D {
|
||||
pub sat: DeviceSummedAreaTable,
|
||||
pub func: DeviceArray2D<Float>,
|
||||
pub func: Array2D<Float>,
|
||||
}
|
||||
|
||||
impl DeviceWindowedPiecewiseConstant2D {
|
||||
|
|
@ -1058,10 +1107,10 @@ pub struct PiecewiseLinear2D<const N: usize> {
|
|||
pub inv_patch_size: Vector2f,
|
||||
pub param_size: [u32; N],
|
||||
pub param_strides: [u32; N],
|
||||
pub param_values: [Ptr<Float>; N],
|
||||
pub data: Ptr<Float>,
|
||||
pub marginal_cdf: Ptr<Float>,
|
||||
pub conditional_cdf: Ptr<Float>,
|
||||
pub param_values: [GVec<Float>; N],
|
||||
pub data: GVec<Float>,
|
||||
pub marginal_cdf: GVec<Float>,
|
||||
pub conditional_cdf: GVec<Float>,
|
||||
}
|
||||
|
||||
impl<const N: usize> PiecewiseLinear2D<N> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use rayon::prelude::*;
|
||||
use shared::core::aggregates::{{DeviceBVHAggregate, LinearBVHNode};
|
||||
use shared::core::aggregates::{DeviceBVHAggregate, LinearBVHNode};
|
||||
use shared::core::geometry::{Bounds3f, Point3f, Ray, Vector3f};
|
||||
use shared::core::primitive::{Primitive, PrimitiveTrait};
|
||||
use shared::core::shape::ShapeIntersection;
|
||||
|
|
@ -307,7 +307,7 @@ impl<P: PrimitiveTrait + Clone + Send + Sync> BVHAggregate<P> {
|
|||
pub fn build_hlbvh(
|
||||
bvh_primitives: &[BVHPrimitiveInfo],
|
||||
total_nodes: &AtomicUsize,
|
||||
original_primitives: &[P],
|
||||
_original_primitives: &[P],
|
||||
max_prims_in_node: usize,
|
||||
) -> Box<BVHBuildNode> {
|
||||
let bounds = bvh_primitives
|
||||
|
|
|
|||
|
|
@ -388,11 +388,11 @@ impl CameraFactory for Camera {
|
|||
Arc::from(aperture_image.unwrap()),
|
||||
);
|
||||
|
||||
// arena.alloc(camera);
|
||||
arena.alloc(camera);
|
||||
Ok(Camera::Realistic(camera.device()))
|
||||
}
|
||||
"spherical" => {
|
||||
let full_res = film.full_resolution();
|
||||
let _full_res = film.full_resolution();
|
||||
let camera_params =
|
||||
CameraBaseParameters::new(camera_transform, film, medium, params, loc)?;
|
||||
let base = CameraBase::create(camera_params);
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
use crate::Arena;
|
||||
use crate::core::image::{Image, ImageChannelDesc, ImageChannelValues, ImageIO, ImageMetadata};
|
||||
use crate::films::*;
|
||||
use crate::spectra::data::get_named_spectrum;
|
||||
use crate::spectra::piecewise::PiecewiseLinearSpectrumBuffer;
|
||||
use anyhow::{Result, anyhow};
|
||||
use crate::Arena;
|
||||
use anyhow::{anyhow, Result};
|
||||
use rayon::iter::ParallelIterator;
|
||||
use rayon::prelude::IntoParallelIterator;
|
||||
use shared::core::camera::CameraTransform;
|
||||
use shared::core::color::{RGB, SRGB, XYZ, white_balance};
|
||||
use shared::core::color::{white_balance, RGB, SRGB, XYZ};
|
||||
use shared::core::film::{DevicePixelSensor, Film, FilmBase, GBufferFilm, RGBFilm, SpectralFilm};
|
||||
use shared::core::filter::{Filter, FilterTrait};
|
||||
use shared::core::geometry::{Bounds2f, Bounds2i, Point2f, Point2i};
|
||||
use shared::core::image::PixelFormat;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::spectra::{RGBColorSpace, cie::SWATCHES_RAW};
|
||||
use shared::utils::math::{SquareMatrix, linear_least_squares};
|
||||
use shared::spectra::{cie::SWATCHES_RAW, RGBColorSpace};
|
||||
use shared::utils::math::{linear_least_squares, SquareMatrix};
|
||||
use shared::{Float, Ptr};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use crate::spectra::{
|
||||
CIE_X_DATA, CIE_Y_DATA, CIE_Z_DATA, DenselySampledSpectrumBuffer, get_spectra_context,
|
||||
get_spectra_context, DenselySampledSpectrumBuffer, CIE_X_DATA, CIE_Y_DATA, CIE_Z_DATA,
|
||||
};
|
||||
use crate::utils::{FileLoc, ParameterDictionary};
|
||||
|
||||
|
|
@ -363,7 +363,7 @@ pub trait FilmTrait: Sync {
|
|||
.collect();
|
||||
|
||||
let mut image = Image::new(format, resolution, channel_names, SRGB.into());
|
||||
let rgb_desc = ImageChannelDesc::new(&[0, 1, 2]);
|
||||
let _rgb_desc = ImageChannelDesc::new(&[0, 1, 2]);
|
||||
|
||||
for (iy, row_data) in processed_rows.into_iter().enumerate() {
|
||||
for (ix, rgb_chunk) in row_data.chunks_exact(3).enumerate() {
|
||||
|
|
@ -381,10 +381,6 @@ pub trait FilmTrait: Sync {
|
|||
);
|
||||
}
|
||||
|
||||
// self.base().pixel_bounds = pixel_bounds;
|
||||
// self.base().full_resolution = resolution;
|
||||
// self.colorspace = colorspace;
|
||||
|
||||
image
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,28 @@
|
|||
use crate::filters::*;
|
||||
use crate::utils::containers::Array2D;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::utils::DeviceRepr;
|
||||
use crate::utils::{FileLoc, ParameterDictionary};
|
||||
use crate::Arena;
|
||||
use anyhow::{anyhow, Result};
|
||||
use shared::core::filter::{DeviceFilterSampler, Filter};
|
||||
use shared::core::filter::Filter;
|
||||
use shared::core::geometry::{Bounds2f, Point2f, Vector2f};
|
||||
use shared::filters::*;
|
||||
use shared::Float;
|
||||
use shared::{Array2D, Float};
|
||||
|
||||
pub trait FilterFactory {
|
||||
fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result<Filter>;
|
||||
fn create(
|
||||
name: &str,
|
||||
params: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Filter>;
|
||||
}
|
||||
|
||||
impl FilterFactory for Filter {
|
||||
fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result<Self> {
|
||||
fn create(
|
||||
name: &str,
|
||||
params: &ParameterDictionary,
|
||||
loc: &FileLoc,
|
||||
arena: &Arena,
|
||||
) -> Result<Self> {
|
||||
match name {
|
||||
"box" => {
|
||||
let xw = params.get_one_float("xradius", 0.5)?;
|
||||
|
|
@ -27,7 +35,6 @@ impl FilterFactory for Filter {
|
|||
let yw = params.get_one_float("yradius", 1.5)?;
|
||||
let sigma = params.get_one_float("sigma", 0.5)?;
|
||||
let filter = GaussianFilter::new(Vector2f::new(xw, yw), sigma);
|
||||
Ok(Filter::Gaussian(filter))
|
||||
}
|
||||
"mitchell" => {
|
||||
let xw = params.get_one_float("xradius", 2.)?;
|
||||
|
|
@ -54,52 +61,3 @@ impl FilterFactory for Filter {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct FilterSampler {
|
||||
pub domain: Bounds2f,
|
||||
pub distrib: PiecewiseConstant2D,
|
||||
pub f: Array2D<Float>,
|
||||
}
|
||||
|
||||
impl FilterSampler {
|
||||
pub fn new<F>(radius: Vector2f, func: F) -> Self
|
||||
where
|
||||
F: Fn(Point2f) -> Float,
|
||||
{
|
||||
let domain = Bounds2f::from_points(
|
||||
Point2f::new(-radius.x(), -radius.y()),
|
||||
Point2f::new(radius.x(), radius.y()),
|
||||
);
|
||||
|
||||
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() {
|
||||
let p = domain.lerp(Point2f::new(
|
||||
(x as Float + 0.5) / f.x_size() as Float,
|
||||
(y as Float + 0.5) / f.y_size() as Float,
|
||||
));
|
||||
f[(x as i32, y as i32)] = func(p);
|
||||
}
|
||||
}
|
||||
|
||||
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ use shared::spectra::RGBColorSpace;
|
|||
use shared::utils::math::SquareMatrix;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ImageChannelDesc {
|
||||
pub offset: Vec<usize>,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ 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};
|
||||
use shared::core::image::{Image, ImageBase, PixelFormat, Pixels, WrapMode, WrapMode2D};
|
||||
use shared::utils::math::square;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
|
@ -69,133 +69,66 @@ impl DerefMut for ImageChannelValues {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PixelStorage {
|
||||
U8(Vec<u8>),
|
||||
F16(Vec<f16>),
|
||||
F32(Vec<f32>),
|
||||
}
|
||||
|
||||
impl PixelStorage {
|
||||
pub fn as_pixels(&self) -> Pixels {
|
||||
match self {
|
||||
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())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format(&self) -> PixelFormat {
|
||||
match self {
|
||||
PixelStorage::U8(_) => PixelFormat::U8,
|
||||
PixelStorage::F16(_) => PixelFormat::F16,
|
||||
PixelStorage::F32(_) => PixelFormat::F32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
PixelStorage::U8(d) => d.len(),
|
||||
PixelStorage::F16(d) => d.len(),
|
||||
PixelStorage::F32(d) => d.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Image {
|
||||
pub pixels: PixelStorage,
|
||||
pub struct HostImage {
|
||||
pub inner: Image,
|
||||
pub channel_names: Vec<String>,
|
||||
pub device: DeviceImage,
|
||||
}
|
||||
|
||||
// impl Deref for Image {
|
||||
// type Target = DeviceImage;
|
||||
// #[inline]
|
||||
// fn deref(&self) -> &Self::Target {
|
||||
// &self.device
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImageAndMetadata {
|
||||
pub image: Image,
|
||||
pub image: HostImage,
|
||||
pub metadata: ImageMetadata,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
// Constructors
|
||||
fn from_storage(
|
||||
storage: PixelStorage,
|
||||
impl HostImage {
|
||||
pub fn from_u8(
|
||||
data: &[u8],
|
||||
resolution: Point2i,
|
||||
channel_names: &[impl AsRef<str>],
|
||||
encoding: ColorEncoding,
|
||||
) -> Self {
|
||||
let n_channels = channel_names.len() as i32;
|
||||
let expected = (resolution.x() * resolution.y()) as usize * n_channels as usize;
|
||||
let channel_names = channel_names
|
||||
.iter()
|
||||
.map(|s| s.as_ref().to_string())
|
||||
.collect();
|
||||
assert_eq!(storage.len(), expected, "Pixel data size mismatch");
|
||||
|
||||
let device = DeviceImage {
|
||||
base: ImageBase {
|
||||
format: storage.format(),
|
||||
encoding,
|
||||
resolution,
|
||||
n_channels,
|
||||
},
|
||||
pixels: storage.as_pixels(),
|
||||
};
|
||||
|
||||
Self {
|
||||
pixels: storage,
|
||||
channel_names,
|
||||
device,
|
||||
inner: Image::from_u8(data, resolution, n_channels, encoding),
|
||||
channel_names: channel_names.iter().map(|s| s.as_ref().to_string()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_u8(
|
||||
data: Vec<u8>,
|
||||
resolution: Point2i,
|
||||
channel_names: &[impl AsRef<str>],
|
||||
encoding: ColorEncoding,
|
||||
) -> Self {
|
||||
Self::from_storage(PixelStorage::U8(data), resolution, channel_names, encoding)
|
||||
}
|
||||
|
||||
pub fn from_f16(
|
||||
data: Vec<half::f16>,
|
||||
resolution: Point2i,
|
||||
channel_names: &[impl AsRef<str>],
|
||||
) -> Self {
|
||||
Self::from_storage(PixelStorage::F16(data), resolution, channel_names, LINEAR)
|
||||
}
|
||||
|
||||
pub fn from_f32(
|
||||
data: Vec<f32>,
|
||||
data: &[f32],
|
||||
resolution: Point2i,
|
||||
channel_names: &[impl AsRef<str>],
|
||||
) -> Self {
|
||||
Self::from_storage(PixelStorage::F32(data), resolution, channel_names, LINEAR)
|
||||
let n_channels = channel_names.len() as i32;
|
||||
Self {
|
||||
inner: Image::from_f32(data, resolution, n_channels),
|
||||
channel_names: channel_names.iter().map(|s| s.as_ref().to_string()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_f16(
|
||||
data: &[u16],
|
||||
resolution: Point2i,
|
||||
channel_names: &[impl AsRef<str>],
|
||||
) -> Self {
|
||||
let n_channels = channel_names.len() as i32;
|
||||
Self {
|
||||
inner: Image::from_f16(data, resolution, n_channels),
|
||||
channel_names: channel_names.iter().map(|s| s.as_ref().to_string()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
format: PixelFormat,
|
||||
resolution: Point2i,
|
||||
channel_names: &[impl AsRef<str>],
|
||||
encoding: Arc<ColorEncoding>,
|
||||
encoding: ColorEncoding,
|
||||
) -> Self {
|
||||
let n_channels = channel_names.len();
|
||||
let pixel_count = (resolution.x() * resolution.y()) as usize * n_channels;
|
||||
|
||||
let storage = match format {
|
||||
PixelFormat::U8 => PixelStorage::U8(vec![0; pixel_count].into()),
|
||||
PixelFormat::F16 => PixelStorage::F16(vec![f16::ZERO; pixel_count].into()),
|
||||
PixelFormat::F32 => PixelStorage::F32(vec![0.0; pixel_count].into()),
|
||||
};
|
||||
|
||||
Self::from_storage(storage, resolution, channel_names, *encoding)
|
||||
let n_channels = channel_names.len() as i32;
|
||||
Self {
|
||||
inner: Image::new(format, resolution, n_channels, encoding),
|
||||
channel_names: channel_names.iter().map(|s| s.as_ref().to_string()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_constant(
|
||||
|
|
@ -204,206 +137,29 @@ impl Image {
|
|||
values: &[f32],
|
||||
) -> Self {
|
||||
let n_channels = channel_names.len();
|
||||
if values.len() != n_channels {
|
||||
panic!(
|
||||
"Image::new_constant: values length ({}) must match channel count ({})",
|
||||
values.len(),
|
||||
n_channels
|
||||
);
|
||||
}
|
||||
assert_eq!(values.len(), n_channels, "values length must match channel count");
|
||||
|
||||
let n_pixels = (resolution.x() * resolution.y()) as usize;
|
||||
|
||||
let mut data = Vec::with_capacity(n_pixels * n_channels);
|
||||
|
||||
for _ in 0..n_pixels {
|
||||
data.extend_from_slice(values);
|
||||
}
|
||||
|
||||
Self::from_f32(data, resolution, channel_names)
|
||||
}
|
||||
|
||||
// Access
|
||||
pub fn device(&self) -> &DeviceImage {
|
||||
&self.device
|
||||
}
|
||||
|
||||
pub fn resolution(&self) -> Point2i {
|
||||
self.base.resolution
|
||||
}
|
||||
|
||||
fn n_channels(&self) -> i32 {
|
||||
self.base().n_channels
|
||||
}
|
||||
|
||||
pub fn format(&self) -> PixelFormat {
|
||||
self.base().format
|
||||
Self::from_f32(&data, resolution, channel_names)
|
||||
}
|
||||
|
||||
pub fn channel_names(&self) -> Vec<&str> {
|
||||
self.channel_names.iter().map(|s| s.as_str()).collect()
|
||||
}
|
||||
|
||||
pub fn encoding(&self) -> ColorEncoding {
|
||||
self.base().encoding
|
||||
}
|
||||
|
||||
fn pixel_offset(&self, p: Point2i) -> usize {
|
||||
let width = self.resolution().x() as usize;
|
||||
let idx = p.y() as usize * width + p.x() as usize;
|
||||
idx * self.n_channels() as usize
|
||||
}
|
||||
|
||||
// Read
|
||||
pub fn as_f32_slice(&self) -> Option<&[f32]> {
|
||||
match &self.pixels {
|
||||
PixelStorage::F32(data) => Some(data.as_slice()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn get_channel(&self, p: Point2i, c: i32) -> Float {
|
||||
self.get_channel_with_wrap(p, c, WrapMode::Clamp.into())
|
||||
}
|
||||
|
||||
pub fn get_channel_with_wrap(&self, mut p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float {
|
||||
if !self.device.base.remap_pixel_coords(&mut p, wrap_mode) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let offset = self.pixel_offset(p) + c as usize;
|
||||
|
||||
match &self.pixels {
|
||||
PixelStorage::U8(data) => self.device.base.encoding.to_linear_scalar(data[offset]),
|
||||
PixelStorage::F16(data) => data[offset].to_f32(),
|
||||
PixelStorage::F32(data) => data[offset],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_channels(&self, p: Point2i) -> ImageChannelValues {
|
||||
self.get_channels_with_wrap(p, WrapMode::Clamp.into())
|
||||
}
|
||||
|
||||
pub fn get_channels_with_wrap(
|
||||
&self,
|
||||
mut p: Point2i,
|
||||
wrap_mode: WrapMode2D,
|
||||
) -> ImageChannelValues {
|
||||
if !self.device.base.remap_pixel_coords(&mut p, wrap_mode) {
|
||||
return ImageChannelValues(smallvec![0.0; self.n_channels() as usize]);
|
||||
}
|
||||
|
||||
let offset = self.pixel_offset(p);
|
||||
let nc = self.n_channels() as usize;
|
||||
let mut values = SmallVec::with_capacity(nc);
|
||||
|
||||
match &self.pixels {
|
||||
PixelStorage::U8(data) => {
|
||||
for i in 0..nc {
|
||||
values.push(self.device.base.encoding.to_linear_scalar(data[offset + i]));
|
||||
}
|
||||
}
|
||||
PixelStorage::F16(data) => {
|
||||
for i in 0..nc {
|
||||
values.push(data[offset + i].to_f32());
|
||||
}
|
||||
}
|
||||
PixelStorage::F32(data) => {
|
||||
for i in 0..nc {
|
||||
values.push(data[offset + i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageChannelValues(values)
|
||||
}
|
||||
|
||||
// Write
|
||||
pub fn set_channel(&mut self, p: Point2i, c: i32, mut value: Float) {
|
||||
if value.is_nan() {
|
||||
value = 0.0;
|
||||
}
|
||||
|
||||
let res = self.resolution();
|
||||
if p.x() < 0 || p.x() >= res.x() || p.y() < 0 || p.y() >= res.y() {
|
||||
return;
|
||||
}
|
||||
|
||||
let offset = self.pixel_offset(p) + c as usize;
|
||||
|
||||
match &mut self.pixels {
|
||||
PixelStorage::U8(data) => {
|
||||
data[offset] = self.device.base.encoding.from_linear_scalar(value);
|
||||
}
|
||||
PixelStorage::F16(data) => {
|
||||
data[offset] = f16::from_f32(value);
|
||||
}
|
||||
PixelStorage::F32(data) => {
|
||||
data[offset] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_channels(&mut self, p: Point2i, values: &ImageChannelValues) {
|
||||
for i in 0..values.len() {
|
||||
self.set_channel(p, i.try_into().unwrap(), values[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Descriptions
|
||||
pub fn get_channels_with_desc(
|
||||
&self,
|
||||
p: Point2i,
|
||||
desc: &ImageChannelDesc,
|
||||
wrap_mode: WrapMode2D,
|
||||
) -> ImageChannelValues {
|
||||
let mut pp = p;
|
||||
if !self.device.base.remap_pixel_coords(&mut pp, wrap_mode) {
|
||||
return ImageChannelValues(smallvec![0.0; desc.offset.len()]);
|
||||
}
|
||||
|
||||
let pixel_offset = self.pixel_offset(pp);
|
||||
let mut values = SmallVec::with_capacity(desc.offset.len());
|
||||
|
||||
match &self.pixels {
|
||||
PixelStorage::U8(data) => {
|
||||
for &c in &desc.offset {
|
||||
let raw = data[pixel_offset + c];
|
||||
values.push(self.device.base.encoding.to_linear_scalar(raw));
|
||||
}
|
||||
}
|
||||
PixelStorage::F16(data) => {
|
||||
for &c in &desc.offset {
|
||||
values.push(data[pixel_offset + c].to_f32());
|
||||
}
|
||||
}
|
||||
PixelStorage::F32(data) => {
|
||||
for &c in &desc.offset {
|
||||
values.push(data[pixel_offset + c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageChannelValues(values)
|
||||
}
|
||||
|
||||
pub fn channel_names_from_desc(&self, desc: &ImageChannelDesc) -> Vec<&str> {
|
||||
desc.offset
|
||||
.iter()
|
||||
.map(|&i| self.channel_names[i].as_str())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_channel_desc(
|
||||
&self,
|
||||
requested_channels: &[impl AsRef<str> + std::fmt::Display],
|
||||
requested: &[impl AsRef<str> + std::fmt::Display],
|
||||
) -> Result<ImageChannelDesc> {
|
||||
let mut offset = Vec::with_capacity(requested_channels.len());
|
||||
|
||||
for req in requested_channels.iter() {
|
||||
let mut offset = Vec::with_capacity(requested.len());
|
||||
for req in requested {
|
||||
match self.channel_names.iter().position(|n| n == req.as_ref()) {
|
||||
Some(idx) => {
|
||||
offset.push(idx);
|
||||
}
|
||||
Some(idx) => offset.push(idx),
|
||||
None => {
|
||||
return Err(anyhow!(
|
||||
"Missing channel '{}'. Available: {:?}",
|
||||
|
|
@ -413,81 +169,98 @@ impl Image {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ImageChannelDesc { offset })
|
||||
}
|
||||
|
||||
pub fn all_channels_desc(&self) -> ImageChannelDesc {
|
||||
ImageChannelDesc {
|
||||
offset: (0..self.n_channels() as usize).collect(),
|
||||
offset: (0..self.inner.n_channels() as usize).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn channel_names_from_desc(&self, desc: &ImageChannelDesc) -> Vec<&str> {
|
||||
desc.offset.iter().map(|&i| self.channel_names[i].as_str()).collect()
|
||||
}
|
||||
|
||||
pub fn get_channels(&self, p: Point2i) -> ImageChannelValues {
|
||||
self.get_channels_with_wrap(p, WrapMode::Clamp.into())
|
||||
}
|
||||
|
||||
pub fn get_channels_with_wrap(&self, mut p: Point2i, wrap_mode: WrapMode2D) -> ImageChannelValues {
|
||||
if !self.inner.remap_pixel_coords(&mut p, wrap_mode) {
|
||||
return ImageChannelValues(SmallVec::from_elem(0.0, self.inner.n_channels() as usize));
|
||||
}
|
||||
let offset = self.inner.pixel_offset(p);
|
||||
let nc = self.inner.n_channels() as usize;
|
||||
let mut values = SmallVec::with_capacity(nc);
|
||||
for i in 0..nc {
|
||||
values.push(unsafe { self.inner.pixels.read(offset + i, &self.inner.encoding) });
|
||||
}
|
||||
ImageChannelValues(values)
|
||||
}
|
||||
|
||||
pub fn get_channels_with_desc(
|
||||
&self,
|
||||
p: Point2i,
|
||||
desc: &ImageChannelDesc,
|
||||
wrap_mode: WrapMode2D,
|
||||
) -> ImageChannelValues {
|
||||
let mut pp = p;
|
||||
if !self.inner.remap_pixel_coords(&mut pp, wrap_mode) {
|
||||
return ImageChannelValues(SmallVec::from_elem(0.0, desc.offset.len()));
|
||||
}
|
||||
let pixel_offset = self.inner.pixel_offset(pp);
|
||||
let mut values = SmallVec::with_capacity(desc.offset.len());
|
||||
for &c in &desc.offset {
|
||||
values.push(unsafe { self.inner.pixels.read(pixel_offset + c, &self.inner.encoding) });
|
||||
}
|
||||
ImageChannelValues(values)
|
||||
}
|
||||
|
||||
pub fn set_channels(&mut self, p: Point2i, values: &ImageChannelValues) {
|
||||
for i in 0..values.len() {
|
||||
self.inner.set_channel(p, i as i32, values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_channels(&self, desc: &ImageChannelDesc) -> Self {
|
||||
let new_names: Vec<String> = desc
|
||||
.offset
|
||||
.iter()
|
||||
.map(|&i| self.channel_names[i].clone())
|
||||
.collect();
|
||||
|
||||
let res = self.resolution();
|
||||
let new_names: Vec<String> = desc.offset.iter().map(|&i| self.channel_names[i].clone()).collect();
|
||||
let res = self.inner.resolution();
|
||||
let pixel_count = (res.x() * res.y()) as usize;
|
||||
let src_nc = self.n_channels() as usize;
|
||||
let src_nc = self.inner.n_channels() as usize;
|
||||
let dst_nc = desc.offset.len();
|
||||
|
||||
let new_storage = match &self.pixels {
|
||||
PixelStorage::U8(src) => {
|
||||
let mut dst = vec![0u8; pixel_count * dst_nc];
|
||||
for i in 0..pixel_count {
|
||||
for (out_idx, &in_c) in desc.offset.iter().enumerate() {
|
||||
dst[i * dst_nc + out_idx] = src[i * src_nc + in_c];
|
||||
}
|
||||
}
|
||||
PixelStorage::U8(dst)
|
||||
}
|
||||
PixelStorage::F16(src) => {
|
||||
let mut dst = vec![f16::ZERO; pixel_count * dst_nc];
|
||||
for i in 0..pixel_count {
|
||||
for (out_idx, &in_c) in desc.offset.iter().enumerate() {
|
||||
dst[i * dst_nc + out_idx] = src[i * src_nc + in_c];
|
||||
}
|
||||
}
|
||||
PixelStorage::F16(dst)
|
||||
}
|
||||
PixelStorage::F32(src) => {
|
||||
// Always produce f32 output for simplicity
|
||||
let mut dst = vec![0.0f32; pixel_count * dst_nc];
|
||||
for i in 0..pixel_count {
|
||||
let src_offset = i * src_nc;
|
||||
for (out_idx, &in_c) in desc.offset.iter().enumerate() {
|
||||
dst[i * dst_nc + out_idx] = src[i * src_nc + in_c];
|
||||
}
|
||||
}
|
||||
PixelStorage::F32(dst)
|
||||
}
|
||||
dst[i * dst_nc + out_idx] = unsafe {
|
||||
self.inner.pixels.read(src_offset + in_c, &self.inner.encoding)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Self::from_storage(new_storage, res, &new_names, self.encoding())
|
||||
Self::from_f32(&dst, res, &new_names)
|
||||
}
|
||||
|
||||
pub fn get_sampling_distribution<F>(&self, dxd_a: F, domain: Bounds2f) -> Array2D<Float>
|
||||
where
|
||||
F: Fn(Point2f) -> Float + Sync + Send,
|
||||
{
|
||||
let width = self.resolution().x();
|
||||
let height = self.resolution().y();
|
||||
let width = self.inner.resolution().x();
|
||||
let height = self.inner.resolution().y();
|
||||
|
||||
let mut dist: Array2D<Float> = Array2D::new_dims(width, height);
|
||||
let mut dist = Array2D::new_dims(width, height);
|
||||
|
||||
dist.values
|
||||
dist.as_mut_slice()
|
||||
.par_chunks_mut(width as usize)
|
||||
.enumerate()
|
||||
.for_each(|(y, row)| {
|
||||
let y = y as i32;
|
||||
|
||||
for (x, out_val) in row.iter_mut().enumerate() {
|
||||
let x = x as i32;
|
||||
|
||||
let value = self.get_channels(Point2i::new(x, y)).average();
|
||||
|
||||
let u = (x as Float + 0.5) / width as Float;
|
||||
let v = (y as Float + 0.5) / height as Float;
|
||||
let p = domain.lerp(Point2f::new(u, v));
|
||||
|
|
@ -499,29 +272,36 @@ impl Image {
|
|||
}
|
||||
|
||||
pub fn get_sampling_distribution_uniform(&self) -> Array2D<Float> {
|
||||
let default_domain = Bounds2f::from_points(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0));
|
||||
let domain = Bounds2f::from_points(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0));
|
||||
self.get_sampling_distribution(|_| 1.0, domain)
|
||||
}
|
||||
|
||||
self.get_sampling_distribution(|_| 1.0, default_domain)
|
||||
pub fn has_any_infinite_pixels(&self) -> bool {
|
||||
self.inner.has_any_infinite_pixels()
|
||||
}
|
||||
|
||||
pub fn has_any_nan_pixels(&self) -> bool {
|
||||
self.inner.has_any_nan_pixels()
|
||||
}
|
||||
|
||||
pub fn mse(
|
||||
&self,
|
||||
desc: ImageChannelDesc,
|
||||
ref_img: &Image,
|
||||
desc: &ImageChannelDesc,
|
||||
ref_img: &HostImage,
|
||||
generate_mse_image: bool,
|
||||
) -> (ImageChannelValues, Option<Image>) {
|
||||
let res = self.resolution();
|
||||
) -> (ImageChannelValues, Option<HostImage>) {
|
||||
let res = self.inner.resolution();
|
||||
assert_eq!(res, ref_img.inner.resolution());
|
||||
|
||||
let mut sum_se: Vec<f64> = vec![0.; desc.size()];
|
||||
let names_ref = self.channel_names_from_desc(&desc);
|
||||
let ref_desc = ref_img
|
||||
.get_channel_desc(&self.channel_names_from_desc(&desc))
|
||||
.expect("Channels not found in image");
|
||||
assert_eq!(res, ref_img.resolution());
|
||||
.get_channel_desc(&self.channel_names_from_desc(desc))
|
||||
.expect("Channels not found in reference image");
|
||||
|
||||
let width = res.x() as usize;
|
||||
let height = res.y() as usize;
|
||||
let n_channels = desc.offset.len();
|
||||
let n_channels = desc.size();
|
||||
|
||||
let mut sum_se: Vec<f64> = vec![0.0; n_channels];
|
||||
let mut mse_pixels = if generate_mse_image {
|
||||
vec![0.0f32; width * height * n_channels]
|
||||
} else {
|
||||
|
|
@ -530,18 +310,15 @@ impl Image {
|
|||
|
||||
for y in 0..res.y() {
|
||||
for x in 0..res.x() {
|
||||
let v =
|
||||
self.get_channels_with_desc(Point2i::new(x, y), &desc, WrapMode::Clamp.into());
|
||||
let v_ref = self.get_channels_with_desc(
|
||||
Point2i::new(x, y),
|
||||
&ref_desc,
|
||||
WrapMode::Clamp.into(),
|
||||
let v = self.get_channels_with_desc(
|
||||
Point2i::new(x, y), desc, WrapMode::Clamp.into(),
|
||||
);
|
||||
for c in 0..desc.size() {
|
||||
let v_ref = ref_img.get_channels_with_desc(
|
||||
Point2i::new(x, y), &ref_desc, WrapMode::Clamp.into(),
|
||||
);
|
||||
for c in 0..n_channels {
|
||||
let se = square(v[c] as f64 - v_ref[c] as f64);
|
||||
if se.is_infinite() {
|
||||
continue;
|
||||
}
|
||||
if se.is_infinite() { continue; }
|
||||
sum_se[c] += se;
|
||||
if generate_mse_image {
|
||||
let idx = (y as usize * width + x as usize) * n_channels + c;
|
||||
|
|
@ -556,7 +333,8 @@ impl Image {
|
|||
sum_se.iter().map(|&s| (s / pixel_count) as Float).collect();
|
||||
|
||||
let mse_image = if generate_mse_image {
|
||||
Some(Image::new(PixelFormat::F32, res, &names_ref, LINEAR.into()))
|
||||
let names = self.channel_names_from_desc(desc);
|
||||
Some(HostImage::from_f32(&mse_pixels, res, &names))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
@ -564,53 +342,11 @@ impl Image {
|
|||
(ImageChannelValues(mse_values), mse_image)
|
||||
}
|
||||
|
||||
pub fn update_view_pointers(&mut self) {
|
||||
self.device.pixels = match &self.pixels {
|
||||
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())),
|
||||
};
|
||||
pub fn image(&self) -> &Image {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub fn has_any_infinite_pixels(&self) -> bool {
|
||||
if self.format() == PixelFormat::F32 {
|
||||
return false;
|
||||
}
|
||||
|
||||
for y in 0..self.resolution().y() {
|
||||
for x in 0..self.resolution().x() {
|
||||
for c in 0..self.n_channels() {
|
||||
if self.get_channel(Point2i::new(x, y), c).is_infinite() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn has_any_nan_pixels(&self) -> bool {
|
||||
if self.format() == PixelFormat::F32 {
|
||||
return false;
|
||||
}
|
||||
|
||||
for y in 0..self.resolution().y() {
|
||||
for x in 0..self.resolution().x() {
|
||||
for c in 0..self.n_channels() {
|
||||
if self.get_channel(Point2i::new(x, y), c).is_nan() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Image {
|
||||
type Target = DeviceImage;
|
||||
|
||||
fn deref(&self) -> &DeviceImage {
|
||||
&self.device
|
||||
pub fn into_image(self) -> Image {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use super::Image;
|
||||
use crate::core::image::PixelStorage;
|
||||
use super::HostImage;
|
||||
use crate::core::image::pixel::PixelStorageTrait;
|
||||
use crate::core::image::PixelStorage;
|
||||
use rayon::prelude::*;
|
||||
use shared::Float;
|
||||
use shared::core::color::ColorEncoding;
|
||||
use shared::core::geometry::{Bounds2i, Point2i};
|
||||
use shared::core::image::{PixelFormat, WrapMode, WrapMode2D};
|
||||
use shared::utils::math::windowed_sinc;
|
||||
use shared::Float;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -15,71 +15,105 @@ pub struct ResampleWeight {
|
|||
pub weight: [Float; 4],
|
||||
}
|
||||
|
||||
impl Image {
|
||||
impl HostImage {
|
||||
pub fn flip_y(&mut self) {
|
||||
let res = self.resolution();
|
||||
let nc = self.n_channels() as usize;
|
||||
let res = self.inner.resolution;
|
||||
let nc = self.inner.n_channels as usize;
|
||||
|
||||
match &mut self.pixels {
|
||||
PixelStorage::U8(d) => flip_y_kernel(d, res, nc),
|
||||
PixelStorage::F16(d) => flip_y_kernel(d, res, nc),
|
||||
PixelStorage::F32(d) => flip_y_kernel(d, res, nc),
|
||||
match self.inner.format {
|
||||
PixelFormat::U8 => flip_y_kernel(self.inner.pixels.as_u8_mut(), res, nc),
|
||||
PixelFormat::F16 => flip_y_kernel(self.inner.pixels.as_f16_mut(), res, nc),
|
||||
PixelFormat::F32 => flip_y_kernel(self.inner.pixels.as_f32_slice_mut(), res, nc),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn crop(&self, bounds: Bounds2i) -> Image {
|
||||
let res = self.resolution();
|
||||
let n_channels = self.n_channels() as usize;
|
||||
|
||||
pub fn crop(&self, bounds: Bounds2i) -> HostImage {
|
||||
let n_channels = self.inner.n_channels as usize;
|
||||
let new_res = Point2i::new(
|
||||
bounds.p_max.x() - bounds.p_min.x(),
|
||||
bounds.p_max.y() - bounds.p_min.y(),
|
||||
);
|
||||
|
||||
let mut new_image = Image::new(
|
||||
self.format(),
|
||||
new_res,
|
||||
&self.channel_names,
|
||||
self.encoding().into(),
|
||||
);
|
||||
let new_names = self.channel_names.clone();
|
||||
let mut new_image =
|
||||
HostImage::new(self.inner.format, new_res, &new_names, self.inner.encoding);
|
||||
|
||||
match (&self.pixels, &mut new_image.pixels) {
|
||||
(PixelStorage::U8(src), PixelStorage::U8(dst)) => {
|
||||
crop_kernel(src, dst, res, bounds, n_channels)
|
||||
}
|
||||
(PixelStorage::F16(src), PixelStorage::F16(dst)) => {
|
||||
crop_kernel(src, dst, res, bounds, n_channels)
|
||||
}
|
||||
(PixelStorage::F32(src), PixelStorage::F32(dst)) => {
|
||||
crop_kernel(src, dst, res, bounds, n_channels)
|
||||
}
|
||||
_ => panic!("Format mismatch in crop"),
|
||||
match self.inner.format {
|
||||
PixelFormat::U8 => crop_kernel(
|
||||
self.inner.pixels.as_u8(),
|
||||
new_image.inner.pixels.as_u8_mut(),
|
||||
self.inner.resolution,
|
||||
bounds,
|
||||
n_channels,
|
||||
),
|
||||
PixelFormat::F16 => crop_kernel(
|
||||
self.inner.pixels.as_f16(),
|
||||
new_image.inner.pixels.as_f16_mut(),
|
||||
self.inner.resolution,
|
||||
bounds,
|
||||
n_channels,
|
||||
),
|
||||
PixelFormat::F32 => crop_kernel(
|
||||
self.inner.pixels.as_f32_slice(),
|
||||
new_image.inner.pixels.as_f32_slice_mut(),
|
||||
self.inner.resolution,
|
||||
bounds,
|
||||
n_channels,
|
||||
),
|
||||
}
|
||||
|
||||
new_image
|
||||
}
|
||||
|
||||
pub fn copy_rect_out(&self, extent: Bounds2i, buf: &mut [Float], wrap: WrapMode2D) {
|
||||
match &self.pixels {
|
||||
PixelStorage::U8(d) => copy_rect_out_kernel(d, self, extent, buf, wrap),
|
||||
PixelStorage::F16(d) => copy_rect_out_kernel(d, self, extent, buf, wrap),
|
||||
PixelStorage::F32(d) => copy_rect_out_kernel(d, self, extent, buf, wrap),
|
||||
match self.inner.format {
|
||||
PixelFormat::U8 => {
|
||||
copy_rect_out_kernel(self.inner.pixels.as_u8(), self, extent, buf, wrap)
|
||||
}
|
||||
PixelFormat::F16 => {
|
||||
copy_rect_out_kernel(self.inner.pixels.as_f16(), self, extent, buf, wrap)
|
||||
}
|
||||
PixelFormat::F32 => {
|
||||
copy_rect_out_kernel(self.inner.pixels.as_f32_slice(), self, extent, buf, wrap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_rect_in(&mut self, extent: Bounds2i, buf: &[Float]) {
|
||||
let res = self.resolution();
|
||||
let n_channels = self.n_channels() as usize;
|
||||
let encoding = self.encoding();
|
||||
let res = self.inner.resolution;
|
||||
let n_channels = self.inner.n_channels as usize;
|
||||
let encoding = self.inner.encoding;
|
||||
let format = self.inner.format;
|
||||
|
||||
match &mut self.pixels {
|
||||
PixelStorage::U8(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf),
|
||||
PixelStorage::F16(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf),
|
||||
PixelStorage::F32(d) => copy_rect_in_kernel(d, res, n_channels, encoding, extent, buf),
|
||||
match format {
|
||||
PixelFormat::U8 => copy_rect_in_kernel(
|
||||
self.inner.pixels.as_u8_mut(),
|
||||
res,
|
||||
n_channels,
|
||||
encoding,
|
||||
extent,
|
||||
buf,
|
||||
),
|
||||
PixelFormat::F16 => copy_rect_in_kernel(
|
||||
self.inner.pixels.as_f16_mut(),
|
||||
res,
|
||||
n_channels,
|
||||
encoding,
|
||||
extent,
|
||||
buf,
|
||||
),
|
||||
PixelFormat::F32 => copy_rect_in_kernel(
|
||||
self.inner.pixels.as_f32_slice_mut(),
|
||||
res,
|
||||
n_channels,
|
||||
encoding,
|
||||
extent,
|
||||
buf,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn float_resize_up(&self, new_res: Point2i, wrap_mode: WrapMode2D) -> Image {
|
||||
pub fn float_resize_up(&self, new_res: Point2i, wrap_mode: WrapMode2D) -> HostImage {
|
||||
let res = self.resolution();
|
||||
assert!(new_res.x() >= res.x() && new_res.y() >= res.y());
|
||||
assert!(
|
||||
|
|
@ -87,7 +121,7 @@ impl Image {
|
|||
"ResizeUp requires Float format"
|
||||
);
|
||||
|
||||
let resampled_image = Arc::new(Mutex::new(Image::new(
|
||||
let resampled_image = Arc::new(Mutex::new(HostImage::new(
|
||||
PixelFormat::F32,
|
||||
new_res,
|
||||
&self.channel_names,
|
||||
|
|
@ -132,7 +166,7 @@ impl Image {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn generate_pyramid(base: Image, _wrap: WrapMode) -> Vec<Image> {
|
||||
pub fn generate_pyramid(base: HostImage, _wrap: WrapMode) -> Vec<HostImage> {
|
||||
let mut levels = vec![base];
|
||||
let internal_wrap = WrapMode2D {
|
||||
uv: [WrapMode::Clamp; 2],
|
||||
|
|
@ -146,17 +180,32 @@ impl Image {
|
|||
}
|
||||
|
||||
let new_res = Point2i::new((old.x() / 2).max(1), (old.y() / 2).max(1));
|
||||
let mut next = Image::new(
|
||||
prev.format(),
|
||||
let mut next = HostImage::new(
|
||||
prev.inner.format,
|
||||
new_res,
|
||||
&prev.channel_names,
|
||||
prev.encoding().into(),
|
||||
prev.inner.encoding,
|
||||
);
|
||||
|
||||
match &mut next.pixels {
|
||||
PixelStorage::U8(d) => downsample_kernel(d, new_res, prev, internal_wrap),
|
||||
PixelStorage::F16(d) => downsample_kernel(d, new_res, prev, internal_wrap),
|
||||
PixelStorage::F32(d) => downsample_kernel(d, new_res, prev, internal_wrap),
|
||||
match next.inner.format {
|
||||
PixelFormat::U8 => downsample_kernel(
|
||||
next.inner.pixels.as_u8_mut(),
|
||||
new_res,
|
||||
&prev.inner,
|
||||
internal_wrap,
|
||||
),
|
||||
PixelFormat::F16 => downsample_kernel(
|
||||
next.inner.pixels.as_f16_mut(),
|
||||
new_res,
|
||||
&prev.inner,
|
||||
internal_wrap,
|
||||
),
|
||||
PixelFormat::F32 => downsample_kernel(
|
||||
next.inner.pixels.as_f32_slice_mut(),
|
||||
new_res,
|
||||
&prev.inner,
|
||||
internal_wrap,
|
||||
),
|
||||
}
|
||||
levels.push(next);
|
||||
}
|
||||
|
|
@ -202,7 +251,7 @@ fn crop_kernel<T: PixelStorageTrait>(
|
|||
|
||||
fn copy_rect_out_kernel<T: PixelStorageTrait>(
|
||||
src: &[T],
|
||||
image: &Image,
|
||||
image: &HostImage,
|
||||
extent: Bounds2i,
|
||||
buf: &mut [Float],
|
||||
wrap: WrapMode2D,
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ use crate::core::shape::{ShapeFactory, ShapeWithContext};
|
|||
use crate::core::texture::{FloatTexture, SpectrumTexture};
|
||||
use crate::utils::parallel::{run_async, AsyncJob};
|
||||
use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
|
||||
use crate::utils::{resolve_filename, Upload};
|
||||
use crate::{Arena, FileLoc};
|
||||
use crate::utils::resolve_filename;
|
||||
use crate::{Arena, DeviceRepr, FileLoc};
|
||||
use anyhow::{anyhow, Result};
|
||||
use parking_lot::Mutex;
|
||||
use shared::core::camera::{Camera, CameraTransform};
|
||||
|
|
@ -121,7 +121,7 @@ impl BasicScene {
|
|||
sampler: SceneEntity,
|
||||
integ: SceneEntity,
|
||||
accel: SceneEntity,
|
||||
arena: Arc<Arena>,
|
||||
arena: &Arena,
|
||||
) -> Result<()> {
|
||||
*self.integrator.lock() = Some(integ);
|
||||
*self.accelerator.lock() = Some(accel);
|
||||
|
|
@ -130,7 +130,7 @@ impl BasicScene {
|
|||
*self.film_colorspace.lock() = Some(Arc::clone(cs));
|
||||
}
|
||||
|
||||
let filter = Filter::create(&filter.name, &filter.parameters, &filter.loc)
|
||||
let filter = Filter::create(&filter.name, &filter.parameters, &filter.loc, &arena)
|
||||
.map_err(|e| anyhow!("Failed to create filter: {}", e))?;
|
||||
let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.)?;
|
||||
let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.)?;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
use crate::textures::*;
|
||||
use crate::utils::mipmap::MIPMap;
|
||||
use crate::utils::mipmap::MIPMapFilterOptions;
|
||||
use crate::utils::{Arena, FileLoc, TextureParameterDictionary};
|
||||
use anyhow::{Result, anyhow};
|
||||
use crate::utils::mipmap::{MIPMapFilterOptions, MIPMap};
|
||||
use crate::utils::TextureParameterDictionary;
|
||||
use crate::{Arena, Device, FileLoc, DeviceRepr};
|
||||
use anyhow::{anyhow, Result};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use shared::Float;
|
||||
use shared::core::color::ColorEncoding;
|
||||
use shared::core::geometry::Vector3f;
|
||||
use shared::core::image::WrapMode;
|
||||
use shared::core::texture::SpectrumType;
|
||||
use shared::core::texture::{
|
||||
CylindricalMapping, PlanarMapping, SphericalMapping, TextureEvalContext, TextureMapping2D,
|
||||
UVMapping,
|
||||
UVMapping, GPUFloatTexture, GPUSpectrumTexture
|
||||
};
|
||||
use shared::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use shared::textures::*;
|
||||
use shared::utils::Transform;
|
||||
use shared::Float;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
|
|
@ -29,20 +29,49 @@ pub trait SpectrumTextureTrait {
|
|||
fn evaluate(&self, _ctx: &TextureEvalContext, _lambda: &SampledWavelengths) -> SampledSpectrum;
|
||||
}
|
||||
|
||||
#[enum_dispatch(FloatTextureTrait)]
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone, Debug, Device)]
|
||||
#[device(name = "GPUFloatTexture")]
|
||||
pub enum FloatTexture {
|
||||
#[device(clone)]
|
||||
Constant(FloatConstantTexture),
|
||||
Image(FloatImageTexture),
|
||||
Mix(FloatMixTexture),
|
||||
DirectionMix(FloatDirectionMixTexture),
|
||||
Scaled(FloatScaledTexture),
|
||||
Bilerp(FloatBilerpTexture),
|
||||
|
||||
#[device(clone)]
|
||||
Checkerboard(FloatCheckerboardTexture),
|
||||
|
||||
#[device(clone)]
|
||||
Dots(FloatDotsTexture),
|
||||
|
||||
#[device(clone)]
|
||||
FBm(FBmTexture),
|
||||
|
||||
#[device(clone)]
|
||||
Windy(WindyTexture),
|
||||
|
||||
#[device(clone)]
|
||||
Wrinkled(WrinkledTexture),
|
||||
|
||||
Scaled(FloatScaledTexture),
|
||||
|
||||
Mix(FloatMixTexture),
|
||||
|
||||
DirectionMix(FloatDirectionMixTexture),
|
||||
|
||||
#[device(custom = "upload_image", variant_type = "GPUFloatImageTexture")]
|
||||
Image(FloatImageTexture),
|
||||
|
||||
#[device(clone)]
|
||||
Bilerp(FloatBilerpTexture),
|
||||
}
|
||||
|
||||
impl FloatTexture {
|
||||
fn upload_image(inner: &FloatImageTexture, _arena: &Arena) -> GPUFloatImageTexture {
|
||||
GPUFloatImageTexture {
|
||||
mapping: inner.base.mapping,
|
||||
tex_obj: inner.base.mipmap.texture_object(),
|
||||
scale: inner.base.scale,
|
||||
invert: inner.base.invert,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FloatTexture {
|
||||
|
|
@ -95,21 +124,58 @@ impl FloatTexture {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[enum_dispatch(SpectrumTextureTrait)]
|
||||
#[derive(Clone, Debug, Device)]
|
||||
#[device(name = "GPUSpectrumTexture")]
|
||||
pub enum SpectrumTexture {
|
||||
// RGBConstant(RGBConstantTexture),
|
||||
// RGBReflectanceConstant(RGBReflectanceConstantTexture),
|
||||
#[device(clone)]
|
||||
Constant(SpectrumConstantTexture),
|
||||
Bilerp(SpectrumBilerpTexture),
|
||||
|
||||
#[device(clone)]
|
||||
Checkerboard(SpectrumCheckerboardTexture),
|
||||
Image(SpectrumImageTexture),
|
||||
Marble(MarbleTexture),
|
||||
Mix(SpectrumMixTexture),
|
||||
DirectionMix(SpectrumDirectionMixTexture),
|
||||
|
||||
#[device(clone)]
|
||||
Dots(SpectrumDotsTexture),
|
||||
// Ptex(SpectrumPtexTexture),
|
||||
|
||||
#[device(
|
||||
custom = "upload_spectrum_image",
|
||||
variant_type = "GPUSpectrumImageTexture"
|
||||
)]
|
||||
Image(SpectrumImageTexture),
|
||||
|
||||
#[device(clone)]
|
||||
Bilerp(SpectrumBilerpTexture),
|
||||
|
||||
Scaled(SpectrumScaledTexture),
|
||||
|
||||
#[device(clone)]
|
||||
Marble(MarbleTexture),
|
||||
|
||||
Mix(SpectrumMixTexture),
|
||||
|
||||
DirectionMix(SpectrumDirectionMixTexture),
|
||||
}
|
||||
|
||||
impl SpectrumTexture {
|
||||
fn upload_spectrum_image(
|
||||
inner: &SpectrumImageTexture,
|
||||
arena: &Arena,
|
||||
) -> GPUSpectrumImageTexture {
|
||||
let tex_obj = arena.get_texture_object(&inner.base.mipmap);
|
||||
GPUSpectrumImageTexture {
|
||||
mapping: inner.base.mapping,
|
||||
tex_obj,
|
||||
scale: inner.base.scale,
|
||||
invert: inner.base.invert,
|
||||
is_single_channel: inner.base.mipmap.is_single_channel(),
|
||||
color_space: inner
|
||||
.base
|
||||
.mipmap
|
||||
.color_space
|
||||
.clone()
|
||||
.unwrap_or_else(crate::spectra::default_colorspace),
|
||||
spectrum_type: inner.spectrum_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CreateSpectrumTexture {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ impl GBufferFilmHost {
|
|||
base: base.clone(),
|
||||
output_from_render: *output_from_render,
|
||||
apply_inverse,
|
||||
pixels: pixels.device,
|
||||
pixels,
|
||||
colorspace: colorspace.clone(),
|
||||
max_component_value,
|
||||
write_fp16,
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ use shared::core::film::{FilmBase, SpectralFilm, SpectralPixel};
|
|||
use shared::core::filter::FilterTrait;
|
||||
use shared::spectra::{LAMBDA_MAX, LAMBDA_MIN, RGBColorSpace};
|
||||
use shared::utils::AtomicFloat;
|
||||
use shared::utils::containers::DeviceArray2D;
|
||||
use shared::utils::containers::Array2D;
|
||||
use shared::utils::math::SquareMatrix;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
struct SpectralFilmStorage {
|
||||
pixels: DeviceArray2D<SpectralPixel>,
|
||||
pixels: Array2D<SpectralPixel>,
|
||||
bucket_sums: Vec<f64>,
|
||||
weight_sums: Vec<f64>,
|
||||
bucket_splats: Vec<AtomicFloat>,
|
||||
|
|
@ -72,7 +72,7 @@ impl SpectralFilmHost {
|
|||
filter_integral: base.filter.integral(),
|
||||
output_rgbf_from_sensor_rgb: SquareMatrix::identity(),
|
||||
|
||||
pixels: DeviceArray2D {
|
||||
pixels: Array2D {
|
||||
values: pixels.values.as_mut_ptr(),
|
||||
extent: base.pixel_bounds,
|
||||
stride: base.pixel_bounds.p_max.x() - base.pixel_bounds.p_min.x(),
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
pub trait BoxFilterCreator {}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
use crate::core::filter::CreateFilterSampler;
|
||||
use shared::Float;
|
||||
use shared::core::filter::FilterSampler;
|
||||
use shared::core::geometry::{Point2f, Vector2f};
|
||||
use shared::filters::GaussianFilter;
|
||||
use shared::utils::math::gaussian;
|
||||
|
||||
#[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 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.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, exp_y, sampler }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
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::{lerp, windowed_sinc};
|
||||
|
||||
#[derive(Clone, Debug, Device)]
|
||||
#[device(name = "LanczosSincFilter")]
|
||||
pub struct LanczosSincFilterHost {
|
||||
pub radius: Vector2f,
|
||||
pub tau: Float,
|
||||
#[device(flatten)]
|
||||
pub sampler: FilterSampler,
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
use crate::core::filter::CreateFilterSampler;
|
||||
use shared::Float;
|
||||
use shared::core::filter::FilterSampler;
|
||||
use shared::core::geometry::{Point2f, Vector2f};
|
||||
use shared::filters::MitchellFilter;
|
||||
|
||||
#[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 MitchellFilterHost {
|
||||
pub fn new(radius: Vector2f, b: Float, c: Float) -> Self {
|
||||
let sampler = FilterSampler::new(radius, move |p: Point2f| {
|
||||
mitchell_1d(p.x() / radius.x(), b, c) * mitchell_1d(p.y() / radius.y(), b, c)
|
||||
});
|
||||
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,11 +0,0 @@
|
|||
pub mod boxf;
|
||||
pub mod gaussian;
|
||||
pub mod lanczos;
|
||||
pub mod mitchell;
|
||||
pub mod triangle;
|
||||
|
||||
pub use boxf::*;
|
||||
pub use gaussian::*;
|
||||
pub use lanczos::*;
|
||||
pub use mitchell::*;
|
||||
// pub use triangle::*;
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -17,4 +17,4 @@ pub mod utils;
|
|||
#[cfg(feature = "cuda")]
|
||||
pub mod gpu;
|
||||
|
||||
pub use utils::{Arena, FileLoc, ParameterDictionary};
|
||||
pub use utils::{Arena, Device, DeviceRepr, FileLoc, ParameterDictionary};
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ use crate::core::image::{Image, ImageIO};
|
|||
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 crate::utils::resolve_filename;
|
||||
use crate::{Arena, DeviceRepr, FileLoc, ParameterDictionary};
|
||||
use anyhow::{anyhow, Result};
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::light::{Light, LightBase, LightType};
|
||||
use shared::core::medium::{Medium, MediumInterface};
|
||||
|
|
@ -110,7 +111,7 @@ pub fn create(
|
|||
_ => false,
|
||||
};
|
||||
|
||||
let (light_type, stored_alpha) = if is_constant_zero {
|
||||
let (light_type, _) = if is_constant_zero {
|
||||
(LightType::DeltaPosition, None)
|
||||
} else {
|
||||
(LightType::Area, Some(alpha))
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use crate::core::light::lookup_spectrum;
|
|||
use crate::core::spectrum::spectrum_to_photometric;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename};
|
||||
use crate::utils::resolve_filename;
|
||||
use crate::{Arena, FileLoc, ParameterDictionary, DeviceRepr};
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::core::geometry::Point2i;
|
||||
use shared::core::light::{Light, LightBase, LightType};
|
||||
|
|
@ -80,7 +81,7 @@ pub fn create(
|
|||
];
|
||||
let t =
|
||||
Transform::from_flat(&swap_yz).expect("Could not create transform for GoniometricLight");
|
||||
let final_render_from_light = render_from_light * t;
|
||||
let _final_render_from_light = render_from_light * t;
|
||||
|
||||
let mi = match medium {
|
||||
Some(m) => {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use crate::core::image::{Image, ImageIO};
|
||||
use crate::core::image::{HostImage, ImageIO};
|
||||
use crate::core::light::lookup_spectrum;
|
||||
use crate::core::spectrum::spectrum_to_photometric;
|
||||
use crate::spectra::get_spectra_context;
|
||||
use crate::utils::resolve_filename;
|
||||
use crate::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D};
|
||||
use crate::utils::{resolve_filename, FileLoc, ParameterDictionary, Upload};
|
||||
use crate::Arena;
|
||||
use crate::{Arena, DeviceRepr, FileLoc, ParameterDictionary};
|
||||
use anyhow::{anyhow, Result};
|
||||
use rayon::prelude::*;
|
||||
use shared::core::camera::CameraTransform;
|
||||
use shared::core::geometry::{cos_theta, Bounds2f, Frame, Point2f, Point2i, Point3f, VectorLike};
|
||||
use shared::core::image::{DeviceImage, WrapMode};
|
||||
use shared::core::image::WrapMode;
|
||||
use shared::core::light::{Light, LightBase, LightType};
|
||||
use shared::core::medium::{Medium, MediumInterface};
|
||||
use shared::core::spectrum::Spectrum;
|
||||
|
|
@ -17,19 +17,18 @@ use shared::core::texture::SpectrumType;
|
|||
use shared::lights::{ImageInfiniteLight, PortalInfiniteLight, UniformInfiniteLight};
|
||||
use shared::spectra::{DenselySampledSpectrum, RGBColorSpace};
|
||||
use shared::utils::math::{equal_area_sphere_to_square, equal_area_square_to_sphere};
|
||||
use shared::utils::sampling::{DevicePiecewiseConstant2D, DeviceWindowedPiecewiseConstant2D};
|
||||
use shared::utils::{Ptr, Transform};
|
||||
use shared::{Float, PI};
|
||||
use shared::utils::sampling::{PiecewiseConstant2D, WindowedPiecewiseConstant2D};
|
||||
use shared::{Float, Ptr, Transform, PI};
|
||||
use std::path::Path;
|
||||
|
||||
pub trait CreateImageInfiniteLight {
|
||||
fn new(
|
||||
render_from_light: Transform,
|
||||
scale: Float,
|
||||
image: Ptr<DeviceImage>,
|
||||
image: Ptr<HostImage>,
|
||||
image_color_space: Ptr<RGBColorSpace>,
|
||||
distrib: Ptr<DevicePiecewiseConstant2D>,
|
||||
compensated_distrib: Ptr<DevicePiecewiseConstant2D>,
|
||||
distrib: Ptr<PiecewiseConstant2D>,
|
||||
compensated_distrib: Ptr<PiecewiseConstant2D>,
|
||||
) -> Self;
|
||||
}
|
||||
|
||||
|
|
@ -37,10 +36,10 @@ impl CreateImageInfiniteLight for ImageInfiniteLight {
|
|||
fn new(
|
||||
render_from_light: Transform,
|
||||
scale: Float,
|
||||
image: Ptr<DeviceImage>,
|
||||
image: Ptr<HostImage>,
|
||||
image_color_space: Ptr<RGBColorSpace>,
|
||||
distrib: Ptr<DevicePiecewiseConstant2D>,
|
||||
compensated_distrib: Ptr<DevicePiecewiseConstant2D>,
|
||||
distrib: Ptr<PiecewiseConstant2D>,
|
||||
compensated_distrib: Ptr<PiecewiseConstant2D>,
|
||||
) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::Infinite,
|
||||
|
|
@ -64,11 +63,11 @@ pub trait CreatePortalInfiniteLight {
|
|||
fn new(
|
||||
render_from_light: Transform,
|
||||
scale: Float,
|
||||
image: Ptr<DeviceImage>,
|
||||
image: Ptr<HostImage>,
|
||||
image_color_space: Ptr<RGBColorSpace>,
|
||||
portal: [Point3f; 4],
|
||||
portal_frame: Frame,
|
||||
distribution: Ptr<DeviceWindowedPiecewiseConstant2D>,
|
||||
distribution: Ptr<WindowedPiecewiseConstant2D>,
|
||||
) -> Self;
|
||||
}
|
||||
|
||||
|
|
@ -76,11 +75,11 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight {
|
|||
fn new(
|
||||
render_from_light: Transform,
|
||||
scale: Float,
|
||||
image: Ptr<DeviceImage>,
|
||||
image: Ptr<HostImage>,
|
||||
image_color_space: Ptr<RGBColorSpace>,
|
||||
portal: [Point3f; 4],
|
||||
portal_frame: Frame,
|
||||
distribution: Ptr<DeviceWindowedPiecewiseConstant2D>,
|
||||
distribution: Ptr<WindowedPiecewiseConstant2D>,
|
||||
) -> Self {
|
||||
let base = LightBase::new(
|
||||
LightType::Infinite,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ use crate::core::image::{Image, ImageIO};
|
|||
use crate::core::spectrum::spectrum_to_photometric;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary, Upload, resolve_filename};
|
||||
use crate::utils::resolve_filename;
|
||||
use crate::{Arena, DeviceRepr, FileLoc, ParameterDictionary};
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::Float;
|
||||
use shared::core::geometry::{
|
||||
|
|
@ -77,7 +78,7 @@ pub fn create(
|
|||
}
|
||||
|
||||
let flip = Transform::scale(1., -1., 1.);
|
||||
let render_from_light_flip = render_from_light * flip;
|
||||
let _render_from_light_flip = render_from_light * flip;
|
||||
|
||||
let mi = match medium {
|
||||
Some(m) => {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ 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};
|
||||
use crate::utils::TextureParameterDictionary;
|
||||
use crate::{Arena, DeviceRepr, FileLoc};
|
||||
use anyhow::{bail, Result};
|
||||
use shared::core::material::Material;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::SpectrumType;
|
||||
|
|
|
|||
|
|
@ -2,15 +2,14 @@ use crate::core::image::Image;
|
|||
use crate::core::material::CreateMaterial;
|
||||
use crate::core::texture::SpectrumTexture;
|
||||
use crate::spectra::get_colorspace_device;
|
||||
use crate::utils::{Arena, FileLoc, TextureParameterDictionary, Upload};
|
||||
use crate::{Arena, DeviceRepr, FileLoc};
|
||||
use crate::utils::TextureParameterDictionary;
|
||||
use shared::bxdfs::HairBxDF;
|
||||
use shared::core::material::Material;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use shared::core::texture::SpectrumType;
|
||||
use shared::materials::complex::*;
|
||||
// use shared::spectra::SampledWavelengths;
|
||||
use shared::textures::SpectrumConstantTexture;
|
||||
// use shared::utils::Ptr;
|
||||
use anyhow::Result;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use crate::core::image::{Image, ImageIO};
|
||||
use crate::core::shape::{ALL_BILINEAR_MESHES, CreateShape};
|
||||
use crate::core::shape::{CreateShape, ALL_BILINEAR_MESHES};
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::shapes::mesh::BilinearPatchMesh;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||
use anyhow::{Result, anyhow};
|
||||
use crate::{Arena, FileLoc, ParameterDictionary};
|
||||
use anyhow::{anyhow, Result};
|
||||
use log::warn;
|
||||
use shared::core::shape::Shape;
|
||||
use shared::shapes::BilinearPatchShape;
|
||||
use shared::utils::{Ptr, Transform};
|
||||
use shared::{Ptr, Transform};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
use crate::Arena;
|
||||
use crate::core::shape::CreateShape;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::{FileLoc, ParameterDictionary};
|
||||
use anyhow::{Result, anyhow};
|
||||
use shared::Float;
|
||||
use crate::{Arena, FileLoc, ParameterDictionary};
|
||||
use anyhow::{anyhow, Result};
|
||||
use log::warn;
|
||||
use shared::core::geometry::{Normal3f, Point3f};
|
||||
use shared::core::shape::Shape;
|
||||
use shared::shapes::{CurveCommon, CurveShape, CurveType};
|
||||
use shared::utils::Transform;
|
||||
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 shared::Float;
|
||||
use shared::{Ptr, Transform};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -199,7 +196,7 @@ impl CreateShape for CurveShape {
|
|||
curve_type,
|
||||
seg_normals.expect("Could not determine normals to curve segments"),
|
||||
split_depth.try_into().unwrap(),
|
||||
arena
|
||||
arena,
|
||||
);
|
||||
|
||||
curves.extend(new_curves);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use crate::core::shape::CreateShape;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||
use crate::{Arena, FileLoc, ParameterDictionary};
|
||||
use anyhow::Result;
|
||||
use shared::core::shape::Shape;
|
||||
use shared::shapes::CylinderShape;
|
||||
use shared::utils::Transform;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use shared::Ptr;
|
||||
use shared::{Transform, Ptr};
|
||||
|
||||
impl CreateShape for CylinderShape {
|
||||
fn create(
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use crate::core::shape::CreateShape;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||
use crate::{Arena, FileLoc, ParameterDictionary};
|
||||
use anyhow::Result;
|
||||
use shared::core::shape::Shape;
|
||||
use shared::shapes::DiskShape;
|
||||
use shared::utils::Transform;
|
||||
use shared::{Ptr, Transform};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use shared::Ptr;
|
||||
|
||||
impl CreateShape for DiskShape {
|
||||
fn create(
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
// use crate::Arena;
|
||||
use crate::utils::sampling::PiecewiseConstant2D;
|
||||
use crate::{Arena, Device, DeviceRepr};
|
||||
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};
|
||||
use shared::utils::mesh::{DeviceBilinearPatchMesh, DeviceTriangleMesh};
|
||||
use shared::utils::{Ptr, Transform};
|
||||
use shared::shapes::mesh::{BilinearPatchMesh, TriangleMesh};
|
||||
use shared::{Ptr, Transform};
|
||||
use std::fs::File;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
/// Intermediate mesh from PLY
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TriQuadMesh {
|
||||
pub p: Vec<Point3f>,
|
||||
|
|
@ -21,235 +19,7 @@ pub struct TriQuadMesh {
|
|||
pub quad_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>,
|
||||
pub p: Vec<Point3f>,
|
||||
pub n: Vec<Normal3f>,
|
||||
pub uv: Vec<Point2f>,
|
||||
pub image_distribution: Option<PiecewiseConstant2D>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TriangleMesh {
|
||||
pub storage: Box<TriangleMeshStorage>,
|
||||
pub device: DeviceTriangleMesh,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BilinearPatchMesh {
|
||||
pub storage: Box<BilinearMeshStorage>,
|
||||
pub device: DeviceBilinearPatchMesh,
|
||||
}
|
||||
|
||||
impl Deref for TriangleMesh {
|
||||
type Target = DeviceTriangleMesh;
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for BilinearPatchMesh {
|
||||
type Target = DeviceBilinearPatchMesh;
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl TriangleMesh {
|
||||
pub fn new(
|
||||
render_from_object: &Transform,
|
||||
reverse_orientation: bool,
|
||||
vertex_indices: Vec<i32>,
|
||||
mut p: Vec<Point3f>,
|
||||
mut n: Vec<Normal3f>,
|
||||
mut s: Vec<Vector3f>,
|
||||
uv: Vec<Point2f>,
|
||||
face_indices: Vec<i32>,
|
||||
) -> Self {
|
||||
let n_triangles = vertex_indices.len() / 3;
|
||||
let n_vertices = p.len();
|
||||
|
||||
// Transform positions to render space
|
||||
for pt in p.iter_mut() {
|
||||
*pt = render_from_object.apply_to_point(*pt);
|
||||
}
|
||||
|
||||
// Transform and optionally flip normals
|
||||
if !n.is_empty() {
|
||||
assert_eq!(n_vertices, n.len(), "Normal count mismatch");
|
||||
for nn in n.iter_mut() {
|
||||
*nn = render_from_object.apply_to_normal(*nn);
|
||||
if reverse_orientation {
|
||||
*nn = -*nn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform tangents
|
||||
if !s.is_empty() {
|
||||
assert_eq!(n_vertices, s.len(), "Tangent count mismatch");
|
||||
for ss in s.iter_mut() {
|
||||
*ss = render_from_object.apply_to_vector(*ss);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate UVs
|
||||
if !uv.is_empty() {
|
||||
assert_eq!(n_vertices, uv.len(), "UV count mismatch");
|
||||
}
|
||||
|
||||
// Validate face indices
|
||||
if !face_indices.is_empty() {
|
||||
assert_eq!(n_triangles, face_indices.len(), "Face index count mismatch");
|
||||
}
|
||||
|
||||
let transform_swaps_handedness = render_from_object.swaps_handedness();
|
||||
|
||||
let storage = Box::new(TriangleMeshStorage {
|
||||
vertex_indices,
|
||||
p,
|
||||
n,
|
||||
s,
|
||||
uv,
|
||||
face_indices,
|
||||
});
|
||||
|
||||
// Build device struct with pointers into storage
|
||||
let device = DeviceTriangleMesh {
|
||||
n_triangles: n_triangles as u32,
|
||||
n_vertices: n_vertices as u32,
|
||||
vertex_indices: storage.vertex_indices.as_ptr().into(),
|
||||
p: storage.p.as_ptr().into(),
|
||||
n: if storage.n.is_empty() {
|
||||
Ptr::null()
|
||||
} else {
|
||||
storage.n.as_ptr().into()
|
||||
},
|
||||
s: if storage.s.is_empty() {
|
||||
Ptr::null()
|
||||
} else {
|
||||
storage.s.as_ptr().into()
|
||||
},
|
||||
uv: if storage.uv.is_empty() {
|
||||
Ptr::null()
|
||||
} else {
|
||||
storage.uv.as_ptr().into()
|
||||
},
|
||||
face_indices: if storage.face_indices.is_empty() {
|
||||
Ptr::null()
|
||||
} else {
|
||||
storage.face_indices.as_ptr().into()
|
||||
},
|
||||
reverse_orientation,
|
||||
transform_swaps_handedness,
|
||||
};
|
||||
|
||||
Self { storage, device }
|
||||
}
|
||||
|
||||
pub fn positions(&self) -> &[Point3f] {
|
||||
&self.storage.p
|
||||
}
|
||||
|
||||
pub fn indices(&self) -> &[i32] {
|
||||
&self.storage.vertex_indices
|
||||
}
|
||||
|
||||
pub fn normals(&self) -> &[Normal3f] {
|
||||
&self.storage.n
|
||||
}
|
||||
|
||||
pub fn uvs(&self) -> &[Point2f] {
|
||||
&self.storage.uv
|
||||
}
|
||||
}
|
||||
|
||||
impl BilinearPatchMesh {
|
||||
pub fn new(
|
||||
render_from_object: &Transform,
|
||||
reverse_orientation: bool,
|
||||
vertex_indices: Vec<i32>,
|
||||
mut p: Vec<Point3f>,
|
||||
mut n: Vec<Normal3f>,
|
||||
uv: Vec<Point2f>,
|
||||
image_distribution: Option<PiecewiseConstant2D>,
|
||||
) -> Self {
|
||||
let n_patches = vertex_indices.len() / 4;
|
||||
let n_vertices = p.len();
|
||||
|
||||
for pt in p.iter_mut() {
|
||||
*pt = render_from_object.apply_to_point(*pt);
|
||||
}
|
||||
|
||||
if !n.is_empty() {
|
||||
assert_eq!(n_vertices, n.len(), "Normal count mismatch");
|
||||
for nn in n.iter_mut() {
|
||||
*nn = render_from_object.apply_to_normal(*nn);
|
||||
if reverse_orientation {
|
||||
*nn = -*nn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !uv.is_empty() {
|
||||
assert_eq!(n_vertices, uv.len(), "UV count mismatch");
|
||||
}
|
||||
|
||||
let transform_swaps_handedness = render_from_object.swaps_handedness();
|
||||
|
||||
let storage = Box::new(BilinearMeshStorage {
|
||||
vertex_indices,
|
||||
p,
|
||||
n,
|
||||
uv,
|
||||
image_distribution,
|
||||
});
|
||||
|
||||
let device = DeviceBilinearPatchMesh {
|
||||
n_patches: n_patches as u32,
|
||||
n_vertices: n_vertices as u32,
|
||||
vertex_indices: storage.vertex_indices.as_ptr().into(),
|
||||
p: storage.p.as_ptr().into(),
|
||||
n: if storage.n.is_empty() {
|
||||
Ptr::null()
|
||||
} else {
|
||||
storage.n.as_ptr().into()
|
||||
},
|
||||
uv: if storage.uv.is_empty() {
|
||||
Ptr::null()
|
||||
} else {
|
||||
storage.uv.as_ptr().into()
|
||||
},
|
||||
reverse_orientation,
|
||||
transform_swaps_handedness,
|
||||
image_distribution: storage
|
||||
.image_distribution
|
||||
.as_ref()
|
||||
.map(|d| Ptr::from(&d.device))
|
||||
.unwrap_or(Ptr::null()),
|
||||
};
|
||||
|
||||
Self { storage, device }
|
||||
}
|
||||
}
|
||||
|
||||
// PLY Helper Functions
|
||||
|
||||
fn get_float(elem: &DefaultElement, key: &str) -> AnyResult<f32> {
|
||||
match elem.get(key) {
|
||||
Some(Property::Float(v)) => Ok(*v),
|
||||
|
|
@ -290,14 +60,12 @@ fn get_list_uint(elem: &DefaultElement, key: &str) -> AnyResult<Vec<u32>> {
|
|||
}
|
||||
}
|
||||
|
||||
// TriQuadMesh Implementation
|
||||
|
||||
impl TriQuadMesh {
|
||||
pub fn read_ply<P: AsRef<Path>>(filename: P) -> AnyResult<Self> {
|
||||
let path = filename.as_ref();
|
||||
let filename_display = path.display().to_string();
|
||||
|
||||
let mut f = File::open(path)
|
||||
let f = File::open(path)
|
||||
.with_context(|| format!("Couldn't open PLY file \"{}\"", filename_display))?;
|
||||
|
||||
let p = Parser::<DefaultElement>::new();
|
||||
|
|
@ -313,7 +81,6 @@ impl TriQuadMesh {
|
|||
|
||||
let mut mesh = TriQuadMesh::default();
|
||||
|
||||
// Parse vertices
|
||||
if let Some(vertices) = ply.payload.get("vertex") {
|
||||
if vertices.is_empty() {
|
||||
bail!("{}: PLY file has no vertices", filename_display);
|
||||
|
|
@ -360,7 +127,6 @@ impl TriQuadMesh {
|
|||
bail!("{}: PLY file has no vertex elements", filename_display);
|
||||
}
|
||||
|
||||
// Parse faces
|
||||
if let Some(faces) = ply.payload.get("face") {
|
||||
mesh.tri_indices.reserve(faces.len() * 3);
|
||||
mesh.quad_indices.reserve(faces.len() * 4);
|
||||
|
|
@ -390,7 +156,6 @@ impl TriQuadMesh {
|
|||
bail!("{}: PLY file has no face elements", filename_display);
|
||||
}
|
||||
|
||||
// Validate indices
|
||||
let vertex_count = mesh.p.len() as i32;
|
||||
for &idx in &mesh.tri_indices {
|
||||
if idx >= vertex_count {
|
||||
|
|
@ -421,18 +186,13 @@ impl TriQuadMesh {
|
|||
return;
|
||||
}
|
||||
|
||||
// Each quad becomes 2 triangles
|
||||
self.tri_indices.reserve(self.quad_indices.len() / 4 * 6);
|
||||
|
||||
for quad in self.quad_indices.chunks_exact(4) {
|
||||
let (v0, v1, v2, v3) = (quad[0], quad[1], quad[2], quad[3]);
|
||||
|
||||
// Triangle 1: v0, v1, v2
|
||||
self.tri_indices.push(v0);
|
||||
self.tri_indices.push(v1);
|
||||
self.tri_indices.push(v2);
|
||||
|
||||
// Triangle 2: v0, v2, v3
|
||||
self.tri_indices.push(v0);
|
||||
self.tri_indices.push(v2);
|
||||
self.tri_indices.push(v3);
|
||||
|
|
@ -442,10 +202,8 @@ impl TriQuadMesh {
|
|||
}
|
||||
|
||||
pub fn compute_normals(&mut self) {
|
||||
// Initialize normals to zero
|
||||
self.n = vec![Normal3f::new(0.0, 0.0, 0.0); self.p.len()];
|
||||
|
||||
// Accumulate face normals for triangles
|
||||
for tri in self.tri_indices.chunks_exact(3) {
|
||||
let (i0, i1, i2) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
|
||||
|
||||
|
|
@ -457,13 +215,11 @@ impl TriQuadMesh {
|
|||
let v20 = p2 - p0;
|
||||
let face_normal = v10.cross(v20);
|
||||
|
||||
// Accumulate (will normalize later)
|
||||
self.n[i0] = self.n[i0] + Normal3f::from(face_normal);
|
||||
self.n[i1] = self.n[i1] + Normal3f::from(face_normal);
|
||||
self.n[i2] = self.n[i2] + Normal3f::from(face_normal);
|
||||
}
|
||||
|
||||
// Accumulate face normals for quads
|
||||
for quad in self.quad_indices.chunks_exact(4) {
|
||||
let indices: Vec<usize> = quad.iter().map(|&i| i as usize).collect();
|
||||
|
||||
|
|
@ -480,7 +236,6 @@ impl TriQuadMesh {
|
|||
}
|
||||
}
|
||||
|
||||
// Normalize all normals
|
||||
for normal in &mut self.n {
|
||||
let len_sq = normal.norm_squared();
|
||||
if len_sq > 0.0 {
|
||||
|
|
@ -489,7 +244,6 @@ impl TriQuadMesh {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert to a TriangleMesh, consuming self
|
||||
pub fn into_triangle_mesh(
|
||||
mut self,
|
||||
render_from_object: &Transform,
|
||||
|
|
@ -507,15 +261,13 @@ impl TriQuadMesh {
|
|||
self.tri_indices,
|
||||
self.p,
|
||||
self.n,
|
||||
Vec::new(), // s (tangents)
|
||||
Vec::new(),
|
||||
self.uv,
|
||||
self.face_indices,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create from PLY directly
|
||||
|
||||
impl TriangleMesh {
|
||||
pub fn from_ply<P: AsRef<Path>>(
|
||||
filename: P,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use crate::core::shape::CreateShape;
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||
use crate::{Arena, FileLoc, ParameterDictionary};
|
||||
use anyhow::Result;
|
||||
use shared::core::shape::Shape;
|
||||
use shared::shapes::SphereShape;
|
||||
use shared::utils::Transform;
|
||||
use shared::{Ptr, Transform};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use shared::Ptr;
|
||||
|
||||
impl CreateShape for SphereShape {
|
||||
fn create(
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use crate::core::shape::{ALL_TRIANGLE_MESHES, CreateShape};
|
||||
use crate::core::shape::{CreateShape, ALL_TRIANGLE_MESHES};
|
||||
use crate::core::texture::FloatTexture;
|
||||
use crate::shapes::mesh::TriangleMesh;
|
||||
use crate::utils::{Arena, FileLoc, ParameterDictionary};
|
||||
use anyhow::{Result, bail};
|
||||
use crate::{Arena, FileLoc, ParameterDictionary};
|
||||
use anyhow::{bail, Result};
|
||||
use log::warn;
|
||||
use shared::core::shape::Shape;
|
||||
use shared::shapes::TriangleShape;
|
||||
use shared::utils::{Ptr, Transform};
|
||||
use shared::{Ptr, Transform};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,28 @@
|
|||
use crate::Arena;
|
||||
use crate::core::texture::{
|
||||
CreateSpectrumTexture, FloatTexture, FloatTextureTrait, SpectrumTexture, SpectrumTextureTrait,
|
||||
};
|
||||
use crate::utils::{FileLoc, TextureParameterDictionary};
|
||||
use crate::{Arena, Device, DeviceRepr};
|
||||
use anyhow::Result;
|
||||
use shared::Float;
|
||||
use shared::core::geometry::{Vector3f, VectorLike};
|
||||
use shared::core::texture::{SpectrumType, TextureEvalContext};
|
||||
use shared::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use shared::textures::{
|
||||
GPUFloatDirectionMixTexture, GPUFloatMixTexture, GPUSpectrumDirectionMixTexture,
|
||||
GPUSpectrumMixTexture,
|
||||
};
|
||||
use shared::utils::Transform;
|
||||
use shared::Float;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone, Debug, Device)]
|
||||
#[device(name = "GPUFloatMixTexture")]
|
||||
pub struct FloatMixTexture {
|
||||
#[device(upload)]
|
||||
pub tex1: Arc<FloatTexture>,
|
||||
#[device(upload)]
|
||||
pub tex2: Arc<FloatTexture>,
|
||||
#[device(upload)]
|
||||
pub amount: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
|
|
@ -56,9 +64,12 @@ impl FloatTextureTrait for FloatMixTexture {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone, Debug, Device)]
|
||||
#[device(name = "GPUFloatDirectionMixTexture")]
|
||||
pub struct FloatDirectionMixTexture {
|
||||
#[device(upload)]
|
||||
pub tex1: Arc<FloatTexture>,
|
||||
#[device(upload)]
|
||||
pub tex2: Arc<FloatTexture>,
|
||||
pub dir: Vector3f,
|
||||
}
|
||||
|
|
@ -89,10 +100,14 @@ impl FloatTextureTrait for FloatDirectionMixTexture {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone, Debug, Device)]
|
||||
#[device(name = "GPUSpectrumMixTexture")]
|
||||
pub struct SpectrumMixTexture {
|
||||
#[device(upload)]
|
||||
pub tex1: Arc<SpectrumTexture>,
|
||||
#[device(upload)]
|
||||
pub tex2: Arc<SpectrumTexture>,
|
||||
#[device(upload)]
|
||||
pub amount: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
|
|
@ -113,9 +128,12 @@ impl SpectrumTextureTrait for SpectrumMixTexture {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone, Debug, Device)]
|
||||
#[device(name = "GPUSpectrumDirectionMixTexture")]
|
||||
pub struct SpectrumDirectionMixTexture {
|
||||
#[device(upload)]
|
||||
pub tex1: Arc<SpectrumTexture>,
|
||||
#[device(upload)]
|
||||
pub tex2: Arc<SpectrumTexture>,
|
||||
pub dir: Vector3f,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
use crate::Arena;
|
||||
use crate::core::texture::{CreateSpectrumTexture, FloatTexture, SpectrumTexture};
|
||||
use crate::core::texture::{FloatTextureTrait, SpectrumTextureTrait};
|
||||
use crate::utils::{FileLoc, TextureParameterDictionary};
|
||||
use crate::{Arena, Device, DeviceRepr};
|
||||
use anyhow::Result;
|
||||
use shared::Float;
|
||||
use shared::core::texture::{SpectrumType, TextureEvalContext};
|
||||
use shared::spectra::{SampledSpectrum, SampledWavelengths};
|
||||
use shared::textures::{GPUFloatScaledTexture, GPUSpectrumScaledTexture};
|
||||
use shared::utils::Transform;
|
||||
use shared::Float;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone, Debug, Device)]
|
||||
#[device(name = "GPUFloatScaledTexture")]
|
||||
pub struct FloatScaledTexture {
|
||||
#[device(upload)]
|
||||
pub tex: Arc<FloatTexture>,
|
||||
#[device(upload)]
|
||||
pub scale: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +48,6 @@ impl FloatScaledTexture {
|
|||
std::mem::swap(&mut tex, &mut scale);
|
||||
}
|
||||
std::mem::swap(&mut tex, &mut scale);
|
||||
// arena.alloc(FloatScaledTexture::new(tex, scale));
|
||||
Ok(FloatTexture::Scaled(FloatScaledTexture::new(
|
||||
tex.clone(),
|
||||
scale.clone(),
|
||||
|
|
@ -62,9 +65,12 @@ impl FloatTextureTrait for FloatScaledTexture {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Device)]
|
||||
#[device(name = "GPUSpectrumScaledTexture")]
|
||||
pub struct SpectrumScaledTexture {
|
||||
#[device(upload)]
|
||||
pub tex: Arc<SpectrumTexture>,
|
||||
#[device(upload)]
|
||||
pub scale: Arc<FloatTexture>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use crate::utils::backend::GpuAllocator;
|
||||
use shared::core::shape::Shape;
|
||||
use shared::core::material::Material;
|
||||
use shared::core::spectrum::Spectrum;
|
||||
use crate::utils::mipmap::MIPMap;
|
||||
use parking_lot::Mutex;
|
||||
use shared::Ptr;
|
||||
use std::alloc::Layout;
|
||||
|
|
@ -173,378 +171,3 @@ impl<A: GpuAllocator + Default> Default for Arena<A> {
|
|||
Self::new(A::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DeviceRepr {
|
||||
/// The `#[repr(C)] Copy` device-side struct.
|
||||
type Target: Copy;
|
||||
|
||||
/// 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;
|
||||
|
||||
/// 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> {
|
||||
let value = self.upload_value(arena);
|
||||
arena.alloc(value)
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
None => Ptr::null(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ impl GpuAllocator for SystemAllocator {
|
|||
if layout.size() == 0 {
|
||||
return layout.align() as *mut u8;
|
||||
}
|
||||
std::alloc::alloc(layout)
|
||||
unsafe { std::alloc::alloc(layout) }
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
if layout.size() > 0 {
|
||||
std::alloc::dealloc(ptr, layout);
|
||||
unsafe { std::alloc::dealloc(ptr, layout) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use crate::{Arena, DeviceRepr};
|
||||
use parking_lot::Mutex;
|
||||
use shared::core::geometry::{Bounds2i, Point2i};
|
||||
use shared::utils::containers::DeviceArray2D;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct InternCache<T> {
|
||||
|
|
@ -32,63 +32,3 @@ where
|
|||
new_item
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(DeviceRepr)]
|
||||
pub struct Array2D<T> {
|
||||
pub device: DeviceArray2D<T>,
|
||||
pub values: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> Deref for Array2D<T> {
|
||||
type Target = DeviceArray2D<T>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Array2D<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Array2D<T> {
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
&self.values
|
||||
}
|
||||
|
||||
fn from_vec(extent: Bounds2i, mut values: Vec<T>) -> Self {
|
||||
let width = extent.p_max.x() - extent.p_min.x();
|
||||
let device = DeviceArray2D {
|
||||
extent,
|
||||
values: values.as_mut_ptr().into(),
|
||||
stride: width,
|
||||
};
|
||||
Self { device, values }
|
||||
}
|
||||
|
||||
pub fn device(&self) -> &DeviceArray2D<T> {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Clone> Array2D<T> {
|
||||
pub fn new(extent: Bounds2i) -> Self {
|
||||
let n = extent.area() as usize;
|
||||
let storage = vec![T::default(); n];
|
||||
Self::from_vec(extent, storage)
|
||||
}
|
||||
|
||||
pub fn new_dims(nx: i32, ny: i32) -> Self {
|
||||
let extent = Bounds2i::from_points(Point2i::new(0, 0), Point2i::new(nx, ny));
|
||||
Self::new(extent)
|
||||
}
|
||||
|
||||
pub fn new_filled(nx: i32, ny: i32, val: T) -> Self {
|
||||
let extent = Bounds2i::from_points(Point2i::new(0, 0), Point2i::new(nx, ny));
|
||||
let n = (nx * ny) as usize;
|
||||
let storage = vec![val; n];
|
||||
Self::from_vec(extent, storage)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
use half::f16;
|
||||
use shared::Float;
|
||||
use shared::utils::hash::hash_buffer;
|
||||
use shared::utils::math::{
|
||||
DeviceDigitPermutation, PRIMES, f16_to_f32_software, permutation_element,
|
||||
};
|
||||
use shared::utils::math::{permutation_element, DeviceDigitPermutation, PRIMES};
|
||||
use shared::Float;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn f16_to_f32(bits: u16) -> f32 {
|
||||
|
|
|
|||
517
src/utils/mod.rs
517
src/utils/mod.rs
|
|
@ -11,14 +11,16 @@ pub mod parameters;
|
|||
pub mod parser;
|
||||
pub mod sampling;
|
||||
pub mod strings;
|
||||
pub mod upload;
|
||||
|
||||
pub use arena::DeviceRepr;
|
||||
pub use device_derive::Device;
|
||||
pub use error::FileLoc;
|
||||
pub use file::{read_float_file, resolve_filename};
|
||||
pub use parameters::{
|
||||
ParameterDictionary, ParsedParameter, ParsedParameterVector, TextureParameterDictionary,
|
||||
};
|
||||
pub use strings::*;
|
||||
pub use upload::DeviceRepr;
|
||||
|
||||
#[cfg(feature = "vulkan")]
|
||||
pub type Arena = arena::Arena<backend::vulkan::VulkanAllocator>;
|
||||
|
|
@ -28,516 +30,3 @@ 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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,78 +1,35 @@
|
|||
use crate::core::image::Image;
|
||||
use crate::utils::arena::Arena;
|
||||
use crate::utils::backend::GpuAllocator;
|
||||
use crate::utils::containers::Array2D;
|
||||
use crate::{Arena, DeviceRepr};
|
||||
use shared::core::geometry::{Bounds2f, Point2i, Vector2f, Vector2i};
|
||||
use shared::utils::sampling::{
|
||||
AliasTable, Bin, DevicePiecewiseConstant1D, DevicePiecewiseConstant2D, DeviceSummedAreaTable,
|
||||
DeviceWindowedPiecewiseConstant2D, PiecewiseLinear2D,
|
||||
AliasTable, Bin, PiecewiseConstant1D, PiecewiseConstant2D,
|
||||
SummedAreaTable, WindowedPiecewiseConstant2D, PiecewiseLinear2D,
|
||||
};
|
||||
use shared::utils::{gpu_array_from_fn, Ptr};
|
||||
use shared::Float;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PiecewiseConstant1D {
|
||||
pub struct HostPiecewiseConstant1D {
|
||||
func: Vec<Float>,
|
||||
cdf: Vec<Float>,
|
||||
pub min: Float,
|
||||
pub max: Float,
|
||||
}
|
||||
|
||||
impl PiecewiseConstant1D {
|
||||
pub fn new(f: &[Float]) -> Self {
|
||||
Self::new_with_bounds(f.to_vec(), 0.0, 1.0)
|
||||
}
|
||||
|
||||
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| f(min + (i as Float + 0.5) * delta))
|
||||
.collect();
|
||||
Self::new_with_bounds(values, min, max)
|
||||
}
|
||||
|
||||
pub fn new_with_bounds(f: Vec<Float>, min: Float, max: Float) -> Self {
|
||||
let n = f.len();
|
||||
let mut cdf = Vec::with_capacity(n + 1);
|
||||
cdf.push(0.0);
|
||||
|
||||
let delta = (max - min) / n as Float;
|
||||
for i in 0..n {
|
||||
cdf.push(cdf[i] + f[i] * delta);
|
||||
}
|
||||
|
||||
let func_integral = cdf[n];
|
||||
if func_integral > 0.0 {
|
||||
for c in &mut cdf {
|
||||
*c /= func_integral;
|
||||
}
|
||||
}
|
||||
|
||||
Self { func: f, cdf, min, max }
|
||||
}
|
||||
|
||||
// Accessors
|
||||
impl HostPiecewiseConstant1D {
|
||||
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 {
|
||||
// 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
|
||||
}
|
||||
|
||||
/// 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];
|
||||
|
|
@ -81,6 +38,7 @@ impl PiecewiseConstant1D {
|
|||
} else {
|
||||
0.0
|
||||
};
|
||||
let n = self.func.len();
|
||||
let delta = (self.max - self.min) / n as Float;
|
||||
let x = self.min + (offset as Float + du) * delta;
|
||||
let func_integral = self.integral();
|
||||
|
|
@ -110,41 +68,21 @@ impl PiecewiseConstant1D {
|
|||
}
|
||||
}
|
||||
|
||||
#[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(Clone, Debug, DeviceRepr)]
|
||||
#[device(name = "DevicePiecewiseConstant2D")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PiecewiseConstant2D {
|
||||
pub conditionals: Vec<PiecewiseConstant1D>,
|
||||
#[device(flatten)]
|
||||
pub marginal: PiecewiseConstant1D,
|
||||
pub n_u: u32,
|
||||
pub n_v: u32,
|
||||
pub n_u: usize,
|
||||
pub n_v: usize,
|
||||
}
|
||||
|
||||
|
||||
impl PiecewiseConstant2D {
|
||||
pub fn new(data: &Array2D<Float>) -> Self {
|
||||
Self::new_with_bounds(data, Bounds2f::unit())
|
||||
}
|
||||
|
||||
pub fn new_with_bounds(data: &Array2D<Float>, domain: Bounds2f) -> Self {
|
||||
Self::from_slice(
|
||||
data.as_slice(),
|
||||
data.x_size(),
|
||||
data.y_size(),
|
||||
domain,
|
||||
)
|
||||
Self::from_slice(data.as_slice(), data.x_size(), data.y_size(), domain)
|
||||
}
|
||||
|
||||
pub fn from_slice(data: &[Float], n_u: usize, n_v: usize, domain: Bounds2f) -> Self {
|
||||
|
|
@ -155,20 +93,14 @@ impl PiecewiseConstant2D {
|
|||
|
||||
for v in 0..n_v {
|
||||
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(),
|
||||
);
|
||||
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());
|
||||
|
||||
Self { conditionals, marginal, n_u, n_v }
|
||||
}
|
||||
|
|
@ -181,7 +113,9 @@ impl PiecewiseConstant2D {
|
|||
let mut data = Vec::with_capacity(n_u * n_v);
|
||||
for v in 0..n_v {
|
||||
for u in 0..n_u {
|
||||
data.push(image.get_channels(Point2i::new(u as i32, v as i32)).average());
|
||||
data.push(
|
||||
image.get_channels(Point2i::new(u as i32, v as i32)).average(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,179 +127,22 @@ impl PiecewiseConstant2D {
|
|||
}
|
||||
}
|
||||
|
||||
struct PiecewiseLinear2DStorage<const N: usize> {
|
||||
data: Vec<Float>,
|
||||
marginal_cdf: Vec<Float>,
|
||||
conditional_cdf: Vec<Float>,
|
||||
param_values: [Vec<Float>; N],
|
||||
}
|
||||
impl DeviceRepr for PiecewiseConstant2D {
|
||||
type Target = DevicePiecewiseConstant2D;
|
||||
|
||||
pub struct PiecewiseLinear2DHost<const N: usize> {
|
||||
size: Vector2i,
|
||||
inv_patch_size: Vector2f,
|
||||
param_size: [u32; N],
|
||||
param_strides: [u32; N],
|
||||
storage: Arc<PiecewiseLinear2DStorage<N>>,
|
||||
}
|
||||
fn upload_value(&self, arena: &Arena) -> DevicePiecewiseConstant2D {
|
||||
let uploaded: Vec<DevicePiecewiseConstant1D> = self
|
||||
.conditionals
|
||||
.iter()
|
||||
.map(|c| c.upload_value(arena))
|
||||
.collect();
|
||||
let (conditionals_ptr, _) = arena.alloc_slice(&uploaded);
|
||||
|
||||
|
||||
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::new: build_cdf implies normalize=true");
|
||||
}
|
||||
|
||||
let size = Vector2i::new(x_size, y_size);
|
||||
let inv_patch_size = Vector2f::new(1. / (x_size - 1) as Float, 1. / (y_size - 1) as Float);
|
||||
|
||||
let mut param_size = [0u32; N];
|
||||
let mut param_strides = [0u32; N];
|
||||
let param_values = gpu_array_from_fn(|i| param_values[i].to_vec());
|
||||
|
||||
let mut slices: u32 = 1;
|
||||
for i in (0..N).rev() {
|
||||
if param_res[i] < 1 {
|
||||
panic!("PiecewiseLinear2D::new: 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.;
|
||||
|
||||
// Construct conditional CDF
|
||||
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;
|
||||
// Construct marginal CDF
|
||||
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;
|
||||
}
|
||||
}
|
||||
// Construct marginal CDF
|
||||
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 view = PiecewiseLinear2D {
|
||||
size,
|
||||
inv_patch_size,
|
||||
param_size,
|
||||
param_strides,
|
||||
param_values: param_values.each_ref().map(|x| x.as_ptr().into()),
|
||||
data: Ptr::null(),
|
||||
marginal_cdf: marginal_cdf.as_ptr().into(),
|
||||
conditional_cdf: conditional_cdf.as_ptr().into(),
|
||||
};
|
||||
|
||||
let storage = Arc::new(PiecewiseLinear2DStorage {
|
||||
data: new_data,
|
||||
marginal_cdf,
|
||||
conditional_cdf,
|
||||
param_values,
|
||||
});
|
||||
|
||||
let mut final_view = view;
|
||||
final_view.data = storage.data.as_ptr().into();
|
||||
final_view.marginal_cdf = storage.marginal_cdf.as_ptr().into();
|
||||
final_view.conditional_cdf = storage.conditional_cdf.as_ptr().into();
|
||||
for i in 0..N {
|
||||
final_view.param_values[i] = storage.param_values[i].as_ptr().into();
|
||||
}
|
||||
|
||||
Self {
|
||||
view: final_view,
|
||||
_storage: 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,
|
||||
DevicePiecewiseConstant2D {
|
||||
conditionals: conditionals_ptr,
|
||||
marginal: self.marginal.upload_value(arena),
|
||||
n_u: self.n_u as u32,
|
||||
n_v: self.n_v as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -411,10 +188,8 @@ impl AliasTableHost {
|
|||
while !under.is_empty() && !over.is_empty() {
|
||||
let un = under.pop().unwrap();
|
||||
let ov = over.pop().unwrap();
|
||||
|
||||
bins[un.index].q = un.p_hat as Float;
|
||||
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 });
|
||||
|
|
@ -442,22 +217,17 @@ impl AliasTableHost {
|
|||
impl DeviceRepr for AliasTableHost {
|
||||
type Target = AliasTable;
|
||||
|
||||
fn upload_value<A: GpuAllocator>(&self, arena: &Arena<A>) -> AliasTable {
|
||||
fn upload_value(&self, arena: &Arena) -> 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,
|
||||
}
|
||||
AliasTable { bins: bins_ptr, size: self.bins.len() as u32 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, DeviceRepr)]
|
||||
#[device(name = "DeviceSummedAreaTable")]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SummedAreaTable {
|
||||
#[device(flatten)]
|
||||
sum: Array2D<f64>,
|
||||
}
|
||||
|
||||
|
|
@ -488,12 +258,19 @@ impl SummedAreaTable {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, DeviceRepr)]
|
||||
#[device(name = "DeviceWindowedPiecewiseConstant2D")]
|
||||
impl DeviceRepr for SummedAreaTable {
|
||||
type Target = DeviceSummedAreaTable;
|
||||
|
||||
fn upload_value(&self, arena: &Arena) -> DeviceSummedAreaTable {
|
||||
DeviceSummedAreaTable {
|
||||
sum: self.sum.upload_value(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WindowedPiecewiseConstant2D {
|
||||
#[device(flatten)]
|
||||
sat: SummedAreaTable,
|
||||
#[device(flatten)]
|
||||
func: Array2D<Float>,
|
||||
}
|
||||
|
||||
|
|
@ -504,6 +281,17 @@ impl WindowedPiecewiseConstant2D {
|
|||
}
|
||||
}
|
||||
|
||||
impl DeviceRepr for WindowedPiecewiseConstant2D {
|
||||
type Target = DeviceWindowedPiecewiseConstant2D;
|
||||
|
||||
fn upload_value(&self, arena: &Arena) -> DeviceWindowedPiecewiseConstant2D {
|
||||
DeviceWindowedPiecewiseConstant2D {
|
||||
sat: self.sat.upload_value(arena),
|
||||
func: self.func.upload_value(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PiecewiseLinear2DStorage<const N: usize> {
|
||||
data: Vec<Float>,
|
||||
marginal_cdf: Vec<Float>,
|
||||
|
|
@ -534,10 +322,8 @@ impl<const N: usize> PiecewiseLinear2DHost<N> {
|
|||
}
|
||||
|
||||
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 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];
|
||||
|
|
@ -600,18 +386,16 @@ impl<const N: usize> PiecewiseLinear2DHost<N> {
|
|||
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]);
|
||||
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];
|
||||
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;
|
||||
}
|
||||
|
|
@ -626,20 +410,14 @@ impl<const N: usize> PiecewiseLinear2DHost<N> {
|
|||
param_values: owned_param_values,
|
||||
});
|
||||
|
||||
Self {
|
||||
size,
|
||||
inv_patch_size,
|
||||
param_size,
|
||||
param_strides,
|
||||
storage,
|
||||
}
|
||||
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> {
|
||||
fn upload_value(&self, arena: &Arena) -> PiecewiseLinear2D<N> {
|
||||
let s = &self.storage;
|
||||
|
||||
let (data_ptr, _) = arena.alloc_slice(&s.data);
|
||||
|
|
|
|||
Loading…
Reference in a new issue