Implement simple text renderer

This commit is contained in:
Sijmen 2021-06-01 19:58:11 +02:00
parent 4af16fb974
commit e78878150d
Signed by: vijfhoek
GPG key ID: 82D05C89B28B0DAE
11 changed files with 288 additions and 3 deletions

BIN
assets/font/accented.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/font/ascii.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/font/ascii_sga.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/font/ascii_shadow.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/font/nonlatin_european.png (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -9,6 +9,7 @@ mod uniforms;
mod vertex;
mod world;
mod render_context;
mod text_renderer;
use std::time::{Duration, Instant};
use wgpu::SwapChainError;

View file

@ -1,9 +1,17 @@
use std::time::{Duration, Instant};
use cgmath::Vector3;
use wgpu::{
util::{BufferInitDescriptor, DeviceExt},
CommandEncoder, Device, Queue, SwapChainDescriptor, SwapChainTexture,
};
use crate::{render_context::RenderContext, texture::Texture, vertex::Vertex};
use crate::{
render_context::RenderContext,
text_renderer::{self, TextRenderer},
texture::Texture,
vertex::Vertex,
};
const UI_SCALE_X: f32 = 0.0045;
const UI_SCALE_Y: f32 = 0.008;
@ -13,6 +21,20 @@ pub struct HudState {
render_pipeline: wgpu::RenderPipeline,
crosshair_vertex_buffer: wgpu::Buffer,
crosshair_index_buffer: wgpu::Buffer,
text_renderer: TextRenderer,
fps_vertex_buffer: wgpu::Buffer,
fps_index_buffer: wgpu::Buffer,
fps_index_count: usize,
fps_instant: Instant,
fps_frames: u32,
fps_elapsed: Duration,
coordinates_vertex_buffer: wgpu::Buffer,
coordinates_index_buffer: wgpu::Buffer,
coordinates_index_count: usize,
coordinates_last: Vector3<f32>,
}
impl HudState {
@ -40,11 +62,66 @@ impl HudState {
usage: wgpu::BufferUsage::INDEX,
});
let text_renderer = TextRenderer::new(render_context).unwrap();
let (fps_vertex_buffer, fps_index_buffer, fps_index_count) =
text_renderer.string_to_buffers(&render_context, -0.98, 0.97, "");
let (coordinates_vertex_buffer, coordinates_index_buffer, coordinates_index_count) =
text_renderer.string_to_buffers(&render_context, -0.98, 0.97 - text_renderer::DY, "");
Self {
texture_bind_group,
render_pipeline,
crosshair_vertex_buffer,
crosshair_index_buffer,
text_renderer,
fps_vertex_buffer,
fps_index_buffer,
fps_index_count,
fps_instant: Instant::now(),
fps_frames: 0,
fps_elapsed: Duration::from_secs(0),
coordinates_vertex_buffer,
coordinates_index_buffer,
coordinates_index_count,
coordinates_last: Vector3::new(0.0, 0.0, 0.0),
}
}
pub fn update(&mut self, render_context: &RenderContext, position: &Vector3<f32>) {
let elapsed = self.fps_instant.elapsed();
self.fps_instant = Instant::now();
self.fps_elapsed += elapsed;
self.fps_frames += 1;
if self.fps_elapsed.as_millis() >= 500 {
let frametime = self.fps_elapsed / self.fps_frames;
let fps = 1.0 / frametime.as_secs_f32();
let string = format!("{:<5.0} fps", fps);
let (vertices, indices, index_count) =
self.text_renderer
.string_to_buffers(render_context, -0.98, 0.97, &string);
self.fps_vertex_buffer = vertices;
self.fps_index_buffer = indices;
self.fps_index_count = index_count;
self.fps_elapsed = Duration::from_secs(0);
self.fps_frames = 0;
}
if position != &self.coordinates_last {
let string = format!("({:.1},{:.1},{:.1})", position.x, position.y, position.z,);
let (vertices, indices, index_count) = self.text_renderer.string_to_buffers(
render_context,
-0.98,
0.97 - text_renderer::DY * 1.3,
&string,
);
self.coordinates_vertex_buffer = vertices;
self.coordinates_index_buffer = indices;
self.coordinates_index_count = index_count;
}
}
@ -76,6 +153,19 @@ impl HudState {
render_pass.set_bind_group(0, &self.texture_bind_group, &[]);
render_pass.draw_indexed(0..CROSSHAIR_INDICES.len() as u32, 0, 0..1);
render_pass.set_vertex_buffer(0, self.fps_vertex_buffer.slice(..));
render_pass.set_index_buffer(self.fps_index_buffer.slice(..), wgpu::IndexFormat::Uint16);
render_pass.set_bind_group(0, &self.text_renderer.bind_group, &[]);
render_pass.draw_indexed(0..self.fps_index_count as u32, 0, 0..1);
render_pass.set_vertex_buffer(0, self.coordinates_vertex_buffer.slice(..));
render_pass.set_index_buffer(
self.coordinates_index_buffer.slice(..),
wgpu::IndexFormat::Uint16,
);
render_pass.set_bind_group(0, &self.text_renderer.bind_group, &[]);
render_pass.draw_indexed(0..self.coordinates_index_count as u32, 0, 0..1);
Ok(CROSSHAIR_INDICES.len() / 3)
}

View file

@ -3,6 +3,7 @@ pub mod world_state;
use std::time::Duration;
use cgmath::EuclideanSpace;
use winit::{
dpi::PhysicalSize,
event::{DeviceEvent, ElementState, KeyboardInput, VirtualKeyCode},
@ -157,6 +158,10 @@ impl State {
pub fn update(&mut self, dt: Duration) {
self.world_state.update(dt, &self.render_context);
self.hud_state.update(
&self.render_context,
&self.world_state.camera.position.to_vec(),
);
}
pub fn render(&mut self) -> anyhow::Result<usize> {

View file

@ -50,7 +50,7 @@ pub struct WorldState {
impl WorldState {
fn create_textures(render_context: &RenderContext) -> TextureManager {
let mut texture_manager = TextureManager::new(&render_context.device);
let mut texture_manager = TextureManager::new(&render_context);
texture_manager.load_all(render_context).unwrap();
texture_manager
}

174
src/text_renderer.rs Normal file
View file

@ -0,0 +1,174 @@
use std::convert::TryInto;
use wgpu::util::{BufferInitDescriptor, DeviceExt};
use crate::{render_context::RenderContext, texture::Texture, vertex::Vertex};
pub const DX: f32 = 20.0 / 640.0;
pub const DY: f32 = 20.0 / 360.0;
#[rustfmt::skip]
const CHARACTER_WIDTHS: [i32; 16 * 8] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
4, 2, 4, 6, 6, 6, 6, 2, 4, 4, 4, 6, 2, 6, 2, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 5, 6, 5, 6,
7, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 4, 6, 6,
3, 6, 6, 6, 6, 6, 6, 6, 6, 2, 6, 5, 3, 6, 6, 6,
6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 4, 2, 4, 7, 0,
];
pub struct TextRenderer {
pub texture: Texture,
pub bind_group: wgpu::BindGroup,
}
impl TextRenderer {
pub fn new(render_context: &RenderContext) -> anyhow::Result<Self> {
let bytes = std::fs::read("assets/font/ascii_shadow.png")?;
let texture = Texture::from_bytes(render_context, &bytes, "font")?;
let sampler = render_context
.device
.create_sampler(&wgpu::SamplerDescriptor {
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
..wgpu::SamplerDescriptor::default()
});
let bind_group_layout =
render_context
.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Font texture bind group layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler {
comparison: false,
filtering: true,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
],
});
let bind_group = render_context
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&("Font texture bind group")),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&texture.view),
},
],
});
Ok(Self {
texture,
bind_group,
})
}
fn char_uv(c: u8) -> (f32, f32) {
let row = (c / 16) as f32;
let column = (c % 16) as f32;
(column / 16.0, row / 16.0)
}
pub fn char_geometry(
&self,
x: f32,
y: f32,
c: u8,
index_offset: u16,
) -> ([Vertex; 4], [u16; 6]) {
let (tx, ty) = Self::char_uv(c);
let s = 1.0 / 16.0;
#[rustfmt::skip]
let vertices = [
Vertex { position: [x, y, 0.0], texture_coordinates: [tx, ty, 0.0], ..Default::default() },
Vertex { position: [x + DX, y, 0.0], texture_coordinates: [tx + s, ty, 0.0], ..Default::default() },
Vertex { position: [x + DX, y - DY, 0.0], texture_coordinates: [tx + s, ty + s, 0.0], ..Default::default() },
Vertex { position: [x, y - DY, 0.0], texture_coordinates: [tx, ty + s, 0.0], ..Default::default() },
];
#[rustfmt::skip]
let indices = [
1 + index_offset, index_offset, 2 + index_offset,
2 + index_offset, index_offset, 3 + index_offset,
];
(vertices, indices)
}
pub fn string_geometry(&self, mut x: f32, mut y: f32, string: &str) -> (Vec<Vertex>, Vec<u16>) {
let mut vertices = Vec::new();
let mut indices = Vec::new();
// TODO unicode?? ? ???
let ascii = string.replace(|c: char| !c.is_ascii(), "");
for &c in ascii.as_bytes() {
let index_offset = vertices.len().try_into().unwrap();
let (v, i) = self.char_geometry(x, y, c, index_offset);
vertices.extend(&v);
indices.extend(&i);
x += DX * (CHARACTER_WIDTHS[c as usize] as f32 / 8.0);
if x >= 1.0 {
x = 0.0;
y -= DY;
}
}
(vertices, indices)
}
pub fn string_to_buffers(
&self,
render_context: &RenderContext,
x: f32,
y: f32,
string: &str,
) -> (wgpu::Buffer, wgpu::Buffer, usize) {
let (vertices, indices) = self.string_geometry(x, y, string);
let vertex_buffer = render_context
.device
.create_buffer_init(&BufferInitDescriptor {
label: Some("font renderer"),
contents: bytemuck::cast_slice(&vertices),
usage: wgpu::BufferUsage::VERTEX,
});
let index_buffer = render_context
.device
.create_buffer_init(&BufferInitDescriptor {
label: Some("font renderer"),
contents: bytemuck::cast_slice(&indices),
usage: wgpu::BufferUsage::INDEX,
});
(vertex_buffer, index_buffer, indices.len())
}
}

View file

@ -1,7 +1,7 @@
use std::mem::size_of;
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
#[derive(Copy, Clone, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex {
pub position: [f32; 3],
pub texture_coordinates: [f32; 3],