1use std::mem;
2
3use bytemuck::{Pod, Zeroable};
4use owned_ttf_parser::AsFaceRef;
5use wgpu::{
6 util::{self, BufferInitDescriptor, DeviceExt, StagingBelt}, vertex_attr_array, BindGroup, BindGroupDescriptor,
7 BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource,
8 BindingType, BlendComponent, BlendFactor, BlendOperation, BlendState, Buffer, BufferBinding,
9 BufferBindingType, BufferDescriptor, BufferSize, BufferUsages, ColorTargetState, ColorWrites,
10 Device, FilterMode, FragmentState, FrontFace, MultisampleState, PipelineLayoutDescriptor,
11 PrimitiveState, PrimitiveTopology, RenderPass, RenderPipeline, RenderPipelineDescriptor,
12 SamplerBindingType, SamplerDescriptor, ShaderModuleDescriptor, ShaderSource, ShaderStages,
13 SurfaceConfiguration, TextureSampleType, TextureViewDimension, VertexAttribute, VertexBufferLayout,
14 VertexFormat, VertexState, VertexStepMode
15};
16
17use crate::{atlas::Atlas, ortho::orthographic_projection_matrix, typewriter::Paragraph, FontStore};
18
19pub struct TextRenderer {
20 pipeline: RenderPipeline,
21 uniforms: Buffer,
22 vertices: Buffer,
23 indices: Buffer,
24 instances_buffer: Option<Buffer>,
25 instances: Vec<Instance>,
26 constants: BindGroup,
27 texture: BindGroup,
28 texture_version: usize,
29 texture_layout: BindGroupLayout,
30 screen_size: [u32; 2],
31}
32
33impl TextRenderer {
34 pub fn new(device: &Device, surface_config: &SurfaceConfiguration, atlas: &Atlas) -> Self {
35 let screen_size = [surface_config.width, surface_config.height];
36
37 let sampler = device.create_sampler(&SamplerDescriptor {
38 label: Some("Text sampler"),
39 mag_filter: FilterMode::Nearest,
40 min_filter: FilterMode::Nearest,
41 mipmap_filter: FilterMode::Nearest,
42 lod_min_clamp: 0f32,
43 lod_max_clamp: 0f32,
44 ..Default::default()
45 });
46
47 let constant_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
48 label: Some("Text constants layout"),
49 entries: &[
50 BindGroupLayoutEntry {
51 binding: 0,
52 visibility: ShaderStages::VERTEX,
53 ty: BindingType::Buffer {
54 ty: BufferBindingType::Uniform,
55 has_dynamic_offset: false,
56 min_binding_size:BufferSize::new(
57 mem::size_of::<Params>() as u64,
58 ),
59 },
60 count: None,
61 },
62 BindGroupLayoutEntry {
63 binding: 1,
64 visibility: ShaderStages::FRAGMENT,
65 ty: BindingType::Sampler(
66 SamplerBindingType::NonFiltering,
67 ),
68 count: None,
69 }
70 ],
71 });
72
73 let uniforms = device.create_buffer_init(&BufferInitDescriptor {
74 label: Some("Text uniforms buffer"),
75 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
76 contents: bytemuck::bytes_of(&Params {
77 screen_resolution: Resolution {
78 width: screen_size[0],
79 height: screen_size[1],
80 },
81 _pad: [0, 0],
82 transform: orthographic_projection_matrix(0., screen_size[0] as f32, screen_size[1] as f32, 0.)
83 }),
84 });
85
86 let constant_bind_group = device.create_bind_group(&BindGroupDescriptor {
87 label: Some("Text texture bind group"),
88 layout: &constant_layout,
89 entries: &[
90 BindGroupEntry {
91 binding: 0,
92 resource: BindingResource::Buffer(
93 BufferBinding {
94 buffer: &uniforms,
95 offset: 0,
96 size: None,
97 },
98 ),
99 },
100 BindGroupEntry {
101 binding: 1,
102 resource: BindingResource::Sampler(&sampler),
103 },
104 ],
105 });
106
107 let texture_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
108 label: Some("Text texture layout"),
109 entries: &[
110 BindGroupLayoutEntry {
111 binding: 0,
112 visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
113 ty: BindingType::Texture {
114 sample_type: TextureSampleType::Float { filterable: false },
115 view_dimension: TextureViewDimension::D2Array,
116 multisampled: false,
117 },
118 count: None,
119 }
120 ],
121 });
122
123 let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
124 label: Some("text pipeline layout"),
125 bind_group_layouts: &[&constant_layout, &texture_layout],
126 push_constant_ranges: &[],
127 });
128
129 let shader = device.create_shader_module(ShaderModuleDescriptor {
130 label: Some("Text shader"),
131 source: ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("shader.wgsl"))),
132 });
133
134 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
135 label: Some("Text pipeline"),
136 layout: Some(&layout),
137 vertex: VertexState {
138 module: &shader,
139 entry_point: "vs_main",
140 compilation_options: Default::default(),
141 buffers: &[
142 VertexBufferLayout {
143 array_stride: mem::size_of::<Vertex>() as u64,
144 step_mode: VertexStepMode::Vertex,
145 attributes: &[VertexAttribute {
146 shader_location: 0,
147 format: VertexFormat::Float32x2,
148 offset: 0,
149 }],
150 },
151 VertexBufferLayout {
152 array_stride: mem::size_of::<Instance>() as u64,
153 step_mode: VertexStepMode::Instance,
154 attributes: &vertex_attr_array!(
155 1 => Float32x2,
156 2 => Float32,
157 3 => Float32,
158 4 => Float32x2,
159 5 => Float32x2,
160 6 => Uint32,
161 7 => Float32,
162 8 => Sint32,
163 9 => Float32x4
164 ),
165 }
166 ],
167 },
168 primitive: PrimitiveState {
169 topology: PrimitiveTopology::TriangleList,
170 front_face: FrontFace::Cw,
171 ..Default::default()
172 },
173 depth_stencil: None,
174 multisample: MultisampleState { count: 1, mask: !0, alpha_to_coverage_enabled: false },
175 fragment: Some(FragmentState {
176 module: &shader,
177 entry_point: "fs_main",
178 compilation_options: Default::default(),
179 targets: &[Some(ColorTargetState {
180 format: surface_config.format,
181 blend: Some(BlendState {
182 color: BlendComponent {
183 src_factor: BlendFactor::SrcAlpha,
184 dst_factor: BlendFactor::OneMinusSrcAlpha,
185 operation: BlendOperation::Add,
186 },
187 alpha: BlendComponent {
188 src_factor: BlendFactor::One,
189 dst_factor: BlendFactor::OneMinusSrcAlpha,
190 operation: BlendOperation::Add,
191 },
192 }),
193 write_mask: ColorWrites::ALL,
194 })],
195 }),
196 multiview: None,
197 });
198
199 let vertices = device.create_buffer_init(&util::BufferInitDescriptor {
200 label: Some("Text vertex buffer"),
201 contents: bytemuck::cast_slice(&VERTICES),
202 usage: BufferUsages::VERTEX,
203 });
204
205 let indices = device.create_buffer_init(&util::BufferInitDescriptor {
206 label: Some("Text indice buffer"),
207 contents: bytemuck::cast_slice(&INDICES),
208 usage: BufferUsages::INDEX,
209 });
210
211 let texture = device.create_bind_group(&BindGroupDescriptor {
212 label: Some("Text texture atlas bind group"),
213 layout: &texture_layout,
214 entries: &[
215 BindGroupEntry {
216 binding: 0,
217 resource: BindingResource::TextureView(
218 atlas.view(),
219 ),
220 },
221 ],
222 });
223
224 Self {
225 pipeline,
226 uniforms,
227 vertices,
228 indices,
229 instances_buffer: None,
230 instances: Vec::new(),
231 constants: constant_bind_group,
232 texture,
233 texture_version: atlas.layer_count(),
234 texture_layout,
235 screen_size,
236 }
237 }
238
239 pub fn prepare(&mut self, device: &Device, paragraphs: &Vec<Paragraph>, store: &FontStore) {
240 self.instances = Vec::new();
241 let mut glyph_count = 0;
242
243 paragraphs.iter().for_each(|paragraph| {
244
245 let font = store.get(paragraph.font_key).expect("Paragraph has been created without valid font");
246
247 let units_per_em = font.face.as_face_ref().units_per_em() as f32;
248
249 let mut glyph_x = paragraph.position[0];
250
251 paragraph.glyphs.iter().for_each(|(glyph_id, left)| {
252 if let Some(glyph) = font.glyph_cache.get(glyph_id) {
253 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);
254
255 let size = [
256 (glyph.bbox.width() as f32 * paragraph.size as f32 / units_per_em),
257 (glyph.bbox.height() as f32 * paragraph.size as f32 / units_per_em),
258 ];
259
260 let instance = Instance {
261 _position: [glyph_x, glyph_y],
262 _left_side_bearing: glyph.left_side_bearing as f32,
263 _font_size: paragraph.size as f32,
264 _size: size,
265 _position_in_atlas: [glyph.allocation.position()[0] as f32, glyph.allocation.position()[1] as f32],
266 _size_in_atlas: glyph.allocation.size(),
267 _units_per_em: units_per_em,
268 _layer: glyph.allocation.layer() as u32,
269 _color: paragraph.color,
270 };
271
272 self.instances.push(instance);
273
274 glyph_count += 1;
275 }
276
277 glyph_x += left;
278 })
279 });
280
281 self.instances_buffer = Some(device.create_buffer_init(&BufferInitDescriptor {
282 label: Some("Text instances buffer"),
283 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
284 contents: bytemuck::cast_slice(&self.instances[0..self.instances.len()])
285 }));
286 }
287
288 pub fn update_uniforms(&mut self, device: &Device, screen_size: [u32; 2]) {
289 self.uniforms = device.create_buffer_init(&BufferInitDescriptor {
290 label: Some("Text uniforms buffer"),
291 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
292 contents: bytemuck::bytes_of(&Params {
293 screen_resolution: Resolution {
294 width: screen_size[0],
295 height: screen_size[1],
296 },
297 _pad: [0, 0],
298 transform: orthographic_projection_matrix(0., screen_size[0] as f32, screen_size[1] as f32, 0.)
299 }),
300 });
301 }
302
303 pub fn render<'rpass>(&'rpass mut self, render_pass: &mut RenderPass<'rpass>, screen_size: [u32; 2]) {
304
305 if self.instances.is_empty() {
306 return;
307 }
308
309 render_pass.set_pipeline(&self.pipeline);
310 render_pass.set_bind_group(0, &self.constants, &[]);
311 render_pass.set_bind_group(1, &self.texture, &[]);
312 render_pass.set_index_buffer(
313 self.indices.slice(..),
314 wgpu::IndexFormat::Uint16,
315 );
316 render_pass.set_vertex_buffer(0, self.vertices.slice(..));
317 render_pass.set_vertex_buffer(1, self.instances_buffer.as_ref().unwrap().slice(..));
318
319 render_pass.set_scissor_rect(0, 0, screen_size[0], screen_size[1]);
320
321 render_pass.draw_indexed(0..INDICES.len() as u32, 0, 0..self.instances.len() as u32);
322 }
323}
324
325#[repr(C)]
326#[derive(Clone, Copy, Debug, Eq, PartialEq, Pod, Zeroable)]
327pub struct Resolution {
328 pub width: u32,
329 pub height: u32,
330}
331
332#[repr(C)]
333#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
334pub struct Params {
335 screen_resolution: Resolution,
336 _pad: [u32; 2],
337 transform: [f32; 16],
338}
339
340#[repr(C)]
341#[derive(Clone, Copy, Debug, Zeroable, Pod)]
342struct Instance {
343 _position: [f32; 2],
344 _left_side_bearing: f32,
345 _font_size: f32,
346 _size: [f32; 2],
347 _position_in_atlas: [f32; 2],
348 _size_in_atlas: u32,
349 _units_per_em: f32,
350 _layer: u32,
351 _color: [f32; 4],
352}
353
354#[repr(C)]
355#[derive(Clone, Copy, Debug, Pod, Zeroable)]
356struct Vertex {
357 _position: [f32; 3],
358}
359
360const INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
361
362const VERTICES: [Vertex; 4] = [
363 Vertex {
364 _position: [0., 0., 0.]
365 },
366 Vertex {
367 _position: [1., 0., 0.]
368 },
369 Vertex {
370 _position: [1., 1., 0.]
371 },
372 Vertex {
373 _position: [0., 1., 0.]
374 }
375];