Implement cpu-generated text sprite

This commit is contained in:
Steins7 2022-12-29 13:34:20 +01:00 committed by Steins7
parent 5aa0a70f5f
commit d334ee8f98
9 changed files with 205 additions and 38 deletions

30
Cargo.lock generated
View File

@ -147,6 +147,7 @@ dependencies = [
"cgmath",
"chrono",
"fern",
"fontdue",
"image",
"log",
"pollster",
@ -504,6 +505,16 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fontdue"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
dependencies = [
"hashbrown 0.11.2",
"ttf-parser",
]
[[package]]
name = "foreign-types"
version = "0.3.2"
@ -578,7 +589,7 @@ checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a"
dependencies = [
"bitflags",
"gpu-descriptor-types",
"hashbrown",
"hashbrown 0.12.3",
]
[[package]]
@ -590,6 +601,15 @@ dependencies = [
"bitflags",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -666,7 +686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown",
"hashbrown 0.12.3",
]
[[package]]
@ -1319,6 +1339,12 @@ dependencies = [
"serde",
]
[[package]]
name = "ttf-parser"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
[[package]]
name = "unicode-ident"
version = "1.0.5"

View File

@ -13,6 +13,7 @@ fern = { version = "^0.6.1", features = ["colored"] }
bitflags = "^1.3.2"
cgmath = { version = "^0.18.0", features = ["bytemuck"] }
pollster = "^0.2.5"
fontdue = "0.7.2"
# surface creation
winit = "^0.26.1"
@ -33,3 +34,6 @@ features = ["png", "jpeg"]
[patch.crates-io]
cgmath = { path = "cgmath" }
[profile.release]
debug = 1

BIN
assets/DejaVuSansMono.ttf Normal file

Binary file not shown.

View File

@ -80,8 +80,12 @@ impl Canvas {
)
}
pub fn create_text_sprite(&mut self, _size: Size, _scale: f32) -> TextSprite {
unimplemented!();
pub fn create_text_sprite(&mut self, text: &'static str, size: Size) -> TextSprite {
TextSprite::new(
text,
size,
&self.renderer,
)
}
pub fn create_shape_sprite(&mut self) -> ShapeSprite {

View File

@ -5,7 +5,7 @@ use canvas::{
Application,
Canvas,
texture::TextureHandle,
sprite::TextureSprite,
sprite::{TextureSprite, TextSprite},
utils::Position,
};
@ -30,7 +30,7 @@ fn setup_logger() -> Result<(), fern::InitError> {
message
))
})
.level(log::LevelFilter::Debug)
.level(log::LevelFilter::Info)
.chain(std::io::stdout())
.chain(fern::log_file("output.log")?)
.apply()?; Ok(())
@ -40,6 +40,7 @@ struct ExampleState {
pub texture: TextureHandle,
pub tex_sprite: TextureSprite,
pub sub_sprite: TextureSprite,
pub txt_sprites: Vec<TextSprite>,
pub last_instant: Instant,
pub last_offset: u32,
pub last_pos: Position,
@ -51,7 +52,10 @@ struct ExampleApp {}
impl Application<ExampleState> for ExampleApp {
fn init(canvas: &mut Canvas) -> Result<ExampleState, &'static str> {
use canvas::utils::Size;
use canvas::{
utils::Size,
sprite::Sprite,
};
//// 20 x 20 sprite of a picture
let texture = canvas.create_texture_from_file("assets/camel.jpg", None, None, None)
@ -59,9 +63,18 @@ impl Application<ExampleState> for ExampleApp {
let mut tex_sprite = canvas.create_texture_sprite(Size {w: 1280, h: 720});
tex_sprite.set_texture(texture.clone(), Some(Position {x: 0, y: 0}), 1.0);
let mut sub_sprite = canvas.create_texture_sprite(Size {w: 200, h: 200});
let mut sub_sprite = canvas.create_texture_sprite(Size {w: 100, h: 100});
sub_sprite.set_texture(texture.clone(), Some(Position {x: 350, y: 0}), 1.0);
let mut txt_sprites = Vec::<TextSprite>::with_capacity(10);
for x in 0..5 {
for y in 0..2 {
let mut sprite = canvas.create_text_sprite("00", Size {w: 200, h: 200});
sprite.set_position(Position { x: x * 100, y: y * 100});
txt_sprites.push(sprite);
}
}
canvas.clear();
canvas.update();
@ -71,6 +84,7 @@ impl Application<ExampleState> for ExampleApp {
texture,
tex_sprite,
sub_sprite,
txt_sprites,
last_instant,
last_offset: 0,
last_pos: Position::origin(),
@ -85,6 +99,9 @@ impl Application<ExampleState> for ExampleApp {
let elapsed = now.duration_since(state.last_instant).as_millis();
state.last_instant = now;
debug!("frame time: {}ms", elapsed);
for sprite in state.txt_sprites.iter_mut() {
sprite.set_text(&format!("{}", elapsed));
}
//state.sub_sprite.for_each(|pix| unsafe {pix.flat = pix.flat.wrapping_add(1);});
@ -155,7 +172,10 @@ impl Application<ExampleState> for ExampleApp {
canvas.draw(&mut state.tex_sprite);
canvas.draw(&mut state.sub_sprite);
// canvas.draw(&mut state.sub_sprite);
for sprite in state.txt_sprites.iter_mut() {
canvas.draw(sprite);
}
canvas.update();
Ok(())

View File

@ -6,25 +6,114 @@ use log::{debug, error, info, trace, warn};
use crate::{
renderer::WgpuRenderer,
sprite::Sprite,
utils::{Pixel, Position, Size},
utils::{Pixel, Color, Position, Size},
};
use super::TextureSprite;
//--External imports--------------------------------------------------------------------------------
use cgmath::Matrix4;
//--TextSprite struct-------------------------------------------------------------------------------
#[allow(dead_code)]
pub struct TextSprite {
matrix: Matrix4<f32>,
size: Size,
text: &'static str, //TODO: temporary
texture_sprite: TextureSprite,
font: [fontdue::Font; 1],
layout: fontdue::layout::Layout,
}
impl TextSprite {
pub fn set_text(&mut self, _text: &'static str) {
todo!();
pub fn new(text: &'static str, size: Size, renderer: &WgpuRenderer) -> Self {
use crate::{
renderer::Texture,
texture::TextureHandle,
};
use fontdue::layout::{CoordinateSystem, Layout, TextStyle};
//load fonts
//TODO add support for dynamic fonts
let font = include_bytes!("../../assets/DejaVuSansMono.ttf");
let settings = fontdue::FontSettings {
scale: 11.0,
..fontdue::FontSettings::default()
};
let font = [fontdue::Font::from_bytes(&font[..], settings).unwrap()];
//configure text layout to fit sprite size, keep default settings
let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
//compute text layout
layout.append(&font, &TextStyle::new(text, 40.0, 0));
//draw text to the texture
let mut raw_texture : Vec::<Pixel>
= vec![Color::NONE; (size.h * size.w).try_into().unwrap()];
//draw each glyph at the corresponding position on the texture
for glyph in layout.glyphs() {
let (_metrics, bitmap) = font[0].rasterize_config(glyph.key);
let glyph_x = glyph.x as usize;
let glyph_y = glyph.y as usize;
let size_w = size.w as usize;
let size_h = size.h as usize;
//only try to draw a glyph if its origin fits in the texture
if glyph_x < size_w && glyph_y < size_h {
let origin = glyph_x + size_w * glyph_y;
//glyphs that do not fit on the texture are cropped at the edge of the latter
for x in 0..std::cmp::min(glyph.width, size_w - glyph_x) {
for y in 0..std::cmp::min(glyph.height, size_h - glyph_y) {
raw_texture[origin + x + y * size_w]
= Color::BLACK.with_alpha(bitmap[x + y * glyph.width]);
} } } }
//create the texture
let texture = Texture::create(renderer, bytemuck::cast_slice(&raw_texture), size);
let texture_handle = TextureHandle::from_texture(texture);
let texture_sprite = TextureSprite::new(texture_handle, size, &renderer);
Self {
texture_sprite,
font,
layout,
}
}
pub fn set_text(&mut self, text: &str) {
//compute text layout
self.layout.clear();
self.layout.append(&self.font, &fontdue::layout::TextStyle::new(text, 40.0, 0));
let size = self.texture_sprite.get_size();
self.texture_sprite.fill(Color::NONE);
//draw each glyph at the corresponding position on the texture
for glyph in self.layout.glyphs() {
let (_metrics, bitmap) = self.font[0].rasterize_config(glyph.key);
let glyph_x = glyph.x as usize;
let glyph_y = glyph.y as usize;
let size_w = size.w as usize;
let size_h = size.h as usize;
//only try to draw a glyph if its origin fits in the texture
if glyph_x < size_w && glyph_y < size_h {
//glyphs that do not fit on the texture are cropped at the edge of the latter
for x in 0..std::cmp::min(glyph.width, size_w - glyph_x) {
for y in 0..std::cmp::min(glyph.height, size_h - glyph_y) {
self.texture_sprite.set_pixel(
Position {
x: (glyph_x + x) as u32,
y: (glyph_y + y) as u32,
},
Color::BLACK.with_alpha(bitmap[x + y * glyph.width])
);
} } } }
}
pub fn set_color(&mut self, _color: Pixel) {
@ -34,24 +123,24 @@ impl TextSprite {
impl Sprite for TextSprite {
fn set_position(&mut self, _pos: Position) {
todo!();
fn set_position(&mut self, pos: Position) {
self.texture_sprite.set_position(pos);
}
fn set_rotation(&mut self, _rot: f32) {
todo!();
fn set_rotation(&mut self, rot: f32) {
self.texture_sprite.set_rotation(rot);
}
fn set_alpha(&mut self, _alpha: u8) {
todo!();
fn set_alpha(&mut self, alpha: u8) {
self.texture_sprite.set_alpha(alpha);
}
fn set_scale(&mut self, _scale: f32) {
todo!();
fn set_scale(&mut self, scale: f32) {
self.texture_sprite.set_scale(scale);
}
fn render(&mut self, _renderer: &mut WgpuRenderer) {
todo!();
fn render(&mut self, renderer: &mut WgpuRenderer) {
self.texture_sprite.render(renderer);
}
}

View File

@ -42,8 +42,7 @@ impl TextureSprite {
];
pub fn new(texture: TextureHandle,
size: Size,
renderer: &WgpuRenderer)
size: Size, renderer: &WgpuRenderer)
-> Self
{
// initialize pipeline if needed
@ -91,11 +90,20 @@ impl TextureSprite {
self.texture.for_each_in_area(func, self.offset, self.inner_size);
}
pub fn fill(&mut self, pixel: Pixel) {
self.texture.fill(pixel);
}
pub fn get_size(&self) -> Size {
self.texture.get_size()
}
fn update_vertices(&mut self, renderer: &WgpuRenderer) {
debug!("Updating vertices...");
let size = self.texture.get_size();
// compute normalized coordinates
let x_size = self.inner_size.w as f32 / size.w as f32 * self.scale;
let y_size = self.inner_size.h as f32 / size.h as f32 * self.scale;
@ -235,7 +243,7 @@ fn initialize_pipeline(renderer: &WgpuRenderer) -> wgpu::RenderPipeline {
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: renderer.config.format,
blend: Some(wgpu::BlendState::REPLACE),
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),

View File

@ -60,6 +60,12 @@ impl TextureHandle {
texture.is_synced = false;
}
pub fn fill (&mut self, pixel: Pixel) {
let mut texture = self.texture.borrow_mut();
texture.buffer.fill(pixel);
texture.is_synced = false;
}
pub fn texture(&self) -> &Rc<RefCell<Texture>> {
//TODO improve that
&self.texture

View File

@ -8,7 +8,7 @@ use cgmath::Vector3;
//--Position struct---------------------------------------------------------------------------------
#[derive(Copy, Clone)]
pub struct Position {
pub x: u32,
pub x: u32,
pub y: u32,
}
@ -23,7 +23,7 @@ impl Position {
}
impl From<Position> for Vector3<f32> {
fn from(pos: Position) -> Self {
Self::new(pos.x as f32, pos.y as f32, 0.0)
}
@ -52,7 +52,7 @@ impl From<Size> for winit::dpi::Size {
fn from(size: Size) -> Self {
winit::dpi::Size::Physical (
winit::dpi::PhysicalSize {
width: size.w,
width: size.w,
height: size.h,
})
}
@ -98,13 +98,23 @@ pub union Pixel {
unsafe impl bytemuck::Pod for Pixel {}
impl Pixel {
pub fn rgb(r: u8, g: u8, b: u8) -> Pixel {
Pixel {
rgba: Rgba{r, g, b, a: 255},
}
}
pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Pixel {
Pixel {
rgba: Rgba{r, g, b, a},
}
}
pub fn with_alpha(self, a: u8) -> Pixel {
unsafe { Pixel::rgba(self.rgba.r, self.rgba.g, self.rgba.b, a) }
}
pub fn to_rgba(&self) -> Rgba {
unsafe { self.rgba }
}
@ -117,10 +127,10 @@ pub struct Color;
impl Color {
pub const NONE: Pixel = Pixel {flat: 0x00000000};
pub const WHITE: Pixel = Pixel {flat: 0xffffffff};
pub const BLACK: Pixel = Pixel {flat: 0x000000ff};
pub const BLACK: Pixel = Pixel {flat: 0xff000000};
pub const RED: Pixel = Pixel {flat: 0xff0000ff};
pub const GREEN: Pixel = Pixel {flat: 0x00ff00ff};
pub const BLUE: Pixel = Pixel {flat: 0x0000ffff};
pub const GREEN: Pixel = Pixel {flat: 0xff00ff00};
pub const BLUE: Pixel = Pixel {flat: 0xffff0000};
}