diff --git a/core/src/geom.rs b/core/src/geom.rs index 426419b5..5ba5b6f4 100644 --- a/core/src/geom.rs +++ b/core/src/geom.rs @@ -13,3 +13,7 @@ pub struct Tri(pub [V; 3]); #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(transparent)] pub struct Plane(pub(crate) V); + +pub fn vertex(pos: P, attrib: A) -> Vertex { + Vertex { pos, attrib } +} diff --git a/core/src/render.rs b/core/src/render.rs index ed025373..dddc98d0 100644 --- a/core/src/render.rs +++ b/core/src/render.rs @@ -18,7 +18,6 @@ use crate::math::mat::{RealToProjective, RealToReal}; use crate::math::{Linear, Mat4x4, Vec3}; pub mod clip; -pub mod ctx; pub mod raster; pub mod shader; pub mod target; diff --git a/demos/Cargo.toml b/demos/Cargo.toml index d18c661b..9f5aa7ce 100644 --- a/demos/Cargo.toml +++ b/demos/Cargo.toml @@ -1,9 +1,22 @@ [package] name = "retrofire-demos" -version = "0.1.0" +version = "0.3.0" edition = "2021" license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +re = { path = "../core", package="retrofire-core", features = ["std"] } +rf = { path = "../front", package = "retrofire-front" } + +minifb = { version = "0.25.0", optional = true } +softbuffer = { version = "0.3.0", optional = true } +winit = { version = "0.28.6", optional = true } + +[features] +minifb = ["dep:minifb", "rf/minifb"] + +[[bin]] +name = "square" +required-features = ["minifb"] diff --git a/demos/src/bin/square.rs b/demos/src/bin/square.rs new file mode 100644 index 00000000..19904bff --- /dev/null +++ b/demos/src/bin/square.rs @@ -0,0 +1,58 @@ +#![cfg(feature = "minifb")] + +use std::ops::ControlFlow::Continue; + +use re::geom::{vertex, Tri, Vertex}; +use re::math::color::{rgb, Color3f}; +use re::math::mat::{perspective, rotate_z, translate, viewport}; +use re::math::{rads, vec2, vec3, Mat4x4, Vec3}; +use re::render::raster::Frag; +use re::render::shader::Shader; +use re::render::{render, ModelToProjective, ModelToView}; +use rf::minifb::Window; + +fn main() { + let verts = [ + vertex(vec3(-1.0, -1.0, 0.0), rgb(1.0, 0.3, 0.1)), + vertex(vec3(-1.0, 1.0, 0.0), rgb(0.2, 0.8, 0.3)), + vertex(vec3(1.0, -1.0, 0.0), rgb(0.2, 0.5, 1.0)), + vertex(vec3(1.0, 1.0, 0.0), rgb(1.0, 0.3, 0.1)), + ]; + + let mut win = Window::builder() + .title("minifb front demo") + .size(640, 480) + .build(); + + let shader = Shader::new( + |Vertex { pos, attrib }: Vertex, + mvp: &Mat4x4| { + vertex(mvp.apply(&pos.to()), attrib) + }, + |frag: Frag| frag.var.to_color4(), + ); + + let modelview = translate(vec3(0.0, 0.0, 2.0)); + let project = perspective(1.0, 4.0 / 3.0, 0.1..1000.0); + let viewport = viewport(vec2(10, 10)..vec2(630, 470)); + + win.run(|frame| { + let secs = frame.t.as_secs_f32(); + + let mv: Mat4x4 = modelview + .compose(&rotate_z(rads(secs))) + .compose(&translate(vec3(0.0, 0.0, secs.sin()))) + .to(); + let mvp = mv.then(&project); + + render( + &[Tri([0, 1, 2]), Tri([3, 2, 1])], + &verts, + &shader, + &mvp, + viewport, + &mut frame.buf, + ); + Continue(()) + }); +} diff --git a/front/src/lib.rs b/front/src/lib.rs index da4bd66a..292e56a5 100644 --- a/front/src/lib.rs +++ b/front/src/lib.rs @@ -4,8 +4,11 @@ pub mod minifb; #[cfg(feature = "sdl2")] pub mod sdl2; -#[cfg(test)] -mod tests { - #[test] - fn test() {} +pub struct Frame<'a> { + // Time since first frame. + pub t: Duration, + // Time since last frame. + pub dt: Duration, + // Framebuffer in which to draw. + pub buf: Framebuf, MutSlice2<'a, f32>>, } diff --git a/front/src/minifb.rs b/front/src/minifb.rs index e69de29b..d39086ed 100644 --- a/front/src/minifb.rs +++ b/front/src/minifb.rs @@ -0,0 +1,132 @@ +//! Frontend using the `minifb` crate for window creation and event handling. + +use std::ops::ControlFlow::{self, Break}; +use std::time::{Duration, Instant}; + +use minifb::{Key, KeyRepeat, WindowOptions}; + +use retrofire_core::render::target::Framebuf; +use retrofire_core::util::buf::{AsMutSlice2, Buf2}; + +use crate::Frame; + +pub struct Window { + /// The wrapped minifb window. + pub imp: minifb::Window, + /// The width and height of the window. + pub size: (usize, usize), +} + +/// Builder for creating `Window`s. +pub struct Builder<'title> { + pub size: (usize, usize), + pub title: &'title str, + pub max_fps: Option, + pub opts: WindowOptions, +} + +impl Default for Builder<'_> { + fn default() -> Self { + Self { + size: (800, 600), + title: "// retrofire application //", + max_fps: Some(60.0), + opts: WindowOptions::default(), + } + } +} + +impl<'t> Builder<'t> { + /// Sets the width and height of the window. + pub fn size(mut self, w: usize, h: usize) -> Self { + self.size = (w, h); + self + } + /// Sets the title of the window. + pub fn title(mut self, title: &'t str) -> Self { + self.title = title; + self + } + /// Sets the fps cap of the window. + pub fn max_fps(mut self, fps: Option) -> Self { + self.max_fps = fps; + self + } + /// Sets other `minifb` options. + pub fn options(mut self, opts: WindowOptions) -> Self { + self.opts = opts; + self + } + + /// Creates the window. + pub fn build(self) -> Window { + let mut imp = minifb::Window::new( + self.title, + self.size.0, + self.size.1, + self.opts, + ) + .unwrap(); + imp.limit_update_rate( + self.max_fps + .map(|fps| Duration::from_secs_f32(1.0 / fps)), + ); + Window { + imp, + size: self.size, + } + } +} + +impl Window { + /// Returns a window builder. + pub fn builder() -> Builder<'static> { + Builder::default() + } + + /// Updates the window content with `fb`. + /// # Panics + pub fn present(&mut self, fb: &[u32]) { + let (w, h) = self.size; + self.imp.update_with_buffer(fb, w, h).unwrap(); + } + + /// Repeatedly calls `frame_fn` + pub fn run(&mut self, mut frame_fn: F) + where + F: FnMut(&mut Frame) -> ControlFlow<()>, + { + let (w, h) = self.size; + let mut cb = Buf2::new_default(w, h); + let mut zb = Buf2::new_default(w, h); + + let start = Instant::now(); + let mut last = Instant::now(); + loop { + if !self.imp.is_open() + || self + .imp + .is_key_pressed(Key::Escape, KeyRepeat::No) + { + return; + } + + cb.fill(0); + zb.fill(f32::INFINITY); + + let frame = &mut Frame { + t: start.elapsed(), + dt: last.elapsed(), + buf: Framebuf { + color_buf: cb.as_mut_slice2(), + depth_buf: zb.as_mut_slice2(), + }, + }; + last = Instant::now(); + if let Break(()) = frame_fn(frame) { + return; + } + self.present(cb.data_mut()); + } + } +}