Implement simple text renderer
This commit is contained in:
parent
4af16fb974
commit
e78878150d
11 changed files with 288 additions and 3 deletions
BIN
assets/font/accented.png
(Stored with Git LFS)
Normal file
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
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
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
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
BIN
assets/font/nonlatin_european.png
(Stored with Git LFS)
Normal file
Binary file not shown.
|
@ -9,6 +9,7 @@ mod uniforms;
|
|||
mod vertex;
|
||||
mod world;
|
||||
mod render_context;
|
||||
mod text_renderer;
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
use wgpu::SwapChainError;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
174
src/text_renderer.rs
Normal 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())
|
||||
}
|
||||
}
|
|
@ -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],
|
||||
|
|
Loading…
Reference in a new issue