1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Module providing render targets for both Software and Hardware accelerated image rendering.

use itertools::Itertools as _;
use rand::prelude::*;
use rand_pcg::Pcg64Mcg;

use crate::ray::RayResult;

mod software;
pub use software::Image;

#[cfg(feature = "gpu")]
mod vulkan;
#[cfg(feature = "gpu")]
mod vulkan_context;
#[cfg(feature = "gpu")]
pub use vulkan::VulkanImage;

fn calculate_scale(width: f64, height: f64, lightpower: f32, rays: usize, exposure: f64) -> f32 {
    let area_scale = f64::sqrt((width * height) / (1024.0 * 576.0));
    let intensity_scale = lightpower as f64 / (255.0 * 8192.0);
    (f64::exp(1.0 + (10.0 as f64 * exposure)) * area_scale * intensity_scale / rays as f64) as f32
}

/// This trait provideds an render target to be passed to the renderer.
pub trait RenderImage: Send + Sync {
    /// Called in every rendering thread to add a Ray to the render target
    fn draw_line(&self, ray: RayResult);

    /// called momentarilly before the render begins to allow the render target to set it's self up to receive calls to `draw_line`
    fn prepare_render(&mut self, lightpower: f32);

    /// called immediately after a render ends to allow the render target to clean up or finalize results
    fn finish_render(&mut self) {}
}

/// This trait provides interfaces to extract the image data from a render target.
pub trait ExportImage {
    /// Returns the image size in pixels in the format `(width, height)`
    fn get_size(&self) -> (usize, usize);

    /// Returns the lightpower of the scene most recently rendered to this image.
    fn get_lightpower(&self) -> f32;

    /// Outputs the image.
    /// Serialsiing the image to a sequence of 32 bit floating point RGBA samples stored in a `Vec<f32>`,
    /// suitible for use in high bit depth images such as used by blender.
    fn to_rgbaf32(&self) -> Vec<f32>;

    /// Outputs the image.
    /// Serialsiing the image to a sequence of 8 bit RGB samples stored in a `Vec<u8>`,
    /// suitible for use in file streams and other outputs.
    ///
    /// This function also normalises the image applying exposure and gamma.
    /// gamma is passed in the form of an exponent which is defined as `1.0 / gamma`
    fn to_rgba8(&self, rays: usize, exposure: f64, exponent: f32) -> Vec<u8> {
        let (width, height) = self.get_size();
        let scale = calculate_scale(
            width as f64,
            height as f64,
            self.get_lightpower(),
            rays,
            exposure,
        );
        let mut rng = Pcg64Mcg::new(0xcafef00dd15ea5e5);
        self.to_rgbaf32()
            .iter()
            .tuples()
            .flat_map(|(r, g, b, _a)| {
                // red
                let u: f32 = 0f32.max(r * scale);
                let dither = rng.gen_range(0f32..1f32);
                let v: f32 = 255.0 * u.powf(exponent) + dither;
                let r8 = 0f32.max(255.9f32.min(v));

                // green
                let u: f32 = 0f32.max(g * scale);
                let dither = rng.gen_range(0f32..1f32);
                let v: f32 = 255.0 * u.powf(exponent) + dither;
                let g8 = 0f32.max(255.9f32.min(v));

                // blue
                let u: f32 = 0f32.max(b * scale);
                let dither = rng.gen_range(0f32..1f32);
                let v: f32 = 255.0 * u.powf(exponent) + dither;
                let b8 = 0f32.max(255.9f32.min(v));

                [r8 as u8, g8 as u8, b8 as u8, 255u8]
            })
            .collect()
    }
}