use crate::bridge::export_thread::{SceneSnapshot, batch::render_batch};
use glam::Vec3;
use indicatrix::{
color::{ColorSpace, ToneMap},
optics::raytracer::Camera,
renderer::tonemap::tonemap_to_rgba,
};
#[must_use]
pub fn render_frame_rgba(
scene: &SceneSnapshot,
width: u32,
height: u32,
samples_per_pixel: u32,
cam_yaw: f32,
cam_pitch: f32,
color_space: ColorSpace,
) -> Vec<u8> {
let spp = samples_per_pixel.max(1);
let camera = Camera::new(cam_yaw, cam_pitch, scene.distance, 42.0);
let mut accum = vec![Vec3::ZERO; (width * height) as usize];
render_batch(width, height, spp, 0, &camera, scene, &mut accum);
tonemap(&accum, spp, color_space)
}
fn tonemap(accum: &[Vec3], samples_per_pixel: u32, color_space: ColorSpace) -> Vec<u8> {
let inv_samples = 1.0 / samples_per_pixel as f32;
if color_space == ColorSpace::Srgb {
return tonemap_to_rgba(accum, inv_samples);
}
let mut out = Vec::with_capacity(accum.len() * 4);
for xyz in accum {
let rgba = color_space.encode(*xyz * inv_samples, ToneMap::AcesFilmic { exposure: 1.0 });
out.extend_from_slice(&rgba);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bridge::render_thread::RenderContext;
use std::sync::Mutex;
#[test]
fn render_frame_rgba_produces_a_full_opaque_buffer() {
let scene = SceneSnapshot::capture(&Mutex::new(RenderContext::default()));
let rgba = render_frame_rgba(&scene, 8, 8, 1, 0.6, 0.5, ColorSpace::Srgb);
assert_eq!(rgba.len(), 8 * 8 * 4);
assert!(
rgba.chunks(4).all(|px| px[3] == 255),
"every pixel must be fully opaque"
);
}
#[test]
fn render_frame_rgba_accepts_wide_gamut_color_spaces_too() {
let scene = SceneSnapshot::capture(&Mutex::new(RenderContext::default()));
let rgba = render_frame_rgba(&scene, 4, 4, 1, 0.0, 1.4, ColorSpace::DisplayP3);
assert_eq!(rgba.len(), 4 * 4 * 4);
}
}