more radical reorganization!
rendering is now performed in a separate thread. this is of little use now but lays the groundwork for adding parallel rendering. i just need to think about data ownership a little harder to make the tile renderer nice.
This commit is contained in:
parent
1141566e6f
commit
d8a88e81c8
13 changed files with 442 additions and 207 deletions
124
Cargo.lock
generated
124
Cargo.lock
generated
|
@ -8,24 +8,12 @@ version = "1.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "enum_dispatch"
|
||||
version = "0.3.13"
|
||||
|
@ -44,7 +32,7 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "032213946b4eaae09117ec63f020322b78ca7a31d8aa2cf64df3032e1579690f"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cfg-if",
|
||||
"fasthash-sys",
|
||||
"num-traits",
|
||||
"seahash",
|
||||
|
@ -77,9 +65,6 @@ name = "glam"
|
|||
version = "0.30.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0e9b6647e9b41d3a5ef02964c6be01311a7f2472fea40595c635c6d046c259e"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
|
@ -99,23 +84,33 @@ version = "0.2.172"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "matrixmultiply"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"rawpointer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mechthild_core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"enum_dispatch",
|
||||
"fasthash",
|
||||
"glam",
|
||||
"range2d",
|
||||
"ndarray",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mechthild_py"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"mechthild_core",
|
||||
"numpy",
|
||||
"pyo3",
|
||||
]
|
||||
|
||||
|
@ -128,6 +123,39 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndarray"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
|
||||
dependencies = [
|
||||
"matrixmultiply",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"rawpointer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
|
@ -137,6 +165,22 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29f1dee9aa8d3f6f8e8b9af3803006101bb3653866ef056d530d53ae68587191"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"ndarray",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"pyo3",
|
||||
"pyo3-build-config",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
|
@ -149,6 +193,15 @@ version = "1.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
|
@ -160,11 +213,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.24.2"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5203598f366b11a02b13aa20cab591229ff0a89fd121a308a5df751d5fc9219"
|
||||
checksum = "f239d656363bcee73afef85277f1b281e8ac6212a1d42aa90e55b90ed43c47a4"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"indoc",
|
||||
"libc",
|
||||
"memoffset",
|
||||
|
@ -178,9 +230,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.24.2"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99636d423fa2ca130fa5acde3059308006d46f98caac629418e53f7ebb1e9999"
|
||||
checksum = "755ea671a1c34044fa165247aaf6f419ca39caa6003aee791a0df2713d8f1b6d"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"target-lexicon",
|
||||
|
@ -188,9 +240,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.24.2"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78f9cf92ba9c409279bc3305b5409d90db2d2c22392d443a87df3a1adad59e33"
|
||||
checksum = "fc95a2e67091e44791d4ea300ff744be5293f394f1bafd9f78c080814d35956e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
|
@ -198,9 +250,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.24.2"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b999cb1a6ce21f9a6b147dcf1be9ffedf02e0043aec74dc390f3007047cecd9"
|
||||
checksum = "a179641d1b93920829a62f15e87c0ed791b6c8db2271ba0fd7c2686090510214"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
|
@ -210,9 +262,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.24.2"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "822ece1c7e1012745607d5cf0bcb2874769f0f7cb34c4cde03b9358eb9ef911a"
|
||||
checksum = "9dff85ebcaab8c441b0e3f7ae40a6963ecea8a9f5e74f647e33fcf5ec9a1e89e"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
@ -259,10 +311,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
|
||||
[[package]]
|
||||
name = "range2d"
|
||||
version = "0.2.0"
|
||||
name = "rawpointer"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7169c4e8549f72214a10fe6a64e24bf5e2d75ca9378d0ac7934afb9acbdfc313"
|
||||
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
|
@ -273,6 +325,12 @@ dependencies = [
|
|||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "seahash"
|
||||
version = "3.0.7"
|
||||
|
|
|
@ -7,12 +7,11 @@ license.workspace = true
|
|||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bytemuck = { version = "1.22.0", optional = true }
|
||||
enum_dispatch = "0.3.13"
|
||||
fasthash = "0.4.0"
|
||||
glam = "0.30.2"
|
||||
range2d = "0.2.0"
|
||||
ndarray = "0.16.1"
|
||||
num-traits = "0.2.19"
|
||||
|
||||
[features]
|
||||
fast-math = ["glam/fast-math"]
|
||||
bytemuck = ["dep:bytemuck", "glam/bytemuck"]
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
use crate::{
|
||||
color::Rgba,
|
||||
geometry::{Point, Point2, Ray, Transform, Transform2, Vector, point2, ray, vec3},
|
||||
sampling::PixelSample,
|
||||
};
|
||||
use crate::geometry::{Point, Point2, Ray, Transform, Vector, ray, vec3};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Camera {
|
||||
|
@ -27,45 +23,3 @@ impl Camera {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Film {
|
||||
width: u32,
|
||||
height: u32,
|
||||
transform: Transform2,
|
||||
data: Box<[Rgba]>,
|
||||
}
|
||||
|
||||
impl Film {
|
||||
pub fn new(width: u32, height: u32) -> Self {
|
||||
let size = width as usize * height as usize;
|
||||
|
||||
let data = vec![Rgba::BLACK; size].into_boxed_slice();
|
||||
|
||||
let w = width as f32;
|
||||
let h = height as f32;
|
||||
let ar = h / w;
|
||||
|
||||
let transform = Transform2::translate(-1.0, ar) * Transform2::scale(2.0 / w, -2.0 / w);
|
||||
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
transform,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_sample(&mut self, x: u32, y: u32, color: Rgba, weight: f32) {
|
||||
let i = y as usize * self.width as usize + x as usize;
|
||||
self.data[i] += color * weight;
|
||||
}
|
||||
|
||||
pub fn get_camera_sample(&self, x: u32, y: u32, sampler: &mut PixelSample) -> Point2 {
|
||||
let p = point2(x as f32, y as f32) + sampler.get_2d().into();
|
||||
self.transform * p
|
||||
}
|
||||
|
||||
pub fn into_data(self) -> Box<[Rgba]> {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,75 +1,99 @@
|
|||
use std::{
|
||||
fmt,
|
||||
ops::{Add, AddAssign, Mul},
|
||||
};
|
||||
use std::fmt;
|
||||
use std::ops::{Add, AddAssign, Div, DivAssign, Index, Mul, MulAssign};
|
||||
|
||||
use glam::Vec3A;
|
||||
|
||||
use crate::Vector;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct Rgba {
|
||||
r: f32,
|
||||
g: f32,
|
||||
b: f32,
|
||||
a: f32,
|
||||
pub struct Rgb(Vec3A);
|
||||
|
||||
impl Rgb {
|
||||
pub const BLACK: Self = rgb(0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Rgba {
|
||||
Rgba { r, g, b, a }
|
||||
impl num_traits::Zero for Rgb {
|
||||
fn zero() -> Self {
|
||||
Self(Vec3A::ZERO)
|
||||
}
|
||||
|
||||
fn is_zero(&self) -> bool {
|
||||
self.0 == Vec3A::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
impl Rgba {
|
||||
pub const BLACK: Self = rgba(0.0, 0.0, 0.0, 1.0);
|
||||
pub const fn rgb(r: f32, g: f32, b: f32) -> Rgb {
|
||||
Rgb(glam::vec3a(r, g, b))
|
||||
}
|
||||
|
||||
impl fmt::Debug for Rgba {
|
||||
impl From<Vector> for Rgb {
|
||||
fn from(v: Vector) -> Self {
|
||||
Self(v.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Rgb {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple(stringify!(Rgba))
|
||||
.field(&self.r)
|
||||
.field(&self.g)
|
||||
.field(&self.b)
|
||||
.field(&self.a)
|
||||
f.debug_tuple(stringify!(Rgb))
|
||||
.field(&self.0.x)
|
||||
.field(&self.0.y)
|
||||
.field(&self.0.z)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Rgba {
|
||||
type Output = Self;
|
||||
impl Index<usize> for Rgb {
|
||||
type Output = f32;
|
||||
|
||||
fn add(self, Self { r, g, b, a }: Self) -> Self::Output {
|
||||
Self {
|
||||
r: self.r + r,
|
||||
g: self.g + g,
|
||||
b: self.b + b,
|
||||
a: self.a + a,
|
||||
#[inline]
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
match index {
|
||||
0 => &self.0.x,
|
||||
1 => &self.0.y,
|
||||
2 => &self.0.z,
|
||||
_ => panic!("index out of bounds"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Rgba {
|
||||
fn add_assign(&mut self, Self { r, g, b, a }: Self) {
|
||||
self.r += r;
|
||||
self.g += g;
|
||||
self.b += b;
|
||||
self.a += a;
|
||||
impl Add for Rgb {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for Rgba {
|
||||
impl AddAssign for Rgb {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
self.0 += rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for Rgb {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: f32) -> Self::Output {
|
||||
Self {
|
||||
r: self.r * rhs,
|
||||
g: self.g * rhs,
|
||||
b: self.b * rhs,
|
||||
a: self.a * rhs,
|
||||
}
|
||||
Self(self.0 * rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Rgba> for f32 {
|
||||
type Output = Rgba;
|
||||
|
||||
fn mul(self, rhs: Rgba) -> Self::Output {
|
||||
rhs * self
|
||||
impl MulAssign<f32> for Rgb {
|
||||
fn mul_assign(&mut self, rhs: f32) {
|
||||
self.0 *= rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f32> for Rgb {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: f32) -> Self::Output {
|
||||
Self(self.0 / rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<f32> for Rgb {
|
||||
fn div_assign(&mut self, rhs: f32) {
|
||||
self.0 /= rhs
|
||||
}
|
||||
}
|
||||
|
|
37
core/src/film.rs
Normal file
37
core/src/film.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use ndarray::{Array3, Ix2, iter::LanesMut};
|
||||
|
||||
use crate::{geometry::Transform2, sampling::Sampler};
|
||||
|
||||
pub struct Film {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub transform: Transform2,
|
||||
pub data: Array3<f32>,
|
||||
}
|
||||
|
||||
impl Film {
|
||||
pub fn new(width: u32, height: u32) -> Self {
|
||||
let w = width as f32;
|
||||
let h = height as f32;
|
||||
let ar = h / w;
|
||||
|
||||
let transform = Transform2::translate(-1.0, ar) * Transform2::scale(2.0 / w, -2.0 / w);
|
||||
|
||||
let data = Array3::zeros((height as usize, width as usize, 3));
|
||||
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
transform,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sampler(&self, samples: u32, seed: u32) -> Sampler {
|
||||
Sampler::new(self.width, self.height, samples, seed)
|
||||
}
|
||||
|
||||
pub fn lanes_mut(&mut self) -> LanesMut<'_, f32, Ix2> {
|
||||
self.data.lanes_mut(ndarray::Axis(2))
|
||||
}
|
||||
}
|
|
@ -21,6 +21,10 @@ impl Vector {
|
|||
pub const J: Self = Self(glam::Vec3A::Y);
|
||||
pub const K: Self = Self(glam::Vec3A::Z);
|
||||
|
||||
pub fn splat(v: f32) -> Self {
|
||||
Self(glam::Vec3A::splat(v))
|
||||
}
|
||||
|
||||
pub fn length(self) -> f32 {
|
||||
self.0.length()
|
||||
}
|
||||
|
@ -80,6 +84,12 @@ impl Deref for Vector {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Vector> for glam::Vec3A {
|
||||
fn from(value: Vector) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector> for glam::Vec3 {
|
||||
fn from(value: Vector) -> Self {
|
||||
value.0.into()
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
use crate::color::Rgba;
|
||||
|
||||
unsafe impl Pod for Rgba {}
|
||||
unsafe impl Zeroable for Rgba {}
|
153
core/src/lib.rs
153
core/src/lib.rs
|
@ -1,66 +1,129 @@
|
|||
use range2d::Range2D;
|
||||
|
||||
#[cfg(feature = "bytemuck")]
|
||||
pub mod impl_bytemuck;
|
||||
|
||||
pub mod color;
|
||||
use std::{
|
||||
sync::mpsc::{self, Receiver, Sender, channel},
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
|
||||
mod camera;
|
||||
mod color;
|
||||
mod film;
|
||||
mod geometry;
|
||||
mod sampling;
|
||||
mod scene;
|
||||
|
||||
pub use camera::Camera;
|
||||
pub use film::Film;
|
||||
pub use geometry::{Point, Shape, Transform, Vector};
|
||||
pub use scene::Scene;
|
||||
|
||||
use camera::Film;
|
||||
use color::{Rgba, rgba};
|
||||
use geometry::{Hit, Hittable, Ray, vec3};
|
||||
use sampling::Sampler;
|
||||
use color::Rgb;
|
||||
use geometry::{Point2, Transform2, point2};
|
||||
use sampling::PixelSample;
|
||||
|
||||
pub struct Scene {
|
||||
pub struct RenderSession {
|
||||
thread: RenderThread,
|
||||
}
|
||||
|
||||
impl RenderSession {
|
||||
pub fn new(scene: Scene, camera: Camera, film: Film, samples: u32, seed: u32) -> Self {
|
||||
Self {
|
||||
thread: RenderThread::spawn(scene, camera, film, samples, seed),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(self) -> thread::Result<RenderState> {
|
||||
self.thread.stop()
|
||||
}
|
||||
|
||||
pub fn is_finished(&self) -> bool {
|
||||
self.thread.is_finished()
|
||||
}
|
||||
|
||||
pub fn join(self) -> thread::Result<RenderState> {
|
||||
self.thread.join()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RenderState {
|
||||
Stopped(Film),
|
||||
Finished(Film),
|
||||
}
|
||||
|
||||
struct RenderThread {
|
||||
handle: JoinHandle<RenderState>,
|
||||
shutdown: Sender<()>,
|
||||
}
|
||||
|
||||
impl RenderThread {
|
||||
fn spawn(scene: Scene, camera: Camera, film: Film, samples: u32, seed: u32) -> Self {
|
||||
let (tx, rx) = channel::<()>();
|
||||
let handle = thread::spawn(move || render_thread(scene, camera, film, samples, seed, rx));
|
||||
|
||||
Self {
|
||||
handle,
|
||||
shutdown: tx,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_finished(&self) -> bool {
|
||||
self.handle.is_finished()
|
||||
}
|
||||
|
||||
fn join(self) -> thread::Result<RenderState> {
|
||||
self.handle.join()
|
||||
}
|
||||
|
||||
fn stop(self) -> thread::Result<RenderState> {
|
||||
let _ = self.shutdown.send(());
|
||||
self.handle.join()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_thread(
|
||||
scene: Scene,
|
||||
camera: Camera,
|
||||
objects: Vec<Shape>,
|
||||
}
|
||||
mut film: Film,
|
||||
samples: u32,
|
||||
seed: u32,
|
||||
shutdown: Receiver<()>,
|
||||
) -> RenderState {
|
||||
let mut sampler = film.sampler(samples, seed);
|
||||
let t = film.transform;
|
||||
let w = film.width as usize;
|
||||
|
||||
impl Scene {
|
||||
pub fn new(camera: Camera, objects: Vec<Shape>) -> Self {
|
||||
Self { camera, objects }
|
||||
}
|
||||
for (i, mut c) in film.lanes_mut().into_iter().enumerate() {
|
||||
let mut rgb = Rgb::BLACK;
|
||||
|
||||
pub fn render(&self, width: u32, height: u32, samples: u32, seed: u32) -> Box<[Rgba]> {
|
||||
let mut sampler = Sampler::new(width, height, samples, seed);
|
||||
let mut film = Film::new(width, height);
|
||||
let x = i % w;
|
||||
let y = i / w;
|
||||
|
||||
for (y, x) in Range2D::new(0..height, 0..width) {
|
||||
let mut rgb = Vector::ZERO;
|
||||
for mut pixel_sample in sampler.pixel(x as u32, y as u32) {
|
||||
let p_film = get_camera_sample(x as u32, y as u32, &mut pixel_sample, t);
|
||||
let ray = camera.ray(p_film);
|
||||
|
||||
for mut pixel_sample in sampler.pixel(x, y) {
|
||||
let p_film = film.get_camera_sample(x, y, &mut pixel_sample);
|
||||
let ray = self.camera.ray(p_film);
|
||||
|
||||
if let Some(h) = self.intersect(ray) {
|
||||
rgb += h.normal.normalize() * 0.5 + vec3(0.5, 0.5, 0.5);
|
||||
}
|
||||
if let Some(h) = scene.intersect(ray) {
|
||||
rgb += dir_color(h.normal);
|
||||
}
|
||||
|
||||
rgb /= samples as f32;
|
||||
|
||||
film.add_sample(x, y, rgba(rgb.x, rgb.y, rgb.z, 0.0), 1.0);
|
||||
}
|
||||
|
||||
film.into_data()
|
||||
}
|
||||
|
||||
fn intersect(&self, r: Ray) -> Option<Hit> {
|
||||
let mut hit: Option<Hit> = None;
|
||||
|
||||
for obj in &self.objects {
|
||||
let t_max = hit.as_ref().map(|h| h.t).unwrap_or(f32::INFINITY);
|
||||
if let Some(h) = obj.intersect(&r, t_max) {
|
||||
hit = Some(h)
|
||||
use mpsc::TryRecvError as E;
|
||||
match shutdown.try_recv() {
|
||||
Ok(_) | Err(E::Disconnected) => return RenderState::Stopped(film),
|
||||
Err(E::Empty) => (),
|
||||
}
|
||||
}
|
||||
|
||||
hit
|
||||
rgb /= samples as f32;
|
||||
for i in 0..3 {
|
||||
c[i] = rgb[i];
|
||||
}
|
||||
}
|
||||
|
||||
RenderState::Finished(film)
|
||||
}
|
||||
|
||||
fn get_camera_sample(x: u32, y: u32, sampler: &mut PixelSample, transform: Transform2) -> Point2 {
|
||||
transform * (point2(x as f32, y as f32) + sampler.get_2d().into())
|
||||
}
|
||||
|
||||
fn dir_color(v: Vector) -> Rgb {
|
||||
(v.normalize() * 0.5 + Vector::splat(0.5)).into()
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ const PERMUTATIONS: [[u8; 4]; 24] = [
|
|||
[3, 0, 1, 2], [3, 0, 2, 1], [3, 1, 0, 2], [3, 1, 2, 0], [3, 2, 0, 1], [3, 2, 1, 0],
|
||||
];
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Sampler {
|
||||
seed: u32,
|
||||
samples: u32,
|
||||
|
|
26
core/src/scene.rs
Normal file
26
core/src/scene.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use crate::{
|
||||
Shape,
|
||||
geometry::{Hit, Hittable, Ray},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Scene(Vec<Shape>);
|
||||
|
||||
impl Scene {
|
||||
pub fn new(objects: Vec<Shape>) -> Self {
|
||||
Self(objects)
|
||||
}
|
||||
|
||||
pub fn intersect(&self, r: Ray) -> Option<Hit> {
|
||||
let mut hit: Option<Hit> = None;
|
||||
|
||||
for obj in &self.0 {
|
||||
let t_max = hit.as_ref().map(|h| h.t).unwrap_or(f32::INFINITY);
|
||||
if let Some(h) = obj.intersect(&r, t_max) {
|
||||
hit = Some(h)
|
||||
}
|
||||
}
|
||||
|
||||
hit
|
||||
}
|
||||
}
|
|
@ -1,10 +1,14 @@
|
|||
#!python
|
||||
import argparse
|
||||
from PIL import Image
|
||||
from mechthild import Scene, Shape, Camera
|
||||
from mechthild import Session, Shape, Camera
|
||||
|
||||
WIDTH = 800
|
||||
HEIGHT = 600
|
||||
SAMPLES = 32
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('width', type=int, default=400, nargs='?')
|
||||
parser.add_argument('height', type=int, default=400, nargs='?')
|
||||
parser.add_argument('samples', type=int, default=32, nargs='?')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
s1 = Shape.sphere((0.75, 1.0, 1.0), 1.0)
|
||||
s2 = Shape.sphere((-1.5, 2.0, -2.0), 2.0)
|
||||
|
@ -12,10 +16,10 @@ ground = Shape.plane((0.0, 1.0, 0.0), 0.0)
|
|||
|
||||
camera = Camera((0.0, 1.5, 5.0), (0.0, 1.0, 0.0), (0.0, 1.0, 0.0), 80.0)
|
||||
|
||||
scene = Scene(camera, [s1, s2, ground])
|
||||
session = Session([s1, s2, ground], camera, args.width, args.height, args.samples)
|
||||
|
||||
render = scene.render(WIDTH, HEIGHT, SAMPLES)
|
||||
rgba = bytes([int(v * 255) for v in render])
|
||||
film = (session.get_output() * 255).astype('uint8')
|
||||
|
||||
image = Image.frombuffer('RGBA', (WIDTH, HEIGHT), rgba)
|
||||
image = Image.fromarray(film)
|
||||
image.save("test.png")
|
||||
|
||||
|
|
|
@ -6,12 +6,11 @@ authors.workspace = true
|
|||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
name = "mechthild"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
pyo3 = "0.24.0"
|
||||
mechthild_core = { path = "../core", features = ["bytemuck"] }
|
||||
bytemuck = { version = "1.22.0", features = ["extern_crate_alloc"] }
|
||||
pyo3 = { version = "0.25.0", features = ["extension-module"]}
|
||||
numpy = "0.25.0"
|
||||
mechthild_core = { path = "../core" }
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use bytemuck::allocation::cast_slice_box;
|
||||
use mechthild_core::{Camera, Scene, Shape, color::Rgba};
|
||||
use std::{mem, panic};
|
||||
|
||||
use mechthild_core::{Camera, Film, RenderSession, RenderState, Scene, Shape};
|
||||
use numpy::{Ix3, PyArray};
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass(name = "Camera")]
|
||||
|
@ -44,22 +46,86 @@ impl PyShape {
|
|||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "Scene")]
|
||||
struct PyScene(Scene);
|
||||
#[pyclass(name = "Session")]
|
||||
struct PySession(Session);
|
||||
|
||||
#[pymethods]
|
||||
impl PyScene {
|
||||
#[new]
|
||||
pub fn new(camera: &PyCamera, contents: Vec<PyShape>) -> Self {
|
||||
let contents = contents.into_iter().map(|s| s.0).collect();
|
||||
Self(Scene::new(camera.0.clone(), contents))
|
||||
enum Session {
|
||||
Running(RenderSession),
|
||||
Finished(Film),
|
||||
Stopped(Film),
|
||||
// i hate this
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl Default for Session {
|
||||
fn default() -> Self {
|
||||
Self::Empty
|
||||
}
|
||||
}
|
||||
|
||||
impl Session {
|
||||
fn new(scene: Scene, camera: Camera, film: Film, samples: u32, seed: u32) -> Self {
|
||||
Self::Running(RenderSession::new(scene, camera, film, samples, seed))
|
||||
}
|
||||
|
||||
#[pyo3(signature = (width, height, samples, seed = 0))]
|
||||
pub fn render(&self, width: u32, height: u32, samples: u32, seed: u32) -> Vec<f32> {
|
||||
let result = self.0.render(width, height, samples, seed);
|
||||
fn take(&mut self) -> Option<RenderSession> {
|
||||
let session = mem::take(self);
|
||||
|
||||
cast_slice_box::<Rgba, f32>(result).into_vec()
|
||||
if let Self::Running(s) = session {
|
||||
Some(s)
|
||||
} else {
|
||||
*self = session;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PySession {
|
||||
#[new]
|
||||
#[pyo3(signature = (objects, camera, width, height, samples, seed = 0))]
|
||||
pub fn new(
|
||||
objects: Vec<PyShape>,
|
||||
camera: &PyCamera,
|
||||
width: u32,
|
||||
height: u32,
|
||||
samples: u32,
|
||||
seed: u32,
|
||||
) -> Self {
|
||||
let film = Film::new(width, height);
|
||||
let scene = Scene::new(objects.into_iter().map(|s| s.0).collect());
|
||||
|
||||
Self(Session::new(scene, camera.0.clone(), film, samples, seed))
|
||||
}
|
||||
|
||||
pub fn wait(&mut self) {
|
||||
if let Some(session) = self.0.take() {
|
||||
*self = match session.join() {
|
||||
Ok(RenderState::Finished(f)) => Self(Session::Finished(f)),
|
||||
Ok(RenderState::Stopped(f)) => Self(Session::Stopped(f)),
|
||||
Err(e) => panic::resume_unwind(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
if let Some(session) = self.0.take() {
|
||||
*self = match session.stop() {
|
||||
Ok(RenderState::Finished(f)) => Self(Session::Finished(f)),
|
||||
Ok(RenderState::Stopped(f)) => Self(Session::Stopped(f)),
|
||||
Err(e) => panic::resume_unwind(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_output<'py>(&mut self, py: Python<'py>) -> Bound<'py, PyArray<f32, Ix3>> {
|
||||
self.wait();
|
||||
|
||||
match &self.0 {
|
||||
Session::Finished(f) => PyArray::from_array(py, &f.data),
|
||||
Session::Stopped(f) => PyArray::from_array(py, &f.data),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,6 +133,6 @@ impl PyScene {
|
|||
fn mechthild(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<PyCamera>()?;
|
||||
m.add_class::<PyShape>()?;
|
||||
m.add_class::<PyScene>()?;
|
||||
m.add_class::<PySession>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue