Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ repository = "https://github.com/jdahlstrom/retrofire"
documentation = "https://crates.io/crates/retrofire"

[workspace.lints]
rust.mismatched_lifetime_syntaxes = "deny"
clippy.manual_range_contains = "allow"
clippy.collapsible_if = "allow"

Expand Down Expand Up @@ -73,7 +74,6 @@ lto = "thin"
opt-level = 1
split-debuginfo = "unpacked"


[[bench]]
name = "fill"
harness = false
Expand Down
16 changes: 13 additions & 3 deletions core/src/render/tex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ impl<C> Atlas<C> {
Self { layout, texture }
}

pub fn grid(glyph_dims: Dims, texture: Texture<Buf2<C>>) -> Self {
Self::new(Layout::Grid { sub_dims: glyph_dims }, texture)
}

/// Returns the top-left and bottom-right pixel coordinates
/// of the sub-texture with index `i`.
fn rect(&self, i: u32) -> [Point2u; 2] {
Expand All @@ -167,13 +171,13 @@ impl<C> Atlas<C> {
self.texture.data.slice(p0..p1).into()
}

/// Returns the texture coordinates of the sub-texture with index `i`.
/// Returns the texture coordinates of a sub-texture.
///
/// The coordinates are the top-left, top-right, bottom-left, and
/// bottom-right corners of the texture, in that order.
///
/// Note that currently this method does not check `i` is actually a valid
/// index and may return coordinates with values greater than one.
/// Note that currently this method does not check whether `i` is actually
/// a valid index, and may return coordinates with values greater than one.
// TODO Error handling, more readable result type
pub fn coords(&self, i: u32) -> [TexCoord; 4] {
let tex_w = self.texture.width();
Expand All @@ -183,6 +187,12 @@ impl<C> Atlas<C> {
.map(|p| (p.x() as f32 / tex_w, p.y() as f32 / tex_h));
[uv(x0, y0), uv(x1, y0), uv(x0, y1), uv(x1, y1)]
}

/// Returns the width and height of a sub-texture, in pixels.
pub fn dims(&self, _: u32) -> Dims {
let Layout::Grid { sub_dims } = self.layout;
sub_dims
}
}

//
Expand Down
131 changes: 104 additions & 27 deletions core/src/render/text.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
use core::fmt;
use core::fmt::{self, Write};
use core::ops::Range;
#[cfg(feature = "std")]
use std::io;

use crate::geom::{Mesh, tri, vertex};
use crate::math::{Color3, Point2, Vec2, pt2, vec2, vec3};
use crate::geom::{Mesh, Tri, Vertex, Vertex3, tri, vertex};
use crate::math::{
Color3, Color4, Point2, Point2u, ProjMat3, ProjVec3, Vec2, color::gray,
orthographic, pt2, pt3, vec2, vec3, viewport,
};
use crate::util::buf::Buf2;

use super::tex::{Atlas, Layout, SamplerClamp, TexCoord};
use super::{
Batch, Context, Frag, FragmentShader, Model, Target, VertexShader,
tex::{Atlas, Layout, SamplerClamp, TexCoord},
};

/// Text represented as texture-mapped geometry, one quad per glyph.
#[derive(Clone)]
Expand All @@ -18,6 +25,42 @@ pub struct Text {
cursor: Point2,
}

pub struct Console {
text: Text,
left_top: Point2u,
right_bot: Point2u,
}

pub struct TextShader<'a>(&'a Text);

impl VertexShader<Vertex3<TexCoord>, ProjMat3<Model>> for TextShader<'_> {
type Output = Vertex<ProjVec3, TexCoord>;

fn shade_vertex(
&self,
v: Vertex3<TexCoord>,
tf: ProjMat3<Model>,
) -> Self::Output {
vertex(tf.apply(&v.pos), v.attrib)
}
}

impl FragmentShader<TexCoord> for TextShader<'_> {
fn shade_fragment(&self, f: Frag<TexCoord>) -> Option<Color4> {
let c = self.0.sample(f.var);
(c != gray(0)).then_some(c.to_rgba())
}
}

pub type TextBatch<'a> = Batch<
Tri<usize>,
Vertex3<TexCoord>,
ProjMat3<Model>,
TextShader<'a>,
(),
Context,
>;

//
// Inherent impls
//
Expand Down Expand Up @@ -64,7 +107,7 @@ impl Text {
fn write_char(&mut self, idx: u32) {
let Self { font, geom, cursor, .. } = self;

let Layout::Grid { sub_dims: (gw, gh) } = font.layout;
let (gw, gh) = font.dims(idx);
let (glyph_w, glyph_h) = (gw as f32, gh as f32);

let [tl, tr, bl, br] = font.coords(idx);
Expand All @@ -90,6 +133,60 @@ impl Text {

*cursor += vec2(glyph_w, 0.0);
}

fn newline(&mut self) {
let Layout::Grid { sub_dims } = self.font.layout;
// TODO variable line height support
self.cursor = pt2(0.0, self.cursor.y() + sub_dims.1 as f32)
}
}

impl Console {
pub fn new(font: Atlas<Color3>, bounds: Range<Point2u>) -> Self {
Self {
text: Text::new(font),
left_top: bounds.start,
right_bot: bounds.end,
}
}

pub fn print(&mut self, s: &str) {
_ = self.text.write_str(s);
}
pub fn println(&mut self, s: &str) {
self.print(s);
self.print("\n");
}

pub fn write_fmt(&mut self, args: fmt::Arguments) {
_ = self.text.write_fmt(args)
}

pub fn clear(&mut self) {
self.text.clear();
}

pub fn batch(&self) -> TextBatch<'_> {
let Self {
left_top: lt, right_bot: rb, ..
} = self;

let [w, h] = (*rb - *lt).0;
let projection =
orthographic(pt3(0.0, 0.0, 0.0), pt3(w as f32, h as f32, 0.0));

let viewport = viewport(*lt..*rb);

Batch::new()
.mesh(&self.text.geom)
.uniform(projection.to())
.shader(TextShader(&self.text))
.viewport(viewport)
}

pub fn render(&self, target: impl Target, ctx: &Context) {
self.batch().target(target).context(ctx).render();
}
}

//
Expand All @@ -110,18 +207,9 @@ impl io::Write for Text {
///
/// [1]: https://en.wikipedia.org/wiki/Mojibake
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
/*let (rows, cols) = buf
.split(|&b| b == b'\n')
.fold((0, 0), |(rs, cs), row| (rs + 1, cs.max(row.len() as u32)));
if rows == 0 || cols == 0 {
return Ok(0);
}*/
let Layout::Grid { sub_dims } = self.font.layout;
let glyph_h = sub_dims.1 as f32;

for &b in buf {
match b {
b'\n' => self.cursor = pt2(0.0, self.cursor.y() + glyph_h),
b'\n' => self.newline(),
_ => self.write_char(b.into()),
}
}
Expand All @@ -140,20 +228,9 @@ impl fmt::Write for Text {
/// of each `char` as an index into the font. As such, the font should have
/// enough glyphs to cover all the characters used.
fn write_str(&mut self, s: &str) -> fmt::Result {
/*let (rows, cols) = s
.split(|c| c == '\n')
.fold((0, 0), |(rs, cs), row| {
(rs + 1, cs.max(row.chars().count() as u32))
});
if rows == 0 || cols == 0 {
return Ok(());
}*/
let Layout::Grid { sub_dims } = self.font.layout;
let glyph_h = sub_dims.1 as f32;

for c in s.chars() {
match c {
'\n' => self.cursor = pt2(0.0, self.cursor.y() + glyph_h),
'\n' => self.newline(),
_ => self.write_char(c.into()),
}
}
Expand Down
Binary file added demos/assets/font_6x10.pbm
Binary file not shown.
Binary file added demos/assets/font_8x12.pbm
Binary file not shown.
13 changes: 12 additions & 1 deletion demos/src/bin/crates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use re::core::render::{
clip::Status::*,
scene::Obj,
shader,
tex::SamplerClamp,
tex::{Atlas, SamplerClamp},
text::Console,
};
// Try also Rgb565 or Rgba4444
use re::core::util::{pixfmt::Rgba8888, pnm::read_pnm};
Expand All @@ -20,6 +21,7 @@ fn main() {
let mut win = Window::builder()
.title("retrofire//crates")
.pixel_fmt(Rgba8888)
.vsync(false)
.build()
.expect("should create window");

Expand Down Expand Up @@ -56,6 +58,11 @@ fn main() {
let floor = floor();
let crates = crates();

static FONT: &[u8] = include_bytes!("../../assets/font_6x10.pbm");
let font = Atlas::grid((6, 10), read_pnm(FONT).expect("font exists").into());

let mut con = Console::new(font, pt2(20, 20)..pt2(400, 200));

win.run(|frame| {
//
// Camera
Expand Down Expand Up @@ -132,6 +139,10 @@ fn main() {
frame.ctx.stats.borrow_mut().objs.o += 1;
}

con.clear();
write!(con, "{}", frame.ctx.stats.borrow());
con.render(frame.buf, frame.ctx);

Continue(())
})
.expect("should run");
Expand Down
12 changes: 10 additions & 2 deletions front/src/sdl2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,11 @@ impl<PF: PixelFmt<Pixel = [u8; N]>, const N: usize> Window<PF> {
let mut tex = tc.create_texture_streaming(PF::SDL_FMT, w, h)?;

let mut zbuf = Buf2::new(dims);
let mut ctx = self.ctx.clone();
let ctx = self.ctx.clone();

let start = Instant::now();
let mut last = Instant::now();
let stats = RefCell::new(Stats::start());
'main: loop {
self.events.clear();
for e in self.ev_pump.poll_iter() {
Expand All @@ -208,6 +209,11 @@ impl<PF: PixelFmt<Pixel = [u8; N]>, const N: usize> Window<PF> {
}
}

let mut frame_ctx = Context {
stats: stats.clone(),
..ctx.clone()
};

let cf = tex.with_lock(None, |bytes, pitch| {
let bytes = bytes.as_chunks_mut().0;
let pitch = pitch / N;
Expand All @@ -232,11 +238,13 @@ impl<PF: PixelFmt<Pixel = [u8; N]>, const N: usize> Window<PF> {
dt: replace(&mut last, Instant::now()).elapsed(),
buf: &RefCell::new(buf),
win: self,
ctx: &mut ctx,
ctx: &mut frame_ctx,
};
frame_fn(frame)
})?;

*ctx.stats.borrow_mut() += frame_ctx.stats.into_inner();

self.present(&tex)?;
ctx.stats.borrow_mut().frames += 1.0;

Expand Down