diff --git a/Cargo.toml b/Cargo.toml index da79e13..e47ec79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/crates/ptex-filter/Cargo.toml b/crates/ptex-filter/Cargo.toml deleted file mode 100644 index f0a52e7..0000000 --- a/crates/ptex-filter/Cargo.toml +++ /dev/null @@ -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" diff --git a/crates/ptex-filter/build.rs b/crates/ptex-filter/build.rs deleted file mode 100644 index 0b6774c..0000000 --- a/crates/ptex-filter/build.rs +++ /dev/null @@ -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"); -} diff --git a/crates/ptex-filter/cpp/ptex_filter_wrapper.cpp b/crates/ptex-filter/cpp/ptex_filter_wrapper.cpp deleted file mode 100644 index b2a119c..0000000 --- a/crates/ptex-filter/cpp/ptex_filter_wrapper.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "ptex_filter_wrapper.h" -#include - -extern "C" { - -PtexFilterHandle ptex_filter_create(PtexTextureHandle texture, const PtexFilterOptions* opts) { - Ptex::PtexTexture* tex = static_cast(texture); - if (!tex || !opts) return nullptr; - - Ptex::PtexFilter::Options ptex_opts; - ptex_opts.filter = static_cast(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(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(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(texture); - if (tex) { - tex->release(); - } -} - -int32_t ptex_texture_num_channels(PtexTextureHandle texture) { - Ptex::PtexTexture* tex = static_cast(texture); - return tex ? tex->numChannels() : 0; -} - -} diff --git a/crates/ptex-filter/cpp/ptex_filter_wrapper.h b/crates/ptex-filter/cpp/ptex_filter_wrapper.h deleted file mode 100644 index b999325..0000000 --- a/crates/ptex-filter/cpp/ptex_filter_wrapper.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -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 diff --git a/crates/ptex-filter/src/ffi.rs b/crates/ptex-filter/src/ffi.rs deleted file mode 100644 index be7b391..0000000 --- a/crates/ptex-filter/src/ffi.rs +++ /dev/null @@ -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; -} diff --git a/crates/ptex-filter/src/lib.rs b/crates/ptex-filter/src/lib.rs deleted file mode 100644 index 38dbc27..0000000 --- a/crates/ptex-filter/src/lib.rs +++ /dev/null @@ -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, - _marker: PhantomData<*mut ()>, -} - -impl PtexFilter { - /// Creates a new Ptex filter pointer - /// - /// # Safety - pub unsafe fn new(texture_ptr: *mut c_void, opts: &PtexFilterOptions) -> Option { - 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()); - } - } -} diff --git a/shared/src/cameras/realistic.rs b/shared/src/cameras/realistic.rs index 96440ce..0f513da 100644 --- a/shared/src/cameras/realistic.rs +++ b/shared/src/cameras/realistic.rs @@ -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, - pub element_interfaces: Ptr, + pub aperture_image: Ptr, + pub element_interfaces: GVec, pub n_elements: usize, pub physical_extent: Bounds2f, pub exit_pupil_bounds: [Bounds2f; EXIT_PUPIL_SAMPLES], diff --git a/shared/src/core/aggregates.rs b/shared/src/core/aggregates.rs index 94f3154..885b00d 100644 --- a/shared/src/core/aggregates.rs +++ b/shared/src/core/aggregates.rs @@ -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, + pub primitives: GVec, pub primitive_count: u32, - pub nodes: Ptr, + pub nodes: GVec, 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) -> Option { - 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) -> bool { - if self.nodes.is_null() || self.node_count == 0 { + if self.nodes.is_empty() || self.node_count == 0 { return false; } diff --git a/shared/src/core/film.rs b/shared/src/core/film.rs index 1952437..906ab66 100644 --- a/shared/src/core/film.rs +++ b/shared/src/core/film.rs @@ -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, - pub pixels: DeviceArray2D, + pub pixels: Array2D, } #[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, + pub pixels: Array2D, 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, + pub pixels: Array2D, pub output_rgbf_from_sensor_rgb: SquareMatrix, 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, 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( refl: &Spectrum, illum: &Spectrum, @@ -492,7 +491,7 @@ pub struct FilmBase { pub pixel_bounds: Bounds2i, pub filter: Filter, pub diagonal: Float, - pub sensor: Ptr, + pub sensor: Ptr, } #[repr(C)] diff --git a/shared/src/core/filter.rs b/shared/src/core/filter.rs index 740c036..b783e6a 100644 --- a/shared/src/core/filter.rs +++ b/shared/src/core/filter.rs @@ -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, + pub distrib: PiecewiseConstant2D, + pub f: Array2D, } -impl DeviceFilterSampler { +impl FilterSampler { + pub fn new(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 } + } + 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)] diff --git a/shared/src/core/image.rs b/shared/src/core/image.rs index a9025ee..ae1f3c7 100644 --- a/shared/src/core/image.rs +++ b/shared/src/core/image.rs @@ -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, + pixels: GVec, format: PixelFormat, } impl Pixels { - pub fn new_u8(ptr: Ptr) -> Self { + pub fn new(data: GVec, 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) -> 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) -> 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) + } + } + + 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) + } + } } -#[repr(C)] -#[derive(Clone, Copy, Debug)] -pub struct ImageBase { +#[derive(Clone, Debug)] +pub struct Image { pub format: PixelFormat, pub encoding: ColorEncoding, pub resolution: Point2i, pub n_channels: i32, + pub pixels: Pixels, } -impl ImageBase { +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 + } + 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 } -} -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct DeviceImage { - pub base: ImageBase, - pub pixels: Pixels, -} - -impl DeviceImage { - pub fn base(&self) -> ImageBase { - self.base + pub fn get_channel(&self, p: Point2i, c: i32) -> Float { + self.get_channel_with_wrap(p, c, WrapMode::Clamp.into()) } - pub fn resolution(&self) -> Point2i { - self.base.resolution + 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) } } - pub fn is_valid(&self) -> bool { - self.resolution().x() > 0 && self.resolution().y() > 0 + pub fn get_channels_array(&self, p: Point2i) -> [Float; N] { + self.get_channels_array_with_wrap(p, WrapMode::Clamp.into()) } - pub fn format(&self) -> PixelFormat { - self.base().format + pub fn get_channels_array_with_wrap( + &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 n_channels(&self) -> i32 { - self.base().n_channels + 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 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 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 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 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) + 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; + } } - PixelFormat::F16 => { - let raw_val = self.pixels.read_f16(offset); - f16_to_f32_software(raw_val) - } - PixelFormat::F32 => self.pixels.read_f32(offset), } } + false } - 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()) + 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; + } + } + } + } + false } } diff --git a/shared/src/core/interaction.rs b/shared/src/core/interaction.rs index 9b70526..13fef6c 100644 --- a/shared/src/core/interaction.rs +++ b/shared/src/core/interaction.rs @@ -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, - normal_image: Ptr, + normal_image: Ptr, ) { let ctx = NormalBumpEvalContext::from(&*self); let (dpdu, dpdv) = if !displacement.is_null() { diff --git a/shared/src/core/light.rs b/shared/src/core/light.rs index a2a1762..c71b6c0 100644 --- a/shared/src/core/light.rs +++ b/shared/src/core/light.rs @@ -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; diff --git a/shared/src/core/material.rs b/shared/src/core/material.rs index 011d6bb..99cec02 100644 --- a/shared/src/core/material.rs +++ b/shared/src/core/material.rs @@ -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; 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; fn has_subsurface_scattering(&self) -> bool; } diff --git a/shared/src/core/pbrt.rs b/shared/src/core/pbrt.rs index b276c5a..d63f343 100644 --- a/shared/src/core/pbrt.rs +++ b/shared/src/core/pbrt.rs @@ -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; diff --git a/shared/src/core/primitive.rs b/shared/src/core/primitive.rs index 72b3476..98df32f 100644 --- a/shared/src/core/primitive.rs +++ b/shared/src/core/primitive.rs @@ -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), } diff --git a/shared/src/core/sampler.rs b/shared/src/core/sampler.rs index 14e077c..760fb6d 100644 --- a/shared/src/core/sampler.rs +++ b/shared/src/core/sampler.rs @@ -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, + pub digit_permutations: GVec, } #[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(), } } } diff --git a/shared/src/filters/boxf.rs b/shared/src/filters/boxf.rs index 413cb8d..bee4080 100644 --- a/shared/src/filters/boxf.rs +++ b/shared/src/filters/boxf.rs @@ -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()), diff --git a/shared/src/filters/gaussian.rs b/shared/src/filters/gaussian.rs index da81189..e10a494 100644 --- a/shared/src/filters/gaussian.rs +++ b/shared/src/filters/gaussian.rs @@ -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) } } diff --git a/shared/src/filters/lanczos.rs b/shared/src/filters/lanczos.rs index a22db3d..d763f17 100644 --- a/shared/src/filters/lanczos.rs +++ b/shared/src/filters/lanczos.rs @@ -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) } } diff --git a/shared/src/filters/mitchell.rs b/shared/src/filters/mitchell.rs index c17ceb0..4f19b4a 100644 --- a/shared/src/filters/mitchell.rs +++ b/shared/src/filters/mitchell.rs @@ -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) } } diff --git a/shared/src/filters/triangle.rs b/shared/src/filters/triangle.rs index b014fce..1572ca5 100644 --- a/shared/src/filters/triangle.rs +++ b/shared/src/filters/triangle.rs @@ -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 } } } diff --git a/shared/src/lib.rs b/shared/src/lib.rs index d36af1a..68d6b8e 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -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}; diff --git a/shared/src/lights/diffuse.rs b/shared/src/lights/diffuse.rs index 6d09aa3..22f4341 100644 --- a/shared/src/lights/diffuse.rs +++ b/shared/src/lights/diffuse.rs @@ -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, pub colorspace: Ptr, pub lemit: Ptr, - pub image: Ptr, + pub image: Ptr, pub area: Float, pub two_sided: bool, pub scale: Float, diff --git a/shared/src/lights/goniometric.rs b/shared/src/lights/goniometric.rs index 30b446d..995a8f3 100644 --- a/shared/src/lights/goniometric.rs +++ b/shared/src/lights/goniometric.rs @@ -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, pub scale: Float, - pub image: Ptr, - pub distrib: Ptr, + pub image: Ptr, + pub distrib: Ptr, } impl GoniometricLight { diff --git a/shared/src/lights/infinite.rs b/shared/src/lights/infinite.rs index 779a159..c46f138 100644 --- a/shared/src/lights/infinite.rs +++ b/shared/src/lights/infinite.rs @@ -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, + pub image: Ptr, pub image_color_space: Ptr, pub distrib: Ptr, pub compensated_distrib: Ptr, @@ -250,12 +250,12 @@ impl LightTrait for ImageInfiniteLight { #[derive(Debug, Copy, Clone)] pub struct PortalInfiniteLight { pub base: LightBase, - pub image: Ptr, + pub image: Ptr, pub image_color_space: Ptr, 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, } diff --git a/shared/src/materials/coated.rs b/shared/src/materials/coated.rs index 6152acb..8b21457 100644 --- a/shared/src/materials/coated.rs +++ b/shared/src/materials/coated.rs @@ -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, + pub normal_map: Ptr, pub displacement: Ptr, pub reflectance: Ptr, pub albedo: Ptr, @@ -42,7 +42,7 @@ impl CoatedDiffuseMaterial { g: Ptr, eta: Ptr, displacement: Ptr, - normal_map: Ptr, + normal_map: Ptr, 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, + normal_map: Ptr, displacement: Ptr, interface_uroughness: Ptr, interface_vroughness: Ptr, @@ -175,7 +175,7 @@ pub struct CoatedConductorMaterial { impl CoatedConductorMaterial { #[allow(clippy::too_many_arguments)] pub fn new( - normal_map: Ptr, + normal_map: Ptr, displacement: Ptr, interface_uroughness: Ptr, interface_vroughness: Ptr, @@ -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) } diff --git a/shared/src/materials/complex.rs b/shared/src/materials/complex.rs index f669d6b..3f909ae 100644 --- a/shared/src/materials/complex.rs +++ b/shared/src/materials/complex.rs @@ -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, - pub normal_map: Ptr, + pub normal_map: Ptr, pub brdf: Ptr, } @@ -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, + pub normal_map: Ptr, pub displacement: Ptr, pub sigma_a: Ptr, pub sigma_s: Ptr, @@ -174,7 +174,7 @@ impl MaterialTrait for SubsurfaceMaterial { todo!() } - fn get_normal_map(&self) -> Option<&DeviceImage> { + fn get_normal_map(&self) -> Option<&Image> { todo!() } diff --git a/shared/src/materials/conductor.rs b/shared/src/materials/conductor.rs index 2353384..26792e3 100644 --- a/shared/src/materials/conductor.rs +++ b/shared/src/materials/conductor.rs @@ -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, pub v_roughness: Ptr, pub remap_roughness: bool, - pub normal_map: Ptr, + pub normal_map: Ptr, } 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!() } diff --git a/shared/src/materials/dielectric.rs b/shared/src/materials/dielectric.rs index 71be09d..5826c75 100644 --- a/shared/src/materials/dielectric.rs +++ b/shared/src/materials/dielectric.rs @@ -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, + pub normal_map: Ptr, pub displacement: Ptr, pub u_roughness: Ptr, pub v_roughness: Ptr, @@ -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, - pub normal_map: Ptr, + pub normal_map: Ptr, pub eta: Ptr, } @@ -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) } diff --git a/shared/src/materials/mix.rs b/shared/src/materials/mix.rs index 424b62d..5e1da28 100644 --- a/shared/src/materials/mix.rs +++ b/shared/src/materials/mix.rs @@ -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 } diff --git a/shared/src/shapes/bilinear.rs b/shared/src/shapes/bilinear.rs index 53eb6b5..a9269de 100644 --- a/shared/src/shapes/bilinear.rs +++ b/shared/src/shapes/bilinear.rs @@ -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, + pub mesh: Ptr, 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 { + fn mesh(&self) -> Ptr { self.mesh } @@ -118,7 +117,7 @@ impl BilinearPatchShape { } #[cfg(not(target_os = "cuda"))] - pub fn new(mesh: Ptr, blp_index: i32) -> Self { + pub fn new(mesh: Ptr, 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() { diff --git a/shared/src/shapes/mesh.rs b/shared/src/shapes/mesh.rs index cb8aae4..280b703 100644 --- a/shared/src/shapes/mesh.rs +++ b/shared/src/shapes/mesh.rs @@ -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}; diff --git a/shared/src/shapes/mod.rs b/shared/src/shapes/mod.rs index 16e369f..00be086 100644 --- a/shared/src/shapes/mod.rs +++ b/shared/src/shapes/mod.rs @@ -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::*; diff --git a/shared/src/shapes/triangle.rs b/shared/src/shapes/triangle.rs index 8a4d543..aa1d33f 100644 --- a/shared/src/shapes/triangle.rs +++ b/shared/src/shapes/triangle.rs @@ -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, + pub mesh: Ptr, 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); - [ - *base_ptr.add(0) as usize, - *base_ptr.add(1) as usize, - *base_ptr.add(2) as usize, - ] - } + let mesh = unsafe { &*self.mesh }; + let base = (self.tri_index as usize) * 3; + [ + 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, 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, tri_index: i32) -> Self { Self { mesh, tri_index } } - pub fn get_mesh(&self) -> Ptr { + pub fn get_mesh(&self) -> Ptr { self.mesh } diff --git a/shared/src/utils/containers.rs b/shared/src/utils/containers.rs index 102e877..723a9f7 100644 --- a/shared/src/utils/containers.rs +++ b/shared/src/utils/containers.rs @@ -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 Interpolatable for T where { } -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct DeviceArray2D { - pub values: *mut T, - pub extent: Bounds2i, - pub stride: i32, +#[derive(Debug, Clone)] +pub struct Array2D { + extent: Bounds2i, + values: GVec, } -unsafe impl Send for DeviceArray2D {} -unsafe impl Sync for DeviceArray2D {} - -impl DeviceArray2D { - #[inline] - pub fn x_size(&self) -> u32 { - (self.extent.p_max.x() - self.extent.p_min.x()) as u32 +impl Array2D { + 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 Index for DeviceArray2D { +unsafe impl Send for Array2D {} +unsafe impl Sync for Array2D {} + +impl Array2D { + 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 Index<(i32, i32)> for Array2D { 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 IndexMut for DeviceArray2D { - fn index_mut(&mut self, p: Point2i) -> &mut Self::Output { - self.get_mut(p) - } -} - -impl Index<(i32, i32)> for DeviceArray2D { - type Output = T; - fn index(&self, (x, y): (i32, i32)) -> &Self::Output { - &self[Point2i::new(x, y)] - } -} - -impl IndexMut<(i32, i32)> for DeviceArray2D { - fn index_mut(&mut self, (x, y): (i32, i32)) -> &mut Self::Output { - &mut self[Point2i::new(x, y)] +impl IndexMut<(i32, i32)> for Array2D { + 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 { - pub values: Ptr, + pub values: GVec, pub values_len: u32, pub nx: i32, pub ny: i32, @@ -127,11 +107,10 @@ unsafe impl Sync for SampledGrid {} unsafe impl Send for SampledGrid {} impl SampledGrid { - #[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 SampledGrid { pub fn empty() -> Self { Self { - values: Ptr::null(), + values: gvec(), values_len: 0, nx: 0, ny: 0, diff --git a/shared/src/utils/math.rs b/shared/src/utils/math.rs index 8685554..426ec27 100644 --- a/shared/src/utils/math.rs +++ b/shared/src/utils/math.rs @@ -137,7 +137,12 @@ pub fn windowed_sinc(x: Float, radius: Float, tau: Float) -> Float { if x.abs() > radius { return 0.; } - sinc(x) * sinc(x / tau) + + if x < 1e-5 { + 1.0 + } else { + sinc(x) * sinc(x / tau) + } } #[inline] diff --git a/shared/src/utils/mesh.rs b/shared/src/utils/mesh.rs deleted file mode 100644 index e091c93..0000000 --- a/shared/src/utils/mesh.rs +++ /dev/null @@ -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, - pub n: Ptr, - pub s: Ptr, - pub uv: Ptr, - pub vertex_indices: Ptr, - pub face_indices: Ptr, - 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, - pub p: Ptr, - pub n: Ptr, - pub uv: Ptr, - pub vertex_indices: Ptr, - 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 {} diff --git a/shared/src/utils/mod.rs b/shared/src/utils/mod.rs index a18ea95..b160d1d 100644 --- a/shared/src/utils/mod.rs +++ b/shared/src/utils/mod.rs @@ -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(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 { - 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 { - 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 = 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(&self, arena: &Arena) -> Self::Target { - #(#upload_stmts)* - #constructor - } - } - }) -} - -// Enum derivation -fn derive_enum(input: DeriveInput) -> syn::Result { - 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(&self, arena: &Arena) -> Self::Target { - match self { - #(#match_arms,)* - } - } - } - }) -} - -fn get_variant_inner_type(variant: &Variant) -> syn::Result { - 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, - variant_type: Option, -} - -fn parse_variant_attrs(attrs: &[Attribute]) -> syn::Result { - 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, - copy_upload: bool, - flatten: bool, - upload: bool, - spread: Option, -} - -fn parse_field_attrs(attrs: &[Attribute]) -> syn::Result { - 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 { - 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 { - 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 -} diff --git a/shared/src/utils/sampling.rs b/shared/src/utils/sampling.rs index 5a8bd2a..db63650 100644 --- a/shared/src/utils/sampling.rs +++ b/shared/src/utils/sampling.rs @@ -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(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, - pub cdf: Ptr, +#[derive(Debug, Clone)] +pub struct PiecewiseConstant1D { + pub func: GVec, + pub cdf: GVec, 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, min: Float, max: Float, n: usize) -> Self + where + F: Fn(Float) -> Float, + { + let delta = (max - min) / n as Float; + let values: Vec = (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, // Array of n_v conditionals - pub marginal: DevicePiecewiseConstant1D, +#[derive(Debug, Clone)] +pub struct PiecewiseConstant2D { + pub conditionals: GVec, + 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) -> 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, 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, + pub sum: Array2D, } impl DeviceSummedAreaTable { @@ -877,7 +926,7 @@ impl DeviceSummedAreaTable { #[derive(Debug, Copy, Clone)] pub struct DeviceWindowedPiecewiseConstant2D { pub sat: DeviceSummedAreaTable, - pub func: DeviceArray2D, + pub func: Array2D, } impl DeviceWindowedPiecewiseConstant2D { @@ -1058,10 +1107,10 @@ pub struct PiecewiseLinear2D { pub inv_patch_size: Vector2f, pub param_size: [u32; N], pub param_strides: [u32; N], - pub param_values: [Ptr; N], - pub data: Ptr, - pub marginal_cdf: Ptr, - pub conditional_cdf: Ptr, + pub param_values: [GVec; N], + pub data: GVec, + pub marginal_cdf: GVec, + pub conditional_cdf: GVec, } impl PiecewiseLinear2D { diff --git a/src/core/aggregates.rs b/src/core/aggregates.rs index 3fda232..fc03238 100644 --- a/src/core/aggregates.rs +++ b/src/core/aggregates.rs @@ -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 BVHAggregate

{ pub fn build_hlbvh( bvh_primitives: &[BVHPrimitiveInfo], total_nodes: &AtomicUsize, - original_primitives: &[P], + _original_primitives: &[P], max_prims_in_node: usize, ) -> Box { let bounds = bvh_primitives diff --git a/src/core/camera.rs b/src/core/camera.rs index 8985d80..7263976 100644 --- a/src/core/camera.rs +++ b/src/core/camera.rs @@ -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); diff --git a/src/core/film.rs b/src/core/film.rs index ce291da..b19ea73 100644 --- a/src/core/film.rs +++ b/src/core/film.rs @@ -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 } } diff --git a/src/core/filter.rs b/src/core/filter.rs index b8874d2..65ed690 100644 --- a/src/core/filter.rs +++ b/src/core/filter.rs @@ -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; + fn create( + name: &str, + params: &ParameterDictionary, + loc: &FileLoc, + arena: &Arena, + ) -> Result; } impl FilterFactory for Filter { - fn create(name: &str, params: &ParameterDictionary, loc: &FileLoc) -> Result { + fn create( + name: &str, + params: &ParameterDictionary, + loc: &FileLoc, + arena: &Arena, + ) -> Result { 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, -} - -impl FilterSampler { - pub fn new(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(&self, arena: &Arena) -> DeviceFilterSampler { - DeviceFilterSampler { - domain: self.domain, - distrib: self.distrib.upload_value(arena), - f: self.f.upload_value(arena), - } - } -} diff --git a/src/core/image/metadata.rs b/src/core/image/metadata.rs index 8fad636..ea926ca 100644 --- a/src/core/image/metadata.rs +++ b/src/core/image/metadata.rs @@ -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, diff --git a/src/core/image/mod.rs b/src/core/image/mod.rs index 323f4db..5a9b509 100644 --- a/src/core/image/mod.rs +++ b/src/core/image/mod.rs @@ -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), - F16(Vec), - F32(Vec), -} - -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, - 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], 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, + pub fn from_f32( + data: &[f32], resolution: Point2i, channel_names: &[impl AsRef], - encoding: ColorEncoding, ) -> Self { - Self::from_storage(PixelStorage::U8(data), resolution, channel_names, encoding) + 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: Vec, + data: &[u16], resolution: Point2i, channel_names: &[impl AsRef], ) -> Self { - Self::from_storage(PixelStorage::F16(data), resolution, channel_names, LINEAR) - } - - pub fn from_f32( - data: Vec, - resolution: Point2i, - channel_names: &[impl AsRef], - ) -> Self { - Self::from_storage(PixelStorage::F32(data), resolution, channel_names, LINEAR) + 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], - encoding: Arc, + 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 + std::fmt::Display], + requested: &[impl AsRef + std::fmt::Display], ) -> Result { - 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 = desc - .offset - .iter() - .map(|&i| self.channel_names[i].clone()) - .collect(); - - let res = self.resolution(); + let new_names: Vec = 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) + // 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] = unsafe { + self.inner.pixels.read(src_offset + in_c, &self.inner.encoding) + }; } - 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) => { - let mut dst = vec![0.0f32; 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::F32(dst) - } - }; + } - Self::from_storage(new_storage, res, &new_names, self.encoding()) + Self::from_f32(&dst, res, &new_names) } pub fn get_sampling_distribution(&self, dxd_a: F, domain: Bounds2f) -> Array2D 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 = 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 { - 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) { - let res = self.resolution(); + ) -> (ImageChannelValues, Option) { + let res = self.inner.resolution(); + assert_eq!(res, ref_img.inner.resolution()); - let mut sum_se: Vec = 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 = 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 } } diff --git a/src/core/image/ops.rs b/src/core/image/ops.rs index 96fade6..c50149b 100644 --- a/src/core/image/ops.rs +++ b/src/core/image/ops.rs @@ -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 { + pub fn generate_pyramid(base: HostImage, _wrap: WrapMode) -> Vec { 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( fn copy_rect_out_kernel( src: &[T], - image: &Image, + image: &HostImage, extent: Bounds2i, buf: &mut [Float], wrap: WrapMode2D, diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index e6bb424..e7bd4c0 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -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, ) -> 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.)?; diff --git a/src/core/texture.rs b/src/core/texture.rs index 40e709a..56d7eca 100644 --- a/src/core/texture.rs +++ b/src/core/texture.rs @@ -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 { diff --git a/src/films/gbuffer.rs b/src/films/gbuffer.rs index dd74009..a3584d1 100644 --- a/src/films/gbuffer.rs +++ b/src/films/gbuffer.rs @@ -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, diff --git a/src/films/spectral.rs b/src/films/spectral.rs index ea30234..80b8372 100644 --- a/src/films/spectral.rs +++ b/src/films/spectral.rs @@ -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, + pixels: Array2D, bucket_sums: Vec, weight_sums: Vec, bucket_splats: Vec, @@ -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(), diff --git a/src/filters/boxf.rs b/src/filters/boxf.rs deleted file mode 100644 index 2af62d1..0000000 --- a/src/filters/boxf.rs +++ /dev/null @@ -1 +0,0 @@ -pub trait BoxFilterCreator {} diff --git a/src/filters/gaussian.rs b/src/filters/gaussian.rs deleted file mode 100644 index 0267a54..0000000 --- a/src/filters/gaussian.rs +++ /dev/null @@ -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 } - } -} diff --git a/src/filters/lanczos.rs b/src/filters/lanczos.rs deleted file mode 100644 index 4497bf0..0000000 --- a/src/filters/lanczos.rs +++ /dev/null @@ -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) - } -} diff --git a/src/filters/mitchell.rs b/src/filters/mitchell.rs deleted file mode 100644 index a8b2626..0000000 --- a/src/filters/mitchell.rs +++ /dev/null @@ -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 - } -} diff --git a/src/filters/mod.rs b/src/filters/mod.rs deleted file mode 100644 index e4abe99..0000000 --- a/src/filters/mod.rs +++ /dev/null @@ -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::*; diff --git a/src/filters/triangle.rs b/src/filters/triangle.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/filters/triangle.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/lib.rs b/src/lib.rs index 8c0cf49..c84be56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; diff --git a/src/lights/diffuse.rs b/src/lights/diffuse.rs index 220b38b..1edd25b 100644 --- a/src/lights/diffuse.rs +++ b/src/lights/diffuse.rs @@ -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)) diff --git a/src/lights/goniometric.rs b/src/lights/goniometric.rs index a0dd2a8..cec54f3 100644 --- a/src/lights/goniometric.rs +++ b/src/lights/goniometric.rs @@ -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) => { diff --git a/src/lights/infinite.rs b/src/lights/infinite.rs index 2b6d057..020e6c8 100644 --- a/src/lights/infinite.rs +++ b/src/lights/infinite.rs @@ -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, + image: Ptr, image_color_space: Ptr, - distrib: Ptr, - compensated_distrib: Ptr, + distrib: Ptr, + compensated_distrib: Ptr, ) -> Self; } @@ -37,10 +36,10 @@ impl CreateImageInfiniteLight for ImageInfiniteLight { fn new( render_from_light: Transform, scale: Float, - image: Ptr, + image: Ptr, image_color_space: Ptr, - distrib: Ptr, - compensated_distrib: Ptr, + distrib: Ptr, + compensated_distrib: Ptr, ) -> Self { let base = LightBase::new( LightType::Infinite, @@ -64,11 +63,11 @@ pub trait CreatePortalInfiniteLight { fn new( render_from_light: Transform, scale: Float, - image: Ptr, + image: Ptr, image_color_space: Ptr, portal: [Point3f; 4], portal_frame: Frame, - distribution: Ptr, + distribution: Ptr, ) -> Self; } @@ -76,11 +75,11 @@ impl CreatePortalInfiniteLight for PortalInfiniteLight { fn new( render_from_light: Transform, scale: Float, - image: Ptr, + image: Ptr, image_color_space: Ptr, portal: [Point3f; 4], portal_frame: Frame, - distribution: Ptr, + distribution: Ptr, ) -> Self { let base = LightBase::new( LightType::Infinite, diff --git a/src/lights/projection.rs b/src/lights/projection.rs index 522983f..650d781 100644 --- a/src/lights/projection.rs +++ b/src/lights/projection.rs @@ -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) => { diff --git a/src/materials/coated.rs b/src/materials/coated.rs index 5db529f..c21511c 100644 --- a/src/materials/coated.rs +++ b/src/materials/coated.rs @@ -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; diff --git a/src/materials/complex.rs b/src/materials/complex.rs index 925b439..1ed7c93 100644 --- a/src/materials/complex.rs +++ b/src/materials/complex.rs @@ -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; diff --git a/src/shapes/bilinear.rs b/src/shapes/bilinear.rs index 403a80d..dbbd1da 100644 --- a/src/shapes/bilinear.rs +++ b/src/shapes/bilinear.rs @@ -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; diff --git a/src/shapes/curves.rs b/src/shapes/curves.rs index 7fed644..61ed919 100644 --- a/src/shapes/curves.rs +++ b/src/shapes/curves.rs @@ -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); diff --git a/src/shapes/cylinder.rs b/src/shapes/cylinder.rs index 7ab7463..3461dc0 100644 --- a/src/shapes/cylinder.rs +++ b/src/shapes/cylinder.rs @@ -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( diff --git a/src/shapes/disk.rs b/src/shapes/disk.rs index 8b5eee6..1caa6e0 100644 --- a/src/shapes/disk.rs +++ b/src/shapes/disk.rs @@ -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( diff --git a/src/shapes/mesh.rs b/src/shapes/mesh.rs index ee62212..edb4d85 100644 --- a/src/shapes/mesh.rs +++ b/src/shapes/mesh.rs @@ -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, @@ -21,235 +19,7 @@ pub struct TriQuadMesh { pub quad_indices: Vec, } -#[derive(DeviceRepr)] -#[device(name = "DeviceTriangleMesh")] -pub struct TriangleMeshStorage { - pub vertex_indices: Vec, // → Ptr + len (always present) - pub p: Vec, // → Ptr + len (always present) - pub n: Vec, // → Ptr + len (empty → null Ptr, len 0) - pub s: Vec, // → Ptr + len - pub uv: Vec, // → Ptr + len - pub face_indices: Vec, // → Ptr + len -} - - -#[derive(Debug)] -pub(crate) struct BilinearMeshStorage { - pub vertex_indices: Vec, - pub p: Vec, - pub n: Vec, - pub uv: Vec, - pub image_distribution: Option, -} - -#[derive(Debug)] -pub struct TriangleMesh { - pub storage: Box, - pub device: DeviceTriangleMesh, -} - -#[derive(Debug)] -pub struct BilinearPatchMesh { - pub storage: Box, - 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, - mut p: Vec, - mut n: Vec, - mut s: Vec, - uv: Vec, - face_indices: Vec, - ) -> 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, - mut p: Vec, - mut n: Vec, - uv: Vec, - image_distribution: Option, - ) -> 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 { match elem.get(key) { Some(Property::Float(v)) => Ok(*v), @@ -290,14 +60,12 @@ fn get_list_uint(elem: &DefaultElement, key: &str) -> AnyResult> { } } -// TriQuadMesh Implementation - impl TriQuadMesh { pub fn read_ply>(filename: P) -> AnyResult { 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::::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 = 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>( filename: P, diff --git a/src/shapes/sphere.rs b/src/shapes/sphere.rs index a88d2b4..56c222e 100644 --- a/src/shapes/sphere.rs +++ b/src/shapes/sphere.rs @@ -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( diff --git a/src/shapes/triangle.rs b/src/shapes/triangle.rs index ab387af..fba4bfd 100644 --- a/src/shapes/triangle.rs +++ b/src/shapes/triangle.rs @@ -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; diff --git a/src/textures/mix.rs b/src/textures/mix.rs index 8cdc5da..93ab749 100644 --- a/src/textures/mix.rs +++ b/src/textures/mix.rs @@ -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, + #[device(upload)] pub tex2: Arc, + #[device(upload)] pub amount: Arc, } @@ -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, + #[device(upload)] pub tex2: Arc, 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, + #[device(upload)] pub tex2: Arc, + #[device(upload)] pub amount: Arc, } @@ -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, + #[device(upload)] pub tex2: Arc, pub dir: Vector3f, } diff --git a/src/textures/scaled.rs b/src/textures/scaled.rs index 239f085..19c909c 100644 --- a/src/textures/scaled.rs +++ b/src/textures/scaled.rs @@ -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, + #[device(upload)] pub scale: Arc, } @@ -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, + #[device(upload)] pub scale: Arc, } diff --git a/src/utils/arena.rs b/src/utils/arena.rs index 485a183..426c9d0 100644 --- a/src/utils/arena.rs +++ b/src/utils/arena.rs @@ -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 Default for Arena { 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(&self, arena: &Arena) -> 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(&self, arena: &Arena) -> Ptr { - let value = self.upload_value(arena); - arena.alloc(value) - } -} - -impl DeviceRepr for Option { - type Target = T::Target; - - fn upload_value(&self, arena: &Arena) -> 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(&self, arena: &Arena) -> Ptr { - match self { - Some(val) => val.upload(arena), - None => Ptr::null(), - } - } -} - -impl DeviceRepr for std::sync::Arc { - type Target = T::Target; - - fn upload_value(&self, arena: &Arena) -> Self::Target { - (**self).upload_value(arena) - } - - fn upload(&self, arena: &Arena) -> Ptr { - (**self).upload(arena) - } -} - -impl DeviceRepr for Box { - type Target = T::Target; - - fn upload_value(&self, arena: &Arena) -> Self::Target { - (**self).upload_value(arena) - } - - fn upload(&self, arena: &Arena) -> Ptr { - (**self).upload(arena) - } -} - -impl DeviceRepr for Shape { - type Target = Shape; - fn upload_value(&self, _arena: &Arena) -> Shape { - self.clone() - } -} - -impl DeviceRepr for Light { - type Target = Light; - fn upload_value(&self, _arena: &Arena) -> Light { - self.clone() - } -} - -impl DeviceRepr for Spectrum { - type Target = Spectrum; - fn upload_value(&self, _arena: &Arena) -> Spectrum { - self.clone() - } -} - -impl DeviceRepr for Material { - type Target = Material; - fn upload_value(&self, _arena: &Arena) -> Material { - self.clone() - } -} - -// ============================================================================= -// Image → DeviceImage -// ============================================================================= - -impl DeviceRepr for Image { - type Target = DeviceImage; - fn upload_value(&self, _arena: &Arena) -> DeviceImage { - *self.device() - } -} - -// ============================================================================= -// DenselySampledSpectrumBuffer → DenselySampledSpectrum -// ============================================================================= - -impl DeviceRepr for DenselySampledSpectrumBuffer { - type Target = DenselySampledSpectrum; - fn upload_value(&self, _arena: &Arena) -> DenselySampledSpectrum { - self.device() - } -} - -// ============================================================================= -// RGBToSpectrumTable — re-uploads Ptr fields into arena -// ============================================================================= - -impl DeviceRepr for RGBToSpectrumTable { - type Target = RGBToSpectrumTable; - - fn upload_value(&self, arena: &Arena) -> 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(&self, arena: &Arena) -> 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(&self, arena: &Arena) -> 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, -// pub p: Vec, -// pub n: Vec, -// pub s: Vec, -// pub uv: Vec, -// pub face_indices: Vec, -// 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(&self, arena: &Arena) -> 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(&self, arena: &Arena) -> 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(&self, arena: &Arena) -> 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(&self, arena: &Arena) -> 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()), - } - } -} diff --git a/src/utils/backend.rs b/src/utils/backend.rs index 9c44412..3535f1e 100644 --- a/src/utils/backend.rs +++ b/src/utils/backend.rs @@ -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) }; } } } diff --git a/src/utils/containers.rs b/src/utils/containers.rs index 128b0f2..5a767e4 100644 --- a/src/utils/containers.rs +++ b/src/utils/containers.rs @@ -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 { @@ -32,63 +32,3 @@ where new_item } } - -#[derive(Debug, Clone)] -#[derive(DeviceRepr)] -pub struct Array2D { - pub device: DeviceArray2D, - pub values: Vec, -} - -impl Deref for Array2D { - type Target = DeviceArray2D; - fn deref(&self) -> &Self::Target { - &self.device - } -} - -impl DerefMut for Array2D { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.device - } -} - -impl Array2D { - pub fn as_slice(&self) -> &[T] { - &self.values - } - - fn from_vec(extent: Bounds2i, mut values: Vec) -> 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 { - &self.device - } -} - -impl Array2D { - 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) - } -} diff --git a/src/utils/math.rs b/src/utils/math.rs index a1ea46e..500f919 100644 --- a/src/utils/math.rs +++ b/src/utils/math.rs @@ -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 { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 18ba826..e294c8b 100644 --- a/src/utils/mod.rs +++ b/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; @@ -28,516 +30,3 @@ pub type Arena = arena::Arena; #[cfg(not(any(feature = "cuda", feature = "vulkan")))] pub type Arena = arena::Arena; - -/// # 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 { - 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 { - 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 = 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(&self, arena: &Arena) -> Self::Target { - #(#upload_stmts)* - #constructor - } - } - }) -} - -// Enum derivation -fn derive_enum(input: DeriveInput) -> syn::Result { - 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(&self, arena: &Arena) -> Self::Target { - match self { - #(#match_arms,)* - } - } - } - }) -} - -fn get_variant_inner_type(variant: &Variant) -> syn::Result { - 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, - variant_type: Option, -} - -fn parse_variant_attrs(attrs: &[Attribute]) -> syn::Result { - 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, - copy_upload: bool, - flatten: bool, - upload: bool, - spread: Option, -} - -fn parse_field_attrs(attrs: &[Attribute]) -> syn::Result { - 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 { - 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 { - 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 -} - diff --git a/src/utils/sampling.rs b/src/utils/sampling.rs index 37c87db..bfe026f 100644 --- a/src/utils/sampling.rs +++ b/src/utils/sampling.rs @@ -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, cdf: Vec, 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, min: Float, max: Float, n: usize) -> Self - where - F: Fn(Float) -> Float, - { - let delta = (max - min) / n as Float; - let values: Vec = (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, 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::() * 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, - pub cdf: Vec, - 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, - #[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) -> Self { Self::new_with_bounds(data, Bounds2f::unit()) } pub fn new_with_bounds(data: &Array2D, 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 { - data: Vec, - marginal_cdf: Vec, - conditional_cdf: Vec, - param_values: [Vec; N], -} +impl DeviceRepr for PiecewiseConstant2D { + type Target = DevicePiecewiseConstant2D; -pub struct PiecewiseLinear2DHost { - size: Vector2i, - inv_patch_size: Vector2f, - param_size: [u32; N], - param_strides: [u32; N], - storage: Arc>, -} + fn upload_value(&self, arena: &Arena) -> DevicePiecewiseConstant2D { + let uploaded: Vec = self + .conditionals + .iter() + .map(|c| c.upload_value(arena)) + .collect(); + let (conditionals_ptr, _) = arena.alloc_slice(&uploaded); - -impl PiecewiseLinear2DHost { - 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 DeviceRepr for PiecewiseLinear2DHost { - type Target = PiecewiseLinear2D; - - fn upload_value(&self, arena: &Arena) -> PiecewiseLinear2D { - 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; 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(&self, arena: &Arena) -> 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, } @@ -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, } @@ -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 { data: Vec, marginal_cdf: Vec, @@ -534,10 +322,8 @@ impl PiecewiseLinear2DHost { } 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 PiecewiseLinear2DHost { 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 PiecewiseLinear2DHost { 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 DeviceRepr for PiecewiseLinear2DHost { type Target = PiecewiseLinear2D; - fn upload_value(&self, arena: &Arena) -> PiecewiseLinear2D { + fn upload_value(&self, arena: &Arena) -> PiecewiseLinear2D { let s = &self.storage; let (data_ptr, _) = arena.alloc_slice(&s.data);