wgpu_font_renderer/
renderer.rs

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];