diff --git a/.gitignore b/.gitignore index c8f0442..41f06c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ /target +# test artifacts +*.png + # Byte-compiled / optimized / DLL files __pycache__/ .pytest_cache/ diff --git a/Cargo.lock b/Cargo.lock index 9c64446..5e43c46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,27 @@ 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 = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "glam" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0e9b6647e9b41d3a5ef02964c6be01311a7f2472fea40595c635c6d046c259e" +dependencies = [ + "bytemuck", +] + [[package]] name = "heck" version = "0.5.0" @@ -35,11 +50,16 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "mechthild_core" version = "0.1.0" +dependencies = [ + "bytemuck", + "glam", +] [[package]] name = "mechthild_py" version = "0.1.0" dependencies = [ + "bytemuck", "mechthild_core", "pyo3", ] diff --git a/core/Cargo.toml b/core/Cargo.toml index 4c9617c..c7db622 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -7,3 +7,9 @@ license.workspace = true repository.workspace = true [dependencies] +bytemuck = { version = "1.22.0", optional = true } +glam = "0.30.2" + +[features] +fast-math = ["glam/fast-math"] +bytemuck = ["dep:bytemuck", "glam/bytemuck"] diff --git a/core/src/color.rs b/core/src/color.rs new file mode 100644 index 0000000..9498ddc --- /dev/null +++ b/core/src/color.rs @@ -0,0 +1,29 @@ +use std::fmt; + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct Rgba { + r: f32, + g: f32, + b: f32, + a: f32, +} + +impl Rgba { + pub const BLACK: Self = Self::new(0.0, 0.0, 0.0, 1.0); + + pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + Self { r, g, b, a } + } +} + +impl fmt::Debug for Rgba { + 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) + .finish() + } +} diff --git a/core/src/impl_bytemuck.rs b/core/src/impl_bytemuck.rs new file mode 100644 index 0000000..87dfaa0 --- /dev/null +++ b/core/src/impl_bytemuck.rs @@ -0,0 +1,6 @@ +use bytemuck::{Pod, Zeroable}; + +use crate::color::Rgba; + +unsafe impl Pod for Rgba {} +unsafe impl Zeroable for Rgba {} diff --git a/core/src/lib.rs b/core/src/lib.rs index 7d12d9a..d4a58dd 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,14 +1,38 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} +#[cfg(feature = "bytemuck")] +pub mod impl_bytemuck; -#[cfg(test)] -mod tests { - use super::*; +pub mod color; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); +use color::Rgba; + +pub struct Scene; + +impl Scene { + pub fn new() -> Self { + Self + } + + pub fn render(&self, width: usize, height: usize) -> Box<[Rgba]> { + let mut data = Box::new_uninit_slice(width * height); + + for (i, pixel) in data.iter_mut().enumerate() { + let x = (i % width) as f32; + let y = (i / width) as f32; + + pixel.write(Rgba::new( + x / (width - 1) as f32, + y / (height - 1) as f32, + 0.5, + 1.0, + )); + } + + unsafe { data.assume_init() } + } +} + +impl Default for Scene { + fn default() -> Self { + Self::new() } } diff --git a/examples/basic.py b/examples/basic.py new file mode 100644 index 0000000..c96e612 --- /dev/null +++ b/examples/basic.py @@ -0,0 +1,11 @@ +from PIL import Image +from mechthild import Scene + +WIDTH = 800 +HEIGHT = 600 + +render_result = Scene().render(WIDTH, HEIGHT) +rgba = bytes([int(v * 255) for v in render_result]) + +img = Image.frombuffer('RGBA', (WIDTH, HEIGHT), rgba) +img.save("test.png") diff --git a/py/Cargo.toml b/py/Cargo.toml index 37cd364..b3f8da0 100644 --- a/py/Cargo.toml +++ b/py/Cargo.toml @@ -13,4 +13,5 @@ crate-type = ["cdylib"] [dependencies] pyo3 = "0.24.0" -mechthild_core = { path = "../core" } +mechthild_core = { path = "../core", features = ["bytemuck"] } +bytemuck = { version = "1.22.0", features = ["extern_crate_alloc"] } diff --git a/py/src/lib.rs b/py/src/lib.rs index d0c059c..abbe3aa 100644 --- a/py/src/lib.rs +++ b/py/src/lib.rs @@ -1,14 +1,27 @@ +use bytemuck::allocation::cast_slice_box; +use mechthild_core::{Scene, color::Rgba}; use pyo3::prelude::*; -/// Formats the sum of two numbers as string. -#[pyfunction] -fn sum_as_string(a: usize, b: usize) -> PyResult { - Ok(mechthild_core::add(a, b).to_string()) +#[pyclass(name = "Scene")] +struct PyScene(Scene); + +#[pymethods] +impl PyScene { + #[new] + pub fn new() -> Self { + Self(Scene::new()) + } + + pub fn render(&self, width: usize, height: usize) -> Vec { + let result = self.0.render(width, height); + + cast_slice_box::(result).into_vec() + } } /// A Python module implemented in Rust. #[pymodule] fn mechthild(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + m.add_class::()?; Ok(()) }