use std::mem;
use bytemuck::{Pod, Zeroable};
use owned_ttf_parser::AsFaceRef;
use wgpu::{
util::{self, BufferInitDescriptor, DeviceExt, StagingBelt}, vertex_attr_array, BindGroup, BindGroupDescriptor,
BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource,
BindingType, BlendComponent, BlendFactor, BlendOperation, BlendState, Buffer, BufferBinding,
BufferBindingType, BufferDescriptor, BufferSize, BufferUsages, ColorTargetState, ColorWrites,
Device, FilterMode, FragmentState, FrontFace, MultisampleState, PipelineLayoutDescriptor,
PrimitiveState, PrimitiveTopology, RenderPass, RenderPipeline, RenderPipelineDescriptor,
SamplerBindingType, SamplerDescriptor, ShaderModuleDescriptor, ShaderSource, ShaderStages,
SurfaceConfiguration, TextureSampleType, TextureViewDimension, VertexAttribute, VertexBufferLayout,
VertexFormat, VertexState, VertexStepMode
};
use crate::{atlas::Atlas, ortho::orthographic_projection_matrix, typewriter::Paragraph, FontStore};
pub struct TextRenderer {
pipeline: RenderPipeline,
uniforms: Buffer,
vertices: Buffer,
indices: Buffer,
instances_buffer: Option<Buffer>,
instances: Vec<Instance>,
constants: BindGroup,
texture: BindGroup,
texture_version: usize,
texture_layout: BindGroupLayout,
screen_size: [u32; 2],
}
impl TextRenderer {
pub fn new(device: &Device, surface_config: &SurfaceConfiguration, atlas: &Atlas) -> Self {
let screen_size = [surface_config.width, surface_config.height];
let sampler = device.create_sampler(&SamplerDescriptor {
label: Some("Text sampler"),
mag_filter: FilterMode::Nearest,
min_filter: FilterMode::Nearest,
mipmap_filter: FilterMode::Nearest,
lod_min_clamp: 0f32,
lod_max_clamp: 0f32,
..Default::default()
});
let constant_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("Text constants layout"),
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size:BufferSize::new(
mem::size_of::<Params>() as u64,
),
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Sampler(
SamplerBindingType::NonFiltering,
),
count: None,
}
],
});
let uniforms = device.create_buffer_init(&BufferInitDescriptor {
label: Some("Text uniforms buffer"),
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
contents: bytemuck::bytes_of(&Params {
screen_resolution: Resolution {
width: screen_size[0],
height: screen_size[1],
},
_pad: [0, 0],
transform: orthographic_projection_matrix(0., screen_size[0] as f32, screen_size[1] as f32, 0.)
}),
});
let constant_bind_group = device.create_bind_group(&BindGroupDescriptor {
label: Some("Text texture bind group"),
layout: &constant_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::Buffer(
BufferBinding {
buffer: &uniforms,
offset: 0,
size: None,
},
),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&sampler),
},
],
});
let texture_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("Text texture layout"),
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
ty: BindingType::Texture {
sample_type: TextureSampleType::Float { filterable: false },
view_dimension: TextureViewDimension::D2Array,
multisampled: false,
},
count: None,
}
],
});
let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("text pipeline layout"),
bind_group_layouts: &[&constant_layout, &texture_layout],
push_constant_ranges: &[],
});
let shader = device.create_shader_module(ShaderModuleDescriptor {
label: Some("Text shader"),
source: ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("shader.wgsl"))),
});
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
label: Some("Text pipeline"),
layout: Some(&layout),
vertex: VertexState {
module: &shader,
entry_point: "vs_main",
compilation_options: Default::default(),
buffers: &[
VertexBufferLayout {
array_stride: mem::size_of::<Vertex>() as u64,
step_mode: VertexStepMode::Vertex,
attributes: &[VertexAttribute {
shader_location: 0,
format: VertexFormat::Float32x2,
offset: 0,
}],
},
VertexBufferLayout {
array_stride: mem::size_of::<Instance>() as u64,
step_mode: VertexStepMode::Instance,
attributes: &vertex_attr_array!(
1 => Float32x2,
2 => Float32,
3 => Float32,
4 => Float32x2,
5 => Float32x2,
6 => Uint32,
7 => Float32,
8 => Sint32,
9 => Float32x4
),
}
],
},
primitive: PrimitiveState {
topology: PrimitiveTopology::TriangleList,
front_face: FrontFace::Cw,
..Default::default()
},
depth_stencil: None,
multisample: MultisampleState { count: 1, mask: !0, alpha_to_coverage_enabled: false },
fragment: Some(FragmentState {
module: &shader,
entry_point: "fs_main",
compilation_options: Default::default(),
targets: &[Some(ColorTargetState {
format: surface_config.format,
blend: Some(BlendState {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::One,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
}),
write_mask: ColorWrites::ALL,
})],
}),
multiview: None,
});
let vertices = device.create_buffer_init(&util::BufferInitDescriptor {
label: Some("Text vertex buffer"),
contents: bytemuck::cast_slice(&VERTICES),
usage: BufferUsages::VERTEX,
});
let indices = device.create_buffer_init(&util::BufferInitDescriptor {
label: Some("Text indice buffer"),
contents: bytemuck::cast_slice(&INDICES),
usage: BufferUsages::INDEX,
});
let texture = device.create_bind_group(&BindGroupDescriptor {
label: Some("Text texture atlas bind group"),
layout: &texture_layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(
atlas.view(),
),
},
],
});
Self {
pipeline,
uniforms,
vertices,
indices,
instances_buffer: None,
instances: Vec::new(),
constants: constant_bind_group,
texture,
texture_version: atlas.layer_count(),
texture_layout,
screen_size,
}
}
pub fn prepare(&mut self, device: &Device, paragraphs: &Vec<Paragraph>, store: &FontStore) {
self.instances = Vec::new();
let mut glyph_count = 0;
paragraphs.iter().for_each(|paragraph| {
let font = store.get(paragraph.font_key).expect("Paragraph has been created without valid font");
let units_per_em = font.face.as_face_ref().units_per_em() as f32;
let mut glyph_x = paragraph.position[0];
paragraph.glyphs.iter().for_each(|(glyph_id, left)| {
if let Some(glyph) = font.glyph_cache.get(glyph_id) {
let glyph_y = paragraph.position[0] + (glyph.y_offset as f32 / units_per_em * paragraph.size as f32) + (f32::abs(glyph.descent as f32) / units_per_em * paragraph.size as f32);
let size = [
(glyph.bbox.width() as f32 * paragraph.size as f32 / units_per_em),
(glyph.bbox.height() as f32 * paragraph.size as f32 / units_per_em),
];
let instance = Instance {
_position: [glyph_x, glyph_y],
_left_side_bearing: glyph.left_side_bearing as f32,
_font_size: paragraph.size as f32,
_size: size,
_position_in_atlas: [glyph.allocation.position()[0] as f32, glyph.allocation.position()[1] as f32],
_size_in_atlas: glyph.allocation.size(),
_units_per_em: units_per_em,
_layer: glyph.allocation.layer() as u32,
_color: paragraph.color,
};
self.instances.push(instance);
glyph_count += 1;
}
glyph_x += left;
})
});
self.instances_buffer = Some(device.create_buffer_init(&BufferInitDescriptor {
label: Some("Text instances buffer"),
usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
contents: bytemuck::cast_slice(&self.instances[0..self.instances.len()])
}));
}
pub fn update_uniforms(&mut self, device: &Device, screen_size: [u32; 2]) {
self.uniforms = device.create_buffer_init(&BufferInitDescriptor {
label: Some("Text uniforms buffer"),
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
contents: bytemuck::bytes_of(&Params {
screen_resolution: Resolution {
width: screen_size[0],
height: screen_size[1],
},
_pad: [0, 0],
transform: orthographic_projection_matrix(0., screen_size[0] as f32, screen_size[1] as f32, 0.)
}),
});
}
pub fn render<'rpass>(&'rpass mut self, render_pass: &mut RenderPass<'rpass>, screen_size: [u32; 2]) {
if self.instances.is_empty() {
return;
}
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.constants, &[]);
render_pass.set_bind_group(1, &self.texture, &[]);
render_pass.set_index_buffer(
self.indices.slice(..),
wgpu::IndexFormat::Uint16,
);
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
render_pass.set_vertex_buffer(1, self.instances_buffer.as_ref().unwrap().slice(..));
render_pass.set_scissor_rect(0, 0, screen_size[0], screen_size[1]);
render_pass.draw_indexed(0..INDICES.len() as u32, 0, 0..self.instances.len() as u32);
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Pod, Zeroable)]
pub struct Resolution {
pub width: u32,
pub height: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Params {
screen_resolution: Resolution,
_pad: [u32; 2],
transform: [f32; 16],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Zeroable, Pod)]
struct Instance {
_position: [f32; 2],
_left_side_bearing: f32,
_font_size: f32,
_size: [f32; 2],
_position_in_atlas: [f32; 2],
_size_in_atlas: u32,
_units_per_em: f32,
_layer: u32,
_color: [f32; 4],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
struct Vertex {
_position: [f32; 3],
}
const INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
const VERTICES: [Vertex; 4] = [
Vertex {
_position: [0., 0., 0.]
},
Vertex {
_position: [1., 0., 0.]
},
Vertex {
_position: [1., 1., 0.]
},
Vertex {
_position: [0., 1., 0.]
}
];