1use std::path::Path;
38use std::sync::Arc;
39
40use glyphon::fontdb::Source;
41use glyphon::{
42 self, Attrs, Family, FontSystem, Metrics, Shaping, SwashCache, TextArea, TextAtlas, TextBounds,
43};
44
45use crate::colour::Colour;
46use crate::context::WgpuClump;
47use crate::engine_handle::Engine;
48use crate::material::{self, Material};
49use crate::matrix_math::normalize_points;
50use crate::render::Renderer;
51use crate::resource::{
52 self, InProgressResource, LoadingOp, ResourceId, ResourceManager, ResourceType,
53};
54use crate::vectors::Vec2;
55use crate::vertex::{self, Vertex};
56use crate::{layouts, vec2};
57
58pub(crate) struct TextRenderer {
61 pub(crate) font_system: FontSystem,
62 swash_cache: SwashCache,
63 text_cache: glyphon::Cache,
64 atlas: TextAtlas,
65 text_renderer: glyphon::TextRenderer,
66 defualt_font_name: String,
67}
68
69impl TextRenderer {
70 pub fn new(wgpu: &WgpuClump) -> Self {
71 let mut font_system = FontSystem::new();
72 let defualt_font_id = font_system
73 .db_mut()
74 .load_font_source(Source::Binary(Arc::new(include_bytes!("Monocraft.ttf"))))[0];
75
76 let defualt_font_name = font_system.db().face(defualt_font_id).unwrap().families[0]
77 .0
78 .clone();
79 let text_cache = glyphon::Cache::new(&wgpu.device);
82
83 let swash_cache = SwashCache::new();
84 let mut atlas = TextAtlas::new(
85 &wgpu.device,
86 &wgpu.queue,
87 &text_cache,
88 wgpu::TextureFormat::Rgba8UnormSrgb,
89 );
90 let text_renderer = glyphon::TextRenderer::new(
91 &mut atlas,
92 &wgpu.device,
93 wgpu::MultisampleState::default(),
94 None,
95 );
96
97 Self {
98 font_system,
99 swash_cache,
100 text_cache,
101 atlas,
102 text_renderer,
103 defualt_font_name,
104 }
105 }
106
107 pub fn get_defualt_font_name(&self) -> &str {
108 &self.defualt_font_name
109 }
110
111 pub fn load_font_from_bytes(&mut self, data: &[u8]) -> Font {
113 let id = self
114 .font_system
115 .db_mut()
116 .load_font_source(Source::Binary(Arc::new(data.to_vec())))[0];
117 let name = self.font_system.db().face(id).unwrap().families[0]
120 .0
121 .clone();
122
123 Font { name }
124 }
125}
126
127pub struct TextMaterial {
131 font_size: f32,
132 line_height: f32,
133 text: String,
134 inner: Option<InnerMaterial>,
135 colour: Colour,
136 vertex_count: u64,
137 index_count: u64,
138}
139
140impl TextMaterial {
141 pub fn new(text: &str, colour: Colour, font_size: f32, line_height: f32) -> Self {
142 Self {
143 font_size,
144 line_height,
145 text: text.into(),
146 colour,
147 inner: None,
148 vertex_count: 0,
149 index_count: 0,
150 }
151 }
152
153 pub fn set_text(&mut self, text: &str, colour: Colour, engine: &mut Engine) {
156 self.text = text.into();
157
158 let context = match &mut engine.context {
160 None => return,
161 Some(c) => c,
162 };
163
164 if self.add_inner(
165 &context.wgpu,
166 &mut context.text_renderer,
167 &engine.resource_manager,
168 &context.texture_sampler,
169 ) {
170 return;
171 }
172
173 let inner = self.inner.as_mut().unwrap();
174 let font_info = FontInformation {
175 wgpu: &context.wgpu,
176 text_handle: &mut context.text_renderer,
177 resources: &engine.resource_manager,
178 };
179
180 inner.text_buffer.set_text(
181 &mut font_info.text_handle.font_system,
182 text,
183 Attrs::new()
184 .color(colour.into())
185 .family(Family::Name(&font_info.text_handle.defualt_font_name)),
186 Shaping::Basic,
187 );
188
189 self.update_measurements(font_info);
190 }
191
192 pub fn set_text_with_font(
195 &mut self,
196 text: &str,
197 colour: Colour,
198 font: &ResourceId<Font>,
199 engine: &mut Engine,
200 ) {
201 self.text = text.into();
202
203 let context = match &mut engine.context {
205 None => return,
206 Some(c) => c,
207 };
208
209 self.add_inner(
210 &context.wgpu,
211 &mut context.text_renderer,
212 &engine.resource_manager,
213 &context.texture_sampler,
214 );
215
216 let inner = self.inner.as_mut().unwrap();
217 let font_info = FontInformation {
218 wgpu: &context.wgpu,
219 text_handle: &mut context.text_renderer,
220 resources: &engine.resource_manager,
221 };
222
223 let backup = &String::new();
224 let name = font_info
225 .resources
226 .get_font(font)
227 .map(|f| &f.name)
228 .unwrap_or(backup);
229
230 inner.text_buffer.set_text(
231 &mut font_info.text_handle.font_system,
232 text,
233 Attrs::new().color(colour.into()).family(Family::Name(name)),
234 Shaping::Basic,
235 );
236
237 self.update_measurements(font_info);
238 }
239
240 pub fn set_bounds(&mut self, position: Vec2<i32>, size: Vec2<i32>) {
242 if let Some(inner) = &mut self.inner {
243 inner.bounds = Vec2 {
244 x: position,
245 y: size,
246 }
247 }
248 }
249
250 pub fn set_font_size(&mut self, new_size: f32, engine: &mut Engine) {
252 self.font_size = new_size;
253 let context = match &mut engine.context {
255 None => return,
256 Some(c) => c,
257 };
258
259 if self.add_inner(
260 &context.wgpu,
261 &mut context.text_renderer,
262 &engine.resource_manager,
263 &context.texture_sampler,
264 ) {
265 return;
266 }
267
268 let inner = self.inner.as_mut().unwrap();
269 let font_info = FontInformation {
270 wgpu: &context.wgpu,
271 text_handle: &mut context.text_renderer,
272 resources: &engine.resource_manager,
273 };
274
275 let metrics = Metrics::new(self.font_size, self.line_height);
276 inner
277 .text_buffer
278 .set_metrics(&mut font_info.text_handle.font_system, metrics);
279
280 self.update_measurements(font_info);
281 }
282
283 pub fn set_line_height(&mut self, new_height: f32, engine: &mut Engine) {
285 self.line_height = new_height;
286 let context = match &mut engine.context {
288 None => return,
289 Some(c) => c,
290 };
291
292 if self.add_inner(
293 &context.wgpu,
294 &mut context.text_renderer,
295 &engine.resource_manager,
296 &context.texture_sampler,
297 ) {
298 return;
299 }
300
301 let inner = self.inner.as_mut().unwrap();
302 let font_info = FontInformation {
303 wgpu: &context.wgpu,
304 text_handle: &mut context.text_renderer,
305 resources: &engine.resource_manager,
306 };
307
308 let metrics = Metrics::new(self.font_size, self.line_height);
309 inner
310 .text_buffer
311 .set_metrics(&mut font_info.text_handle.font_system, metrics);
312
313 self.update_measurements(font_info);
314 }
315
316 pub fn get_measurements(&self) -> Vec2<u32> {
318 self.inner.as_ref().map(|c| c.size).unwrap_or(vec2!(0))
319 }
320
321 pub fn get_text(&self) -> &str {
322 &self.text
323 }
324
325 fn update_measurements(&mut self, font_info: FontInformation) {
326 let inner = self
327 .inner
328 .as_mut()
329 .expect("inner should be inilized before measuring");
330
331 let hieght = inner.text_buffer.lines.len() as f32 * inner.text_buffer.metrics().line_height;
332 let run_width = inner
333 .text_buffer
334 .layout_runs()
335 .map(|run| run.line_w)
336 .max_by(f32::total_cmp)
337 .unwrap_or(0.0);
338
339 inner.size = Vec2 {
340 x: run_width.ceil() as u32,
341 y: hieght.ceil() as u32,
342 };
343
344 inner.texture = font_info
345 .wgpu
346 .device
347 .create_texture(&wgpu::TextureDescriptor {
348 label: Some("Text Texture"),
349 size: wgpu::Extent3d {
350 width: inner.size.x,
351 height: inner.size.y,
352 depth_or_array_layers: 1,
353 },
354 mip_level_count: 1,
355 sample_count: 1,
356 dimension: wgpu::TextureDimension::D2,
357 format: wgpu::TextureFormat::Rgba8UnormSrgb,
358 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
359 | wgpu::TextureUsages::TEXTURE_BINDING,
360 view_formats: &[],
361 });
362
363 inner
364 .viewport
365 .update(&font_info.wgpu.queue, inner.size.into());
366 }
367
368 pub fn add_instance(&mut self, position: Vec2<f32>, tint: Colour, render: &Renderer) {
371 let inner = self.inner.as_ref().unwrap();
372
373 let rect_size = Vec2 {
374 x: inner.size.x as f32,
375 y: inner.size.y as f32,
376 };
377 let wgpu = render.wgpu;
378 let verts = vertex::from_pixels(position, rect_size, tint.as_raw());
379
380 self.push_rectangle(wgpu, verts);
381 }
382
383 pub fn add_instance_with_rotation(
386 &mut self,
387 position: Vec2<f32>,
388 tint: Colour,
389 degrees: f32,
390 render: &Renderer,
391 ) {
392 let inner = self.inner.as_ref().unwrap();
393
394 let rect_size = Vec2 {
395 x: inner.size.x as f32,
396 y: inner.size.y as f32,
397 };
398 let wgpu = render.wgpu;
399 let verts = vertex::from_pixels_with_rotation(position, rect_size, tint.as_raw(), degrees);
400
401 self.push_rectangle(wgpu, verts);
402 }
403
404 pub fn add_instance_with_uv(
407 &mut self,
408 position: Vec2<f32>,
409 size: Vec2<f32>,
410 uv_pos: Vec2<f32>,
411 uv_size: Vec2<f32>,
412 tint: Colour,
413 render: &Renderer,
414 ) {
415 let inner = self.inner.as_ref().unwrap();
416
417 let wgpu = render.wgpu;
418
419 let uv_pos = normalize_points(
420 uv_pos,
421 Vec2 {
422 x: inner.size.x as f32,
423 y: inner.size.y as f32,
424 },
425 );
426 let uv_size = normalize_points(
427 uv_size,
428 Vec2 {
429 x: inner.size.x as f32,
430 y: inner.size.y as f32,
431 },
432 );
433
434 let verts = vertex::from_pixels_with_uv(position, size, tint.as_raw(), uv_pos, uv_size);
435
436 self.push_rectangle(wgpu, verts);
437 }
438
439 #[allow(clippy::too_many_arguments)]
440 pub fn add_instace_ex(
442 &mut self,
443 position: Vec2<f32>,
444 size: Vec2<f32>,
445 uv_pos: Vec2<f32>,
446 uv_size: Vec2<f32>,
447 degrees: f32,
448 tint: Colour,
449 render: &Renderer,
450 ) {
451 let inner = self.inner.as_ref().unwrap();
452
453 let wgpu = render.wgpu;
454
455 let uv_pos = normalize_points(
456 uv_pos,
457 Vec2 {
458 x: inner.size.x as f32,
459 y: inner.size.y as f32,
460 },
461 );
462 let uv_size = normalize_points(
463 uv_size,
464 Vec2 {
465 x: inner.size.x as f32,
466 y: inner.size.y as f32,
467 },
468 );
469
470 let verts = vertex::from_pixels_ex(position, size, tint.as_raw(), degrees, uv_pos, uv_size);
471
472 self.push_rectangle(wgpu, verts);
473 }
474
475 pub fn add_instance_custom(
478 &mut self,
479 points: [Vec2<f32>; 4],
480 uv_points: [Vec2<f32>; 4],
481 degrees: f32,
482 tint: Colour,
483 render: &Renderer,
484 ) {
485 let inner = self.inner.as_ref().unwrap();
486
487 let wgpu = render.wgpu;
488
489 let uv_points = [
490 normalize_points(
491 uv_points[0],
492 Vec2 {
493 x: inner.size.x as f32,
494 y: inner.size.y as f32,
495 },
496 ),
497 normalize_points(
498 uv_points[1],
499 Vec2 {
500 x: inner.size.x as f32,
501 y: inner.size.y as f32,
502 },
503 ),
504 normalize_points(
505 uv_points[2],
506 Vec2 {
507 x: inner.size.x as f32,
508 y: inner.size.y as f32,
509 },
510 ),
511 normalize_points(
512 uv_points[3],
513 Vec2 {
514 x: inner.size.x as f32,
515 y: inner.size.y as f32,
516 },
517 ),
518 ];
519
520 let verts = vertex::from_pixels_custom(points, uv_points, degrees, tint.as_raw());
521
522 self.push_rectangle(wgpu, verts);
523 }
524
525 fn push_rectangle(&mut self, wgpu: &WgpuClump, verts: [Vertex; 4]) {
526 let num_verts = self.get_vertex_number() as u16;
527 let inner = self.inner.as_mut().unwrap();
528
529 let vertex_size = std::mem::size_of::<Vertex>() as u64;
530 let index_size = 2;
531
532 let max_verts = inner.vertex_buffer.size();
533 if self.vertex_count + (4 * vertex_size) > max_verts {
534 material::grow_buffer(
535 &mut inner.vertex_buffer,
536 wgpu,
537 1,
538 wgpu::BufferUsages::VERTEX,
539 );
540 }
541
542 let indicies = [
543 num_verts,
544 1 + num_verts,
545 2 + num_verts,
546 3 + num_verts,
547 num_verts,
548 2 + num_verts,
549 ];
550
551 let max_indicies = inner.index_buffer.size();
552 if self.index_count + (6 * index_size) > max_indicies {
553 material::grow_buffer(&mut inner.index_buffer, wgpu, 1, wgpu::BufferUsages::INDEX);
554 }
555
556 wgpu.queue.write_buffer(
557 &inner.vertex_buffer,
558 self.vertex_count,
559 bytemuck::cast_slice(&verts),
560 );
561 wgpu.queue.write_buffer(
562 &inner.index_buffer,
563 self.index_count,
564 bytemuck::cast_slice(&indicies),
565 );
566
567 self.vertex_count += 4 * vertex_size;
568 self.index_count += 6 * index_size;
569 }
570
571 fn get_vertex_number(&self) -> u64 {
572 self.vertex_count / std::mem::size_of::<Vertex>() as u64
573 }
574
575 fn get_index_number(&self) -> u64 {
576 self.index_count / 2
577 }
578
579 fn add_inner(
580 &mut self,
581 wgpu: &WgpuClump,
582 text_handle: &mut TextRenderer,
583 resources: &ResourceManager,
584 sampler: &wgpu::Sampler,
585 ) -> bool {
586 if self.inner.is_none() {
587 self.inner = Some(InnerMaterial::new(
588 wgpu,
589 text_handle,
590 resources,
591 sampler,
592 &self.text,
593 self.font_size,
594 self.line_height,
595 self.colour,
596 ));
597 true
598 } else {
599 false
600 }
601 }
602
603 pub fn prepare(&mut self, engine: &mut Engine) {
607 let context = match &mut engine.context {
608 Some(c) => c,
609 None => return, };
611
612 if self.inner.is_none() {
613 self.inner = Some(InnerMaterial::new(
614 &context.wgpu,
615 &mut context.text_renderer,
616 &engine.resource_manager,
617 &context.texture_sampler,
618 &self.text,
619 self.font_size,
620 self.line_height,
621 self.colour,
622 ));
623 }
624
625 let font_info = FontInformation {
626 wgpu: &context.wgpu,
627 text_handle: &mut context.text_renderer,
628 resources: &engine.resource_manager,
629 };
630
631 let inner = self.inner.as_mut().unwrap();
632
633 let texture_view = inner
634 .texture
635 .create_view(&wgpu::TextureViewDescriptor::default());
636 render_text_to_texture(
637 font_info.text_handle,
638 &inner.text_buffer,
639 inner.bounds,
640 &inner.viewport,
641 &texture_view,
642 font_info.wgpu,
643 );
644
645 inner.bind_group = context
646 .wgpu
647 .device
648 .create_bind_group(&wgpu::BindGroupDescriptor {
649 label: Some("Text Widget BindGroup"),
650 layout: &layouts::create_texture_layout(&context.wgpu.device),
651 entries: &[
652 wgpu::BindGroupEntry {
653 binding: 0,
654 resource: wgpu::BindingResource::TextureView(&texture_view),
655 },
656 wgpu::BindGroupEntry {
657 binding: 1,
658 resource: wgpu::BindingResource::Sampler(&context.texture_sampler),
659 },
660 ],
661 });
662 }
663
664 pub fn draw<'others>(&'others mut self, information: &mut Renderer<'_, 'others>) {
666 let inner = self.inner.as_ref().unwrap();
667
668 let pipeline = &information
669 .resources
670 .get_pipeline(&information.defualt_id)
671 .unwrap()
672 .pipeline;
673
674 information.pass.set_pipeline(pipeline);
675 information.pass.set_bind_group(0, &inner.bind_group, &[]);
676
677 information
678 .pass
679 .set_vertex_buffer(0, inner.vertex_buffer.slice(0..self.vertex_count));
680 information.pass.set_index_buffer(
681 inner.index_buffer.slice(0..self.index_count),
682 wgpu::IndexFormat::Uint16,
683 );
684
685 information
686 .pass
687 .draw_indexed(0..self.get_index_number() as u32, 0, 0..1);
688
689 self.vertex_count = 0;
690 self.index_count = 0;
691 }
692}
693
694struct InnerMaterial {
695 size: Vec2<u32>,
696 text_buffer: glyphon::Buffer,
697 vertex_buffer: wgpu::Buffer,
698 index_buffer: wgpu::Buffer,
699 texture: wgpu::Texture,
700 viewport: glyphon::Viewport,
701 bind_group: wgpu::BindGroup,
702 bounds: Vec2<Vec2<i32>>,
703}
704
705impl InnerMaterial {
706 #[allow(clippy::too_many_arguments)]
707 fn new(
708 wgpu: &WgpuClump,
709 text_handle: &mut TextRenderer,
710 resources: &ResourceManager,
711 sampler: &wgpu::Sampler,
712 text: &str,
713 font_size: f32,
714 line_height: f32,
715 colour: Colour,
716 ) -> Self {
717 let font_info = FontInformation {
718 wgpu,
719 text_handle,
720 resources,
721 };
722
723 let mut text_buffer = glyphon::Buffer::new(
724 &mut font_info.text_handle.font_system,
725 Metrics::new(font_size, line_height),
726 );
727
728 text_buffer.set_size(&mut font_info.text_handle.font_system, None, None);
729
730 text_buffer.set_text(
731 &mut font_info.text_handle.font_system,
732 text,
733 Attrs::new()
734 .color(colour.into())
735 .family(Family::Name(&font_info.text_handle.defualt_font_name)),
736 Shaping::Advanced,
737 );
738
739 let hieght = text_buffer.lines.len() as f32 * text_buffer.metrics().line_height;
740 let run_width = text_buffer
741 .layout_runs()
742 .map(|run| run.line_w)
743 .max_by(f32::total_cmp)
744 .unwrap_or(0.0);
745
746 let texture = font_info
747 .wgpu
748 .device
749 .create_texture(&wgpu::TextureDescriptor {
750 label: Some("Text Texture"),
751 size: wgpu::Extent3d {
752 width: run_width.ceil() as u32,
753 height: hieght.ceil() as u32,
754 depth_or_array_layers: 1,
755 },
756 mip_level_count: 1,
757 sample_count: 1,
758 dimension: wgpu::TextureDimension::D2,
759 format: wgpu::TextureFormat::Rgba8UnormSrgb,
760 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
761 | wgpu::TextureUsages::TEXTURE_BINDING,
762 view_formats: &[],
763 });
764
765 let bounds = Vec2 {
766 x: Vec2 { x: 0, y: 0 },
767 y: Vec2 {
768 x: i32::MAX,
769 y: i32::MAX,
770 },
771 };
772
773 let texture_size = Vec2 {
774 x: run_width.ceil() as u32,
775 y: hieght.ceil() as u32,
776 };
777
778 let mut viewport =
779 glyphon::Viewport::new(&font_info.wgpu.device, &font_info.text_handle.text_cache);
780 viewport.update(&font_info.wgpu.queue, texture_size.into());
781
782 let texture_view = texture.create_view(&Default::default());
783
784 render_text_to_texture(
785 font_info.text_handle,
786 &text_buffer,
787 bounds,
788 &viewport,
789 &texture_view,
790 font_info.wgpu,
791 );
792
793 let vertex_size = std::mem::size_of::<Vertex>() as u64;
794
795 let (vertex_buffer, index_buffer) =
796 Material::<()>::create_buffers(&wgpu.device, vertex_size, 16, 2, 32);
797
798 let bind_group = wgpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
799 label: Some("Text Widget BindGroup"),
800 layout: &layouts::create_texture_layout(&wgpu.device),
801 entries: &[
802 wgpu::BindGroupEntry {
803 binding: 0,
804 resource: wgpu::BindingResource::TextureView(&texture_view),
805 },
806 wgpu::BindGroupEntry {
807 binding: 1,
808 resource: wgpu::BindingResource::Sampler(sampler),
809 },
810 ],
811 });
812
813 Self {
814 size: texture_size,
815 text_buffer,
816 texture,
817 vertex_buffer,
818 index_buffer,
819 bind_group,
820 viewport,
821 bounds,
822 }
823 }
824}
825
826pub(crate) struct FontInformation<'a> {
828 text_handle: &'a mut TextRenderer,
829 wgpu: &'a WgpuClump,
830 resources: &'a ResourceManager,
831}
832
833pub struct Font {
835 name: String,
836}
837
838impl Font {
839 pub fn new<P: AsRef<Path>>(
841 path: P,
842 engine: &mut Engine,
843 loading_op: LoadingOp,
844 ) -> ResourceId<Font> {
845 let typed_id = resource::generate_id::<Font>();
846 let id = typed_id.get_id();
847 let path = path.as_ref();
848 let ip_resource = InProgressResource::new(path, id, ResourceType::Font, loading_op);
849
850 engine.loader.blocking_load(ip_resource, engine.get_proxy());
851
852 typed_id
853 }
854
855 pub(crate) fn from_str(name: &str) -> Self {
856 Self {
857 name: name.to_string(),
858 }
859 }
860}
861
862fn render_text_to_texture(
863 text_handle: &mut TextRenderer,
864 text: &glyphon::Buffer,
865 bounds: Vec2<Vec2<i32>>,
866 viewport: &glyphon::Viewport,
867 texture_view: &wgpu::TextureView,
868 wgpu: &WgpuClump,
869) {
870 let text_area = TextArea {
871 buffer: text,
872 left: 0.0,
873 top: 0.0,
874 scale: 1.0,
875 bounds: TextBounds {
876 left: bounds.x.x,
877 top: bounds.x.y,
878 right: bounds.y.x,
879 bottom: bounds.y.y,
880 },
881 default_color: glyphon::Color::rgb(255, 255, 255),
882 custom_glyphs: &[],
883 };
884
885 text_handle
886 .text_renderer
887 .prepare(
888 &wgpu.device,
889 &wgpu.queue,
890 &mut text_handle.font_system,
891 &mut text_handle.atlas,
892 viewport,
893 [text_area],
894 &mut text_handle.swash_cache,
895 )
896 .unwrap_or(());
897
898 let mut text_encoder = wgpu
899 .device
900 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
901 label: Some("text encoder"),
902 });
903
904 let mut text_pass = text_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
905 label: Some("Text to Texture Pass"),
906 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
907 view: texture_view,
908 resolve_target: None,
909 ops: wgpu::Operations {
910 load: wgpu::LoadOp::Clear(wgpu::Color {
911 r: 0.0,
912 g: 0.0,
913 b: 0.0,
914 a: 0.0,
915 }),
916 store: wgpu::StoreOp::Store,
917 },
918 })],
919 timestamp_writes: None,
920 occlusion_query_set: None,
921 depth_stencil_attachment: None,
922 });
923
924 text_handle
925 .text_renderer
926 .render(&text_handle.atlas, viewport, &mut text_pass)
927 .unwrap();
928
929 drop(text_pass);
930 text_handle.atlas.trim();
931
932 wgpu.queue.submit(std::iter::once(text_encoder.finish()));
933}