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]
|
[package]
|
||||||
name = "rsrt"
|
name = "rsrt"
|
||||||
authors = ["Elise Amber Katze <git@katze.sh>"]
|
authors = ["Elise Amber Katze <git@katze.sh>"]
|
||||||
|
|
@ -7,6 +10,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fastrand = "2.1.0"
|
fastrand = "2.1.0"
|
||||||
image = { version = "0.25.1", default-features = false, features = ["png"] }
|
image = { version = "0.25.1", default-features = false, features = ["png"] }
|
||||||
|
paste = "1.0.15"
|
||||||
rayon = "1.10.0"
|
rayon = "1.10.0"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
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 struct RaytracerBuilder {
|
||||||
pub world: HittableList,
|
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 {
|
impl RaytracerBuilder {
|
||||||
pub fn image_size(mut self, width: usize, height: usize) -> Self {
|
fn_ref! {
|
||||||
|
pub fn image_size(&mut self, width: usize, height: usize) {
|
||||||
self.image_width = width;
|
self.image_width = width;
|
||||||
self.image_height = height;
|
self.image_height = height;
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn look_from(mut self, x: f64, y: f64, z: f64) -> Self {
|
pub fn look_from(&mut self, x: f64, y: f64, z: f64) {
|
||||||
self.look_from = Point3::new(x, y, z);
|
self.look_from = Point3::new(x, y, z);
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn look_at(mut self, x: f64, y: f64, z: f64) -> Self {
|
pub fn look_at(&mut self, x: f64, y: f64, z: f64) {
|
||||||
self.look_at = Point3::new(x, y, z);
|
self.look_at = Point3::new(x, y, z);
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vup(mut self, x: f64, y: f64, z: f64) -> Self {
|
pub fn vup(&mut self, x: f64, y: f64, z: f64) {
|
||||||
self.vup = Point3::new(x, y, z);
|
self.vup = Point3::new(x, y, z);
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_depth(mut self, depth: usize) -> Self {
|
pub fn max_depth(&mut self, depth: usize) {
|
||||||
self.max_depth = depth;
|
self.max_depth = depth;
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn samples_per_pixel(mut self, samples_per_pixel: usize) -> Self {
|
pub fn samples_per_pixel(&mut self, samples_per_pixel: usize) {
|
||||||
self.samples_per_pixel = samples_per_pixel;
|
self.samples_per_pixel = samples_per_pixel;
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vertical_fov(mut self, vertical_fov: f64) -> Self {
|
pub fn vertical_fov(&mut self, vertical_fov: f64) {
|
||||||
self.vertical_fov = vertical_fov;
|
self.vertical_fov = vertical_fov;
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn defocus_angle(mut self, defocus_angle: f64) -> Self {
|
pub fn defocus_angle(&mut self, defocus_angle: f64) {
|
||||||
self.defocus_angle = defocus_angle;
|
self.defocus_angle = defocus_angle;
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn focus_dist(mut self, focus_dist: f64) -> Self {
|
pub fn focus_dist(&mut self, focus_dist: f64) {
|
||||||
self.focus_dist = focus_dist;
|
self.focus_dist = focus_dist;
|
||||||
self
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_sphere(self, x: f64, y: f64, z: f64, radius: f64) -> SphereBuilder {
|
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>;
|
fn hit(&self, ray: &Ray, ray_t: &Interval) -> Option<HitRecord>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Object {
|
pub enum Object {
|
||||||
Sphere(sphere::Sphere),
|
Sphere(sphere::Sphere),
|
||||||
}
|
}
|
||||||
|
|
@ -48,6 +49,7 @@ impl Hittable for Object {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct HittableList {
|
pub struct HittableList {
|
||||||
objects: Vec<Object>,
|
objects: Vec<Object>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use crate::{interval::Interval, material::Materials, vec3::Point3};
|
||||||
|
|
||||||
use super::{HitRecord, Hittable};
|
use super::{HitRecord, Hittable};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Sphere {
|
pub struct Sphere {
|
||||||
center: Point3,
|
center: Point3,
|
||||||
radius: f64,
|
radius: f64,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ fn main() {
|
||||||
.with(tracing_subscriber::EnvFilter::from_default_env())
|
.with(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
.init();
|
.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();
|
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::{
|
use crate::{
|
||||||
builder::RaytracerBuilder,
|
builder::RaytracerBuilder,
|
||||||
|
|
@ -124,43 +125,6 @@ impl Raytracer {
|
||||||
let a = 0.5 * (unit_direction.y() + 1.0);
|
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))
|
((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)]
|
#[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