feat: add real-time view
This commit is contained in:
parent
500314755c
commit
c0ed10b2e5
1572
Cargo.lock
generated
1572
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,6 @@
|
|||
[workspace]
|
||||
members = [".", "rtrt"]
|
||||
|
||||
[package]
|
||||
name = "rsrt"
|
||||
authors = ["Elise Amber Katze <git@katze.sh>"]
|
||||
|
|
@ -7,6 +10,7 @@ edition = "2021"
|
|||
[dependencies]
|
||||
fastrand = "2.1.0"
|
||||
image = { version = "0.25.1", default-features = false, features = ["png"] }
|
||||
paste = "1.0.15"
|
||||
rayon = "1.10.0"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
|
|
|
|||
13
rtrt/Cargo.toml
Normal file
13
rtrt/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "rtrt"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
pixels = "0.13.0"
|
||||
rayon = "1.10.0"
|
||||
rsrt = { path = ".." }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
winit = "0.28"
|
||||
winit_input_helper = "0.14"
|
||||
12
rtrt/src/main.rs
Normal file
12
rtrt/src/main.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
mod window;
|
||||
|
||||
fn main() {
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.with(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
window::run().unwrap();
|
||||
}
|
||||
331
rtrt/src/window.rs
Normal file
331
rtrt/src/window.rs
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use pixels::{Error, Pixels, SurfaceTexture};
|
||||
use rsrt::builder::RaytracerBuilder;
|
||||
use rsrt::interval::Interval;
|
||||
use rsrt::raytracer::util::RenderContext;
|
||||
use rsrt::raytracer::{random_f64, random_f64_range, Raytracer};
|
||||
use rsrt::vec3::{Color, Point3, Vec3};
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::event::{Event, VirtualKeyCode};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::window::WindowBuilder;
|
||||
use winit_input_helper::WinitInputHelper;
|
||||
|
||||
const WIDTH: u32 = 1920 / 1;
|
||||
const HEIGHT: u32 = 1080 / 1;
|
||||
const SPP_HQ: usize = 100;
|
||||
const SPP_LQ: usize = 15;
|
||||
const SPP_SLQ: usize = 1;
|
||||
|
||||
struct State {
|
||||
pub look_from: Point3,
|
||||
pub look_at: Point3,
|
||||
pub vfov: f64,
|
||||
pub focus: f64,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
builder: RaytracerBuilder,
|
||||
render_ctx: RenderContext,
|
||||
pub last_render: Instant,
|
||||
pub last_slq: bool,
|
||||
}
|
||||
|
||||
pub fn run() -> Result<(), Error> {
|
||||
let event_loop = EventLoop::new();
|
||||
let mut input = WinitInputHelper::new();
|
||||
let window = {
|
||||
let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64);
|
||||
WindowBuilder::new()
|
||||
.with_title("rtrt")
|
||||
.with_inner_size(size)
|
||||
.with_min_inner_size(size)
|
||||
.build(&event_loop)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let mut pixels = {
|
||||
let window_size = window.inner_size();
|
||||
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
|
||||
Pixels::new(WIDTH, HEIGHT, surface_texture)?
|
||||
};
|
||||
let mut world = State::new();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
if world.last_slq && world.last_render.elapsed() >= Duration::from_secs(2) {
|
||||
world.upgrade_from_slq();
|
||||
}
|
||||
|
||||
// Draw the current frame
|
||||
if let Event::RedrawRequested(_) = event {
|
||||
world.draw(pixels.frame_mut());
|
||||
if let Err(err) = pixels.render() {
|
||||
tracing::error!("pixels.render: {err}");
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle input events
|
||||
if input.update(&event) {
|
||||
// Close events
|
||||
if input.key_pressed(VirtualKeyCode::Escape) || input.close_requested() {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
|
||||
const MOVE_ADJ: f64 = 0.25;
|
||||
const LOOK_ADJ: f64 = 0.25;
|
||||
const FOV_ADJ: f64 = 0.10;
|
||||
const FOCUS_ADJ: f64 = 1.00;
|
||||
|
||||
|
||||
let mut move_backwards_amount = 0.0f64;
|
||||
let mut move_left_amount = 0.0f64;
|
||||
let mut move_up_amount = 0.0f64;
|
||||
let mut look_up_amount = 0.0f64;
|
||||
let mut look_left_amount = 0.0f64;
|
||||
let mut fov_amount = 0.0f64;
|
||||
let mut hq = false;
|
||||
let mut focus_amount = 0.0f64;
|
||||
|
||||
// Move events
|
||||
if input.key_pressed(VirtualKeyCode::W) {
|
||||
move_backwards_amount -= MOVE_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::S) {
|
||||
move_backwards_amount += MOVE_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::A) {
|
||||
move_left_amount += MOVE_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::D) {
|
||||
move_left_amount -= MOVE_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Up) {
|
||||
look_up_amount += LOOK_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Down) {
|
||||
look_up_amount -= LOOK_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Right) {
|
||||
look_left_amount -= LOOK_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Left) {
|
||||
look_left_amount += LOOK_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Space) {
|
||||
move_up_amount += LOOK_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::LShift) || input.key_pressed(VirtualKeyCode::RShift) {
|
||||
move_up_amount -= LOOK_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Equals) {
|
||||
fov_amount += FOV_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Minus) {
|
||||
fov_amount -= FOV_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::LBracket) {
|
||||
focus_amount += FOCUS_ADJ;
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::RBracket) {
|
||||
focus_amount -= FOCUS_ADJ;
|
||||
}
|
||||
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Return) {
|
||||
hq = true;
|
||||
}
|
||||
|
||||
|
||||
let resized: Option<(usize, usize)> = None;
|
||||
// Resize the window
|
||||
if let Some(size) = input.window_resized() {
|
||||
if let Err(err) = pixels.resize_surface(size.width, size.height) {
|
||||
tracing::error!("pixels.resize_surface: {err}");
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
|
||||
// if let Err(err) = pixels.resize_buffer(size.width, size.height) {
|
||||
// tracing::error!("pixels.resize_buffer: {err}");
|
||||
// *control_flow = ControlFlow::Exit;
|
||||
// return;
|
||||
// }
|
||||
|
||||
// resized = Some((size.width, size.height))
|
||||
}
|
||||
|
||||
// Update internal state and request a redraw
|
||||
if hq
|
||||
|| look_left_amount != 0.0
|
||||
|| look_up_amount != 0.0
|
||||
|| move_backwards_amount != 0.0
|
||||
|| move_left_amount != 0.0
|
||||
|| move_up_amount != 0.0
|
||||
|| resized.is_some()
|
||||
|| fov_amount != 0.0
|
||||
|| focus_amount != 0.0
|
||||
{
|
||||
if let Some((width, height)) = resized {
|
||||
world.width = width as usize;
|
||||
world.height = height as usize;
|
||||
}
|
||||
|
||||
world.focus += focus_amount;
|
||||
world.look_at += Vec3::new(0.0, look_up_amount, look_left_amount); //move_backwards_amount, look_up_amount + move_up_amount, look_left_amount + move_left_amount);
|
||||
world.look_from += Vec3::new(move_backwards_amount, move_up_amount, move_left_amount);
|
||||
world.vfov += fov_amount;
|
||||
world.update(hq, resized.is_some());
|
||||
}
|
||||
window.request_redraw();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Create a new `World` instance that can draw a moving box.
|
||||
fn new() -> Self {
|
||||
let look_from = Point3::new(13.0, 2.0, 3.0);
|
||||
let look_at = Point3::new(0.0, 0.0, 0.0);
|
||||
let vfov = 20.0;
|
||||
let focus = 10.0;
|
||||
|
||||
let mut builder = Raytracer::builder()
|
||||
.image_size(WIDTH as usize, HEIGHT as usize)
|
||||
.samples_per_pixel(1)
|
||||
.max_depth(50)
|
||||
.vertical_fov(vfov)
|
||||
.look_from(13.0, 2.0, 3.0)
|
||||
.look_at(0.0, 0.0, 0.0)
|
||||
.vup(0.0, 1.0, 0.0)
|
||||
.defocus_angle(0.6)
|
||||
.focus_dist(focus)
|
||||
.with_sphere(0.0, -1000.0, 0.0, 1000.0)
|
||||
.lambertian(1.0, 0.6, 0.7);
|
||||
|
||||
for a in -11..11 {
|
||||
for b in -11..11 {
|
||||
let choose_mat = random_f64();
|
||||
let center =
|
||||
Point3::new(a as f64 + 0.9 * random_f64(), 0.2, b as f64 + 0.9 * random_f64());
|
||||
|
||||
if (center - Point3::new(4.0, 0.2, 0.0)).len() > 0.9 {
|
||||
let shape_builder =
|
||||
builder.with_sphere(center.x(), center.y(), center.z(), 0.2);
|
||||
|
||||
builder = if choose_mat < 0.8 {
|
||||
let albedo = Color::random() * Color::random();
|
||||
shape_builder.lambertian(albedo.r(), albedo.g(), albedo.b())
|
||||
} else if choose_mat < 0.95 {
|
||||
let albedo = Color::random_range(0.5, 1.0);
|
||||
let fuzz = random_f64_range(0.0, 0.5);
|
||||
shape_builder.metal(albedo.r(), albedo.g(), albedo.b(), fuzz)
|
||||
} else {
|
||||
shape_builder.dielectric(1.5)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let builder = builder
|
||||
.with_sphere(0.0, 1.0, 0.0, 1.0)
|
||||
.dielectric(1.5)
|
||||
.with_sphere(-4.0, 1.0, 0.0, 1.0)
|
||||
.lambertian(1.0, 0.5, 0.6)
|
||||
.with_sphere(4.0, 1.0, 0.0, 1.0)
|
||||
.metal(0.7, 0.6, 0.5, 0.0);
|
||||
|
||||
let render_ctx = RenderContext::new(WIDTH as usize, HEIGHT as usize, 0);
|
||||
builder.clone().finish().render_multithread_rayon_detached(render_ctx.clone());
|
||||
Self {
|
||||
builder: builder.clone(),
|
||||
look_from,
|
||||
look_at,
|
||||
render_ctx,
|
||||
width: WIDTH as usize,
|
||||
height: HEIGHT as usize,
|
||||
last_render: Instant::now(),
|
||||
last_slq: true,
|
||||
vfov,
|
||||
focus
|
||||
}
|
||||
}
|
||||
|
||||
fn upgrade_from_slq(&mut self) {
|
||||
assert!(self.last_slq);
|
||||
self.last_slq = false;
|
||||
self.builder
|
||||
.samples_per_pixel_ref(SPP_LQ);
|
||||
self.builder.clone().finish().render_multithread_rayon_detached(self.render_ctx.clone());
|
||||
}
|
||||
|
||||
fn update(&mut self, hq: bool, changed_buffer: bool) {
|
||||
self.render_ctx.next();
|
||||
|
||||
if changed_buffer {
|
||||
self.render_ctx = RenderContext::new(self.width, self.height, self.render_ctx.render_id());
|
||||
self.builder.image_size_ref(self.width, self.height);
|
||||
}
|
||||
|
||||
let spp = match hq {
|
||||
true => SPP_HQ,
|
||||
false => SPP_SLQ,
|
||||
};
|
||||
|
||||
self.last_render = Instant::now();
|
||||
self.last_slq = spp == SPP_SLQ;
|
||||
|
||||
self.builder
|
||||
.focus_dist_ref(self.focus)
|
||||
.vertical_fov_ref(self.vfov)
|
||||
.samples_per_pixel_ref(match hq {
|
||||
true => SPP_HQ,
|
||||
false => SPP_SLQ,
|
||||
})
|
||||
.look_from_ref(self.look_from.x(), self.look_from.y(), self.look_from.z())
|
||||
.look_at_ref(self.look_at.x(), self.look_at.y(), self.look_at.z());
|
||||
self.builder.clone().finish().render_multithread_rayon_detached(self.render_ctx.clone());
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut [u8]) {
|
||||
for (index, pixel) in frame.chunks_exact_mut(4).enumerate() {
|
||||
fn linear_to_gamma(linear_component: f64) -> f64 {
|
||||
if linear_component > 0.0 {
|
||||
return linear_component.sqrt();
|
||||
}
|
||||
|
||||
0.0
|
||||
}
|
||||
|
||||
let color = self.render_ctx.get_color_raw_idx(index);
|
||||
|
||||
let r = linear_to_gamma(color.r());
|
||||
let g = linear_to_gamma(color.g());
|
||||
let b = linear_to_gamma(color.b());
|
||||
|
||||
const INTENSITY: Interval = Interval::new(0.000, 0.999);
|
||||
|
||||
pixel.copy_from_slice(&[
|
||||
(INTENSITY.clamp(r) * 256.0) as u8,
|
||||
(INTENSITY.clamp(g) * 256.0) as u8,
|
||||
(INTENSITY.clamp(b) * 256.0) as u8,
|
||||
255 as u8,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ impl SphereBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RaytracerBuilder {
|
||||
pub world: HittableList,
|
||||
|
||||
|
|
@ -71,51 +72,62 @@ impl Default for RaytracerBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! fn_ref {
|
||||
($(pub fn $fident:ident(&mut $selfident:ident, $($aident:ident: $aty:ty),*) $b:block)*) => {
|
||||
paste::paste! {
|
||||
$(
|
||||
pub fn $fident(mut $selfident, $($aident: $aty),*) -> Self {
|
||||
$b
|
||||
$selfident
|
||||
}
|
||||
|
||||
pub fn [<$fident _ref>](&mut $selfident, $($aident: $aty),*) -> &mut Self {
|
||||
$b
|
||||
$selfident
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl RaytracerBuilder {
|
||||
pub fn image_size(mut self, width: usize, height: usize) -> Self {
|
||||
self.image_width = width;
|
||||
self.image_height = height;
|
||||
self
|
||||
}
|
||||
fn_ref! {
|
||||
pub fn image_size(&mut self, width: usize, height: usize) {
|
||||
self.image_width = width;
|
||||
self.image_height = height;
|
||||
}
|
||||
|
||||
pub fn look_from(mut self, x: f64, y: f64, z: f64) -> Self {
|
||||
self.look_from = Point3::new(x, y, z);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn look_at(mut self, x: f64, y: f64, z: f64) -> Self {
|
||||
self.look_at = Point3::new(x, y, z);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vup(mut self, x: f64, y: f64, z: f64) -> Self {
|
||||
self.vup = Point3::new(x, y, z);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_depth(mut self, depth: usize) -> Self {
|
||||
self.max_depth = depth;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn samples_per_pixel(mut self, samples_per_pixel: usize) -> Self {
|
||||
self.samples_per_pixel = samples_per_pixel;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vertical_fov(mut self, vertical_fov: f64) -> Self {
|
||||
self.vertical_fov = vertical_fov;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn defocus_angle(mut self, defocus_angle: f64) -> Self {
|
||||
self.defocus_angle = defocus_angle;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn focus_dist(mut self, focus_dist: f64) -> Self {
|
||||
self.focus_dist = focus_dist;
|
||||
self
|
||||
pub fn look_from(&mut self, x: f64, y: f64, z: f64) {
|
||||
self.look_from = Point3::new(x, y, z);
|
||||
}
|
||||
|
||||
pub fn look_at(&mut self, x: f64, y: f64, z: f64) {
|
||||
self.look_at = Point3::new(x, y, z);
|
||||
}
|
||||
|
||||
pub fn vup(&mut self, x: f64, y: f64, z: f64) {
|
||||
self.vup = Point3::new(x, y, z);
|
||||
}
|
||||
|
||||
pub fn max_depth(&mut self, depth: usize) {
|
||||
self.max_depth = depth;
|
||||
}
|
||||
|
||||
pub fn samples_per_pixel(&mut self, samples_per_pixel: usize) {
|
||||
self.samples_per_pixel = samples_per_pixel;
|
||||
}
|
||||
|
||||
pub fn vertical_fov(&mut self, vertical_fov: f64) {
|
||||
self.vertical_fov = vertical_fov;
|
||||
}
|
||||
|
||||
pub fn defocus_angle(&mut self, defocus_angle: f64) {
|
||||
self.defocus_angle = defocus_angle;
|
||||
}
|
||||
|
||||
pub fn focus_dist(&mut self, focus_dist: f64) {
|
||||
self.focus_dist = focus_dist;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_sphere(self, x: f64, y: f64, z: f64, radius: f64) -> SphereBuilder {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ pub trait Hittable {
|
|||
fn hit(&self, ray: &Ray, ray_t: &Interval) -> Option<HitRecord>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Object {
|
||||
Sphere(sphere::Sphere),
|
||||
}
|
||||
|
|
@ -48,6 +49,7 @@ impl Hittable for Object {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HittableList {
|
||||
objects: Vec<Object>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use crate::{interval::Interval, material::Materials, vec3::Point3};
|
|||
|
||||
use super::{HitRecord, Hittable};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Sphere {
|
||||
center: Point3,
|
||||
radius: f64,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ fn main() {
|
|||
.with(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
let image = cover(WIDTH, HEIGHT, 500).render();
|
||||
let image = cover(WIDTH, HEIGHT, 500).render_multithread_rayon();
|
||||
|
||||
out::write_image("./image.png", &image[..], WIDTH as u32, HEIGHT as u32).unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
pub mod util;
|
||||
pub mod render;
|
||||
|
||||
use crate::{
|
||||
builder::RaytracerBuilder,
|
||||
|
|
@ -124,43 +125,6 @@ impl Raytracer {
|
|||
let a = 0.5 * (unit_direction.y() + 1.0);
|
||||
((1.0 - a) * Color::new(1.0, 1.0, 1.0)) + (a * Color::new(0.5, 0.7, 1.0))
|
||||
}
|
||||
|
||||
pub fn render_multithread_rayon(&self) -> Vec<Color> {
|
||||
(0..self.image_height)
|
||||
.into_par_iter()
|
||||
.flat_map(|y| (0..self.image_width).into_par_iter().map(move |x| (x, y)))
|
||||
.map(|(j, i)| {
|
||||
let mut pixel_color = Vec3::default();
|
||||
|
||||
for _ in 0..self.samples_per_pixel {
|
||||
let ray = self.get_ray(i, j);
|
||||
pixel_color += self.ray_color(self.max_depth, &ray);
|
||||
}
|
||||
|
||||
pixel_color * self.pixel_samples_scale
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn render(&self) -> Vec<Color> {
|
||||
let mut out = Vec::with_capacity(self.image_height * self.image_width);
|
||||
|
||||
for j in 0..self.image_height {
|
||||
tracing::debug!("Scanlines remaining: {}", self.image_height - j);
|
||||
for i in 0..self.image_width {
|
||||
let mut pixel_color = Vec3::default();
|
||||
|
||||
for _ in 0..self.samples_per_pixel {
|
||||
let ray = self.get_ray(i, j);
|
||||
pixel_color += self.ray_color(self.max_depth, &ray);
|
||||
}
|
||||
|
||||
out.push(pixel_color * self.pixel_samples_scale);
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
|
|
|||
77
src/raytracer/render.rs
Normal file
77
src/raytracer/render.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
|
||||
use crate::vec3::{Color, Vec3};
|
||||
|
||||
use super::util::RenderContext;
|
||||
|
||||
impl super::Raytracer {
|
||||
pub fn render_multithread_rayon_detached(self, ctx: RenderContext) {
|
||||
let render_id = ctx.render_id();
|
||||
std::thread::spawn(move || {
|
||||
let start = std::time::Instant::now();
|
||||
tracing::info!("Starting Render ID {render_id:#06X}");
|
||||
let terminated = (0..self.image_height)
|
||||
.into_par_iter()
|
||||
.flat_map(|y| (0..self.image_width).into_par_iter().map(move |x| (x, y)))
|
||||
.any(|(i, j)| {
|
||||
if ctx.render_id() != render_id {
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut pixel_color = Vec3::default();
|
||||
|
||||
for _ in 0..self.samples_per_pixel {
|
||||
let ray = self.get_ray(i, j);
|
||||
pixel_color += self.ray_color(self.max_depth, &ray);
|
||||
}
|
||||
|
||||
ctx.set_color(i, j, pixel_color * self.pixel_samples_scale);
|
||||
|
||||
false
|
||||
});
|
||||
|
||||
tracing::info!(
|
||||
"Finished Render ID {render_id:#06X} in {}s (terminated={terminated})",
|
||||
start.elapsed().as_secs_f64()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn render_multithread_rayon(&self) -> Vec<Color> {
|
||||
(0..self.image_height)
|
||||
.into_par_iter()
|
||||
.flat_map(|y| (0..self.image_width).into_par_iter().map(move |x| (x, y)))
|
||||
.map(|(i, j)| {
|
||||
tracing::debug!("Scanlines remaining: {}", self.image_height - j);
|
||||
let mut pixel_color = Vec3::default();
|
||||
|
||||
for _ in 0..self.samples_per_pixel {
|
||||
let ray = self.get_ray(i, j);
|
||||
pixel_color += self.ray_color(self.max_depth, &ray);
|
||||
}
|
||||
|
||||
pixel_color * self.pixel_samples_scale
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn render(&self) -> Vec<Color> {
|
||||
let mut out = Vec::with_capacity(self.image_height * self.image_width);
|
||||
|
||||
for j in 0..self.image_height {
|
||||
tracing::debug!("Scanlines remaining: {}", self.image_height - j);
|
||||
for i in 0..self.image_width {
|
||||
let mut pixel_color = Color::default();
|
||||
|
||||
for _ in 0..self.samples_per_pixel {
|
||||
let ray = self.get_ray(i, j);
|
||||
pixel_color += self.ray_color(self.max_depth, &ray);
|
||||
}
|
||||
|
||||
out.push(pixel_color * self.pixel_samples_scale);
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
118
src/raytracer/util.rs
Normal file
118
src/raytracer/util.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
use std::{alloc::Layout, cell::UnsafeCell, ops::Deref, ptr::NonNull, sync::atomic::AtomicUsize};
|
||||
|
||||
use crate::vec3::Color;
|
||||
|
||||
pub struct RenderContextInner {
|
||||
width: usize,
|
||||
render_id: UnsafeCell<usize>,
|
||||
max: usize,
|
||||
layout: Layout,
|
||||
buffer: UnsafeCell<*mut Color>,
|
||||
refs: AtomicUsize,
|
||||
}
|
||||
|
||||
impl RenderContextInner {
|
||||
pub fn new(width: usize, height: usize, id: usize) -> Self {
|
||||
let layout = Layout::array::<Color>(width * height).unwrap();
|
||||
let buffer = UnsafeCell::new(unsafe { std::alloc::alloc_zeroed(layout) } as *mut Color);
|
||||
Self {
|
||||
width,
|
||||
max: width * height,
|
||||
layout,
|
||||
buffer,
|
||||
refs: AtomicUsize::new(1),
|
||||
render_id: UnsafeCell::new(id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_id(&self) -> usize {
|
||||
let ptr = self.render_id.get();
|
||||
unsafe { std::ptr::read_volatile(ptr) }
|
||||
}
|
||||
|
||||
pub fn next(&self) {
|
||||
let ptr = self.render_id.get();
|
||||
unsafe {
|
||||
std::ptr::write_volatile(ptr, std::ptr::read_volatile(ptr) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_color(&self, x: usize, y: usize, color: Color) {
|
||||
let offset = (y * self.width) + x;
|
||||
|
||||
if offset >= self.max {
|
||||
panic!("Bad index: {offset} >= {}", self.max);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ptr = *self.buffer.get();
|
||||
std::ptr::write(ptr.add(offset), color);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_color_raw_idx(&self, offset: usize) -> Color {
|
||||
if offset >= self.max {
|
||||
panic!("Bad index: {offset} >= {}", self.max);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ptr = *self.buffer.get();
|
||||
std::ptr::read(ptr.add(offset))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_color(&self, x: usize, y: usize) -> Color {
|
||||
let offset = (y * self.width) + x;
|
||||
self.get_color_raw_idx(offset)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RenderContext {
|
||||
inner: NonNull<RenderContextInner>,
|
||||
}
|
||||
|
||||
impl Deref for RenderContext {
|
||||
type Target = RenderContextInner;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { self.inner.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderContext {
|
||||
pub fn new(width: usize, height: usize, id: usize) -> Self {
|
||||
const LAYOUT: Layout = Layout::new::<RenderContextInner>();
|
||||
let inner = NonNull::new(unsafe { std::alloc::alloc_zeroed(LAYOUT) as *mut RenderContextInner }).unwrap();
|
||||
unsafe { inner.write_volatile(RenderContextInner::new(width, height, id)); }
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for RenderContext {
|
||||
fn clone(&self) -> Self {
|
||||
if unsafe { self.inner.as_ref() }.refs.fetch_add(1, std::sync::atomic::Ordering::SeqCst) == usize::MAX {
|
||||
panic!("RenderContextInner refcount overflowed??");
|
||||
}
|
||||
|
||||
unsafe { std::ptr::read_volatile(self as *const Self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RenderContext {
|
||||
fn drop(&mut self) {
|
||||
let r = unsafe { self.inner.as_ref() }.refs.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
|
||||
if r <= 1 {
|
||||
let n = unsafe { self.inner.as_ref() }.refs.load(std::sync::atomic::Ordering::SeqCst);
|
||||
println!("Freeing... old={r} new={n}");
|
||||
unsafe {
|
||||
let ptr_ptr: *mut *mut Color = self.inner.as_ref().buffer.get();
|
||||
let ptr = *ptr_ptr;
|
||||
std::alloc::dealloc(ptr as *mut u8, self.inner.as_ref().layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for RenderContext {}
|
||||
unsafe impl Sync for RenderContext {}
|
||||
Loading…
Reference in a new issue