diff --git a/shared/build.rs b/shared/build.rs index 104c9d6..de7e557 100644 --- a/shared/build.rs +++ b/shared/build.rs @@ -2,4 +2,13 @@ fn main() { // This allows "spirv" to be used in #[cfg(target_arch = "...")] // without triggering a warning. println!("cargo:rustc-check-cfg=cfg(target_arch, values(\"spirv\"))"); + + // `gpu` is set for every device backend, so host-only code can be gated once + // as #[cfg(not(gpu))] instead of naming each target. Adding a backend means + // editing this line, not 30-odd cfg attributes. + println!("cargo::rustc-check-cfg=cfg(gpu)"); + let target = std::env::var("TARGET").unwrap_or_default(); + if target.contains("spirv") || target.contains("cuda") { + println!("cargo::rustc-cfg=gpu"); + } } diff --git a/shared/src/cameras/orthographic.rs b/shared/src/cameras/orthographic.rs index 021d549..98f42e0 100644 --- a/shared/src/cameras/orthographic.rs +++ b/shared/src/cameras/orthographic.rs @@ -24,7 +24,7 @@ pub struct OrthographicCamera { pub dy_camera: Vector3f, } -#[cfg(not(target_os = "cuda"))] +#[cfg(not(gpu))] impl OrthographicCamera { pub fn new( base: CameraBase, diff --git a/shared/src/cameras/perspective.rs b/shared/src/cameras/perspective.rs index e6ced0c..644d95b 100644 --- a/shared/src/cameras/perspective.rs +++ b/shared/src/cameras/perspective.rs @@ -26,7 +26,7 @@ pub struct PerspectiveCamera { pub cos_total_width: Float, } -#[cfg(not(target_os = "cuda"))] +#[cfg(not(gpu))] impl PerspectiveCamera { pub fn new( base: CameraBase, diff --git a/shared/src/core/camera.rs b/shared/src/core/camera.rs index 12dd9f9..6066c22 100644 --- a/shared/src/core/camera.rs +++ b/shared/src/core/camera.rs @@ -137,7 +137,7 @@ pub trait CameraTrait { fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option; fn get_film(&self) -> &Film { - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] { if self.base().film.is_null() { panic!( diff --git a/shared/src/core/film.rs b/shared/src/core/film.rs index a0c7283..258f326 100644 --- a/shared/src/core/film.rs +++ b/shared/src/core/film.rs @@ -96,7 +96,7 @@ impl RGBFilm { } pub fn get_sensor(&self) -> &PixelSensor { - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] { if self.base.sensor.is_null() { panic!( @@ -203,7 +203,7 @@ impl RGBFilm { #[repr(C)] #[derive(Debug, Clone)] -#[cfg_attr(target_os = "cuda", derive(Copy))] +#[cfg_attr(gpu, derive(Copy))] pub struct GBufferPixel { pub rgb_sum: [AtomicFloat; 3], pub weight_sum: AtomicFloat, @@ -240,7 +240,7 @@ impl Default for GBufferPixel { #[repr(C)] #[derive(Debug, Clone)] -#[cfg_attr(target_os = "cuda", derive(Copy))] +#[cfg_attr(gpu, derive(Copy))] pub struct GBufferFilm { pub base: FilmBase, pub output_from_render: AnimatedTransform, @@ -294,7 +294,7 @@ impl GBufferFilm { } pub fn get_sensor(&self) -> &PixelSensor { - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] { if self.base.sensor.is_null() { panic!( @@ -387,7 +387,7 @@ impl GBufferFilm { #[repr(C)] #[derive(Debug)] -#[cfg_attr(target_os = "cuda", derive(Copy))] +#[cfg_attr(gpu, derive(Copy))] pub struct SpectralPixel { pub rgb_sum: [AtomicFloat; 3], pub rgb_weight_sum: AtomicFloat, @@ -419,7 +419,7 @@ impl Default for SpectralPixel { #[repr(C)] #[derive(Debug)] -#[cfg_attr(target_os = "cuda", derive(Copy, Clone))] +#[cfg_attr(gpu, derive(Copy, Clone))] pub struct SpectralFilm { pub base: FilmBase, pub lambda_min: Float, @@ -609,7 +609,7 @@ pub struct FilmBase { #[repr(C)] #[derive(Debug)] -#[cfg_attr(target_os = "cuda", derive(Copy, Clone))] +#[cfg_attr(gpu, derive(Copy, Clone))] pub enum Film { RGB(RGBFilm), GBuffer(GBufferFilm), diff --git a/shared/src/core/interaction.rs b/shared/src/core/interaction.rs index 7cffc05..6efe884 100644 --- a/shared/src/core/interaction.rs +++ b/shared/src/core/interaction.rs @@ -590,7 +590,7 @@ impl SurfaceInteraction { } } - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] pub fn set_intersection_properties( &mut self, mtl: MaterialIdx, diff --git a/shared/src/core/light.rs b/shared/src/core/light.rs index ca71193..8e9136a 100644 --- a/shared/src/core/light.rs +++ b/shared/src/core/light.rs @@ -61,7 +61,7 @@ pub struct LightLiSample { pub p_light: Interaction, } -#[cfg(not(target_os = "cuda"))] +#[cfg(not(gpu))] impl LightLiSample { pub fn new(l: SampledSpectrum, wi: Vector3f, pdf: Float, p_light: Interaction) -> Self { Self { @@ -188,7 +188,7 @@ pub struct LightBounds { pub two_sided: bool, } -#[cfg(not(target_os = "cuda"))] +#[cfg(not(gpu))] impl LightBounds { pub fn new( bounds: &Bounds3f, @@ -327,13 +327,13 @@ pub trait LightTrait { self.base().light_type } - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn bounds(&self) -> Option; - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn preprocess(&mut self, scene_bounds: &Bounds3f); - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum; } diff --git a/shared/src/core/medium.rs b/shared/src/core/medium.rs index 7727821..79d6e7a 100644 --- a/shared/src/core/medium.rs +++ b/shared/src/core/medium.rs @@ -99,7 +99,7 @@ pub struct MajorantGrid { impl MajorantGrid { - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] pub fn new(bounds: Bounds3f, res: Point3i) -> Self { let n_voxels = (res.x() * res.y() * res.z()) as usize; let voxels = gvec_with_capacity(n_voxels); diff --git a/shared/src/core/texture.rs b/shared/src/core/texture.rs index 4aacb05..df97b88 100644 --- a/shared/src/core/texture.rs +++ b/shared/src/core/texture.rs @@ -245,7 +245,7 @@ pub struct PointTransformMapping { } impl PointTransformMapping { - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] pub fn new(texture_from_render: Transform) -> Self { Self { texture_from_render, diff --git a/shared/src/lights/diffuse.rs b/shared/src/lights/diffuse.rs index 4532af9..bf09729 100644 --- a/shared/src/lights/diffuse.rs +++ b/shared/src/lights/diffuse.rs @@ -130,7 +130,7 @@ impl LightTrait for DiffuseAreaLight { } } - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { let mut l = SampledSpectrum::new(0.); if !self.image.is_null() { @@ -153,10 +153,10 @@ impl LightTrait for DiffuseAreaLight { PI * two_side * self.area * l } - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn bounds(&self) -> Option { let mut phi = 0.; if !self.image.is_null() { diff --git a/shared/src/lights/goniometric.rs b/shared/src/lights/goniometric.rs index 9881f31..607dd7f 100644 --- a/shared/src/lights/goniometric.rs +++ b/shared/src/lights/goniometric.rs @@ -1,5 +1,8 @@ -use crate::core::geometry::{Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f}; +use crate::core::geometry::{ + Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, +}; use crate::core::image::Image; +use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction}; use crate::core::light::{ LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, }; @@ -34,12 +37,20 @@ impl LightTrait for GoniometricLight { fn sample_li( &self, - _ctx: &LightSampleContext, + ctx: &LightSampleContext, _u: Point2f, - _lambda: &SampledWavelengths, + lambda: &SampledWavelengths, _allow_incomplete_pdf: bool, ) -> Option { - todo!() + let render_from_light = self.base().render_from_light; + let p = render_from_light.apply_to_point(Point3f::new(0., 0., 0.)); + let wi = (p - ctx.p()).normalize(); + let wl = render_from_light.apply_inverse_vector(-wi); + let li = self.i(wl, lambda) / p.distance_squared(ctx.p()); + let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface); + let intr = SimpleInteraction::new(base); + + Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr))) } fn pdf_li( @@ -76,7 +87,7 @@ impl LightTrait for GoniometricLight { )) } - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { let resolution = self.image.resolution(); let mut sum_y = 0.; diff --git a/shared/src/lights/infinite.rs b/shared/src/lights/infinite.rs index db83094..1950f94 100644 --- a/shared/src/lights/infinite.rs +++ b/shared/src/lights/infinite.rs @@ -109,7 +109,7 @@ impl LightTrait for UniformInfiniteLight { None } - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { 4. * PI * PI * square(self.scene_radius) * self.scale * self.lemit.sample(&lambda) } @@ -227,21 +227,17 @@ impl LightTrait for ImageInfiniteLight { self.image_le(uv, lambda) } - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { let mut sum_l = SampledSpectrum::new(0.); let width = self.image.resolution().x(); let height = self.image.resolution().y(); for v in 0..height { for u in 0..width { - let mut rgb = RGB::default(); - for c in 0..3 { - rgb[c] = self.image.get_channel_with_wrap( - Point2i::new(u, v), - c, - WrapMode::OctahedralSphere.into(), - ); - } + let rgb = RGB::from(self.image.get_channels_with_wrap::<3>( + Point2i::new(u, v), + WrapMode::OctahedralSphere.into(), + )); sum_l += RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()) .sample(&lambda); } @@ -341,7 +337,7 @@ impl PortalInfiniteLight { (self.portal[1] - self.portal[0]).norm() * (self.portal[3] - self.portal[0]).norm() } - pub fn render_from_image(portal_frame: Frame, uv: Point2f) -> (Vector3f, Float) { + pub fn render_from_image_with(portal_frame: Frame, uv: Point2f) -> (Vector3f, Float) { let alpha = -PI / 2.0 + uv.x() * PI; let beta = -PI / 2.0 + uv.y() * PI; @@ -354,6 +350,11 @@ impl PortalInfiniteLight { (portal_frame.from_local(w), duv_dw) } + + #[inline] + pub fn render_from_image(&self, uv: Point2f) -> (Vector3f, Float) { + Self::render_from_image_with(self.portal_frame, uv) + } } impl LightTrait for PortalInfiniteLight { @@ -370,7 +371,7 @@ impl LightTrait for PortalInfiniteLight { ) -> Option { let b = self.image_bounds(ctx.p())?; let (uv, map_pdf) = self.distribution.sample(u, b)?; - let (wi, duv_dw) = Self::render_from_image(self.portal_frame, uv); + let (wi, duv_dw) = self.render_from_image(uv); if duv_dw == 0. { return None; } @@ -403,17 +404,34 @@ impl LightTrait for PortalInfiniteLight { } } - #[cfg(not(target_os = "cuda"))] - fn phi(&self, _lambda: SampledWavelengths) -> SampledSpectrum { - todo!() + #[cfg(not(gpu))] + fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { + let mut sum_l = SampledSpectrum::new(0.); + let width = self.image.resolution().x(); + let height = self.image.resolution().y(); + for y in 0..height { + for x in 0..width { + let rgb = RGB::from(self.image.get_channels::<3>(Point2i::new(x, y))); + let st = Point2f::new( + (x as Float + 0.5) / width as Float, + (y as Float + 0.5) / height as Float, + ); + let (_, duv_dw) = self.render_from_image(st); + sum_l += RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()) + .sample(&lambda) + / duv_dw; + } + } + + self.scale * self.area() * sum_l / (width * height) as Float } - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn preprocess(&mut self, scene_bounds: &Bounds3f) { (self.scene_center, self.scene_radius) = scene_bounds.bounding_sphere(); } - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn bounds(&self) -> Option { None } diff --git a/shared/src/lights/point.rs b/shared/src/lights/point.rs index b35eda4..027ffef 100644 --- a/shared/src/lights/point.rs +++ b/shared/src/lights/point.rs @@ -51,14 +51,14 @@ impl LightTrait for PointLight { 0. } - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { 4. * PI * self.scale * self.i.sample(&lambda) } fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn bounds(&self) -> Option { let p = self .base diff --git a/shared/src/lights/projection.rs b/shared/src/lights/projection.rs index 36ab79f..bc68c11 100644 --- a/shared/src/lights/projection.rs +++ b/shared/src/lights/projection.rs @@ -4,6 +4,7 @@ use crate::core::geometry::{ Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta, }; use crate::core::image::Image; +use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction}; use crate::core::light::{ LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, }; @@ -33,7 +34,7 @@ pub struct ProjectionLight { } impl ProjectionLight { - pub fn i(&self, w: Vector3f, lambda: SampledWavelengths) -> SampledSpectrum { + pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { if w.z() < self.hither { return SampledSpectrum::new(0.); } @@ -44,10 +45,10 @@ impl ProjectionLight { let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y()))); let mut rgb = RGB::default(); for c in 0..3 { - rgb[c] = self.image.lookup_nearest_channel(uv, c as i32); + rgb[c] = self.image.lookup_nearest_channel(uv, c); } - let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero()); - self.scale * s.sample(&lambda) + let s = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()); + self.scale * s.sample(lambda) } } @@ -58,12 +59,23 @@ impl LightTrait for ProjectionLight { fn sample_li( &self, - _ctx: &LightSampleContext, + ctx: &LightSampleContext, _u: Point2f, - _lambda: &SampledWavelengths, + lambda: &SampledWavelengths, _allow_incomplete_pdf: bool, ) -> Option { - todo!() + let render_from_light = self.base().render_from_light; + let p = render_from_light.apply_to_point(Point3f::new(0., 0., 0.)); + let wi = (p - ctx.p()).normalize(); + let wl = render_from_light.apply_inverse_vector(-wi); + let li = self.i(wl, lambda) / p.distance_squared(ctx.p()); + if li.is_black() { + return None; + } + let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface); + let intr = SimpleInteraction::new(base); + + Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr))) } fn pdf_li( @@ -72,7 +84,7 @@ impl LightTrait for ProjectionLight { _wi: Vector3f, _allow_incomplete_pdf: bool, ) -> Float { - todo!() + 0. } fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { @@ -93,10 +105,10 @@ impl LightTrait for ProjectionLight { let dwda = cos_theta(w).powi(3); let mut rgb = RGB::default(); for c in 0..3 { - rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32); + rgb[c] = self.image.get_channel(Point2i::new(x, y), c); } - let s = RGBIlluminantSpectrum::new(&*self.image_color_space, rgb.clamp_zero()); + let s = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()); sum += s.sample(&lambda) * dwda; } } diff --git a/shared/src/lights/spot.rs b/shared/src/lights/spot.rs index 62d48df..5cad17c 100644 --- a/shared/src/lights/spot.rs +++ b/shared/src/lights/spot.rs @@ -65,7 +65,7 @@ impl LightTrait for SpotLight { 0. } - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { self.scale * self.iemit.sample(&lambda) diff --git a/shared/src/materials/complex.rs b/shared/src/materials/complex.rs index facb411..4a55468 100644 --- a/shared/src/materials/complex.rs +++ b/shared/src/materials/complex.rs @@ -30,7 +30,7 @@ pub struct HairMaterial { } impl HairMaterial { - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] #[allow(clippy::too_many_arguments)] pub fn new( sigma_a: Ptr, diff --git a/shared/src/shapes/bilinear.rs b/shared/src/shapes/bilinear.rs index 30dadb4..4d3a413 100644 --- a/shared/src/shapes/bilinear.rs +++ b/shared/src/shapes/bilinear.rs @@ -98,7 +98,7 @@ impl BilinearPatchShape { Some([mesh.n[v0], mesh.n[v1], mesh.n[v2], mesh.n[v3]]) } - #[cfg(not(target_os = "cuda"))] + #[cfg(not(gpu))] pub fn new(mesh: Ptr, blp_index: i32) -> Self { let mut bp = BilinearPatchShape { mesh, diff --git a/src/lights/infinite.rs b/src/lights/infinite.rs index ea4a08a..f387409 100644 --- a/src/lights/infinite.rs +++ b/src/lights/infinite.rs @@ -184,7 +184,7 @@ fn create_portal_light( // Build distribution let duv_dw = |p: Point2f| -> Float { - let (_, jacobian) = PortalInfiniteLight::render_from_image(portal_frame, p); + let (_, jacobian) = PortalInfiniteLight::render_from_image_with(portal_frame, p); jacobian }; let d = remapped.get_sampling_distribution( @@ -247,7 +247,7 @@ fn remap_image_through_portal( (y as Float + 0.5) / height as Float, ); - let (w_world, _) = PortalInfiniteLight::render_from_image(*portal_frame, uv); + let (w_world, _) = PortalInfiniteLight::render_from_image_with(*portal_frame, uv); let w_local = render_from_light.apply_inverse_vector(w_world).normalize(); let uv_equi = equal_area_sphere_to_square(w_local);