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:
wires 2025-06-01 00:21:11 -04:00
parent 1141566e6f
commit d8a88e81c8
Signed by: wires
SSH key fingerprint: SHA256:9GtP+M3O2IivPDlw1UY872UPUuJH2gI0yG6ExBxaaiM
13 changed files with 442 additions and 207 deletions

124
Cargo.lock generated
View file

@ -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"

View file

@ -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"]

View file

@ -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
}
}

View file

@ -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
View 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))
}
}

View file

@ -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()

View file

@ -1,6 +0,0 @@
use bytemuck::{Pod, Zeroable};
use crate::color::Rgba;
unsafe impl Pod for Rgba {}
unsafe impl Zeroable for Rgba {}

View file

@ -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()
}

View file

@ -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
View 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
}
}

View file

@ -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")

View file

@ -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" }

View file

@ -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(())
}