Implement cpu-generated text sprite
This commit is contained in:
parent
e4ccf95a2f
commit
075e8e996f
30
Cargo.lock
generated
30
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -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
BIN
assets/DejaVuSansMono.ttf
Normal file
Binary file not shown.
@ -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 {
|
||||
|
||||
30
src/main.rs
30
src/main.rs
@ -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(())
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
})],
|
||||
}),
|
||||
|
||||
@ -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
|
||||
|
||||
24
src/utils.rs
24
src/utils.rs
@ -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};
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user