Skip to content

Commit

Permalink
Add a minifb frontend and a spinning square demo
Browse files Browse the repository at this point in the history
The frontend adds support for writing simple demos with window creation,
event handling, and a main loop, using the minifb crate.
  • Loading branch information
jdahlstrom committed Oct 14, 2023
1 parent e469a7e commit 07367be
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 6 deletions.
4 changes: 4 additions & 0 deletions core/src/geom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ pub struct Tri<V>(pub [V; 3]);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct Plane<V>(pub(crate) V);

pub fn vertex<P, A>(pos: P, attrib: A) -> Vertex<P, A> {
Vertex { pos, attrib }
}
1 change: 0 additions & 1 deletion core/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 14 additions & 1 deletion demos/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]
58 changes: 58 additions & 0 deletions demos/src/bin/square.rs
Original file line number Diff line number Diff line change
@@ -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<Vec3, _>,
mvp: &Mat4x4<ModelToProjective>| {
vertex(mvp.apply(&pos.to()), attrib)
},
|frag: Frag<Color3f>| 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<ModelToView> = 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(())
});
}
11 changes: 7 additions & 4 deletions front/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, u32>, MutSlice2<'a, f32>>,
}
132 changes: 132 additions & 0 deletions front/src/minifb.rs
Original file line number Diff line number Diff line change
@@ -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<f32>,
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<f32>) -> 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<F>(&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());
}
}
}

0 comments on commit 07367be

Please sign in to comment.