1use std::{borrow::Cow, cmp::min, fmt::Debug, ops::Index};
2
3use beamterm_data::{FontAtlasData, FontStyle, Glyph, GlyphEffect};
4use compact_str::{CompactString, CompactStringExt};
5use web_sys::{WebGl2RenderingContext, console};
6
7use crate::{
8 error::Error,
9 gl::{
10 CellIterator, Drawable, FontAtlas, GL, RenderContext, ShaderProgram, buffer_upload_array,
11 selection::SelectionTracker, ubo::UniformBufferObject,
12 },
13 mat4::Mat4,
14};
15
16#[derive(Debug)]
23pub struct TerminalGrid {
24 gpu: GpuResources,
26 cells: Vec<CellDynamic>,
28 terminal_size: (u16, u16),
30 canvas_size_px: (i32, i32),
32 atlas: FontAtlas,
34 fallback_glyph: u16,
36 selection: SelectionTracker,
38 cells_pending_flush: bool,
40}
41
42#[derive(Debug)]
49struct GpuResources {
50 shader: ShaderProgram,
52 buffers: TerminalBuffers,
54 ubo_vertex: UniformBufferObject,
56 ubo_fragment: UniformBufferObject,
58 sampler_loc: web_sys::WebGlUniformLocation,
60}
61
62impl GpuResources {
63 const FRAGMENT_GLSL: &'static str = include_str!("../shaders/cell.frag");
64 const VERTEX_GLSL: &'static str = include_str!("../shaders/cell.vert");
65
66 fn new(
75 gl: &WebGl2RenderingContext,
76 cell_pos: &[CellStatic],
77 cell_data: &[CellDynamic],
78 cell_size: (i32, i32),
79 ) -> Result<Self, Error> {
80 let vao = create_vao(gl)?;
82 gl.bind_vertex_array(Some(&vao));
83
84 let buffers = setup_buffers(gl, vao, cell_pos, cell_data, cell_size)?;
86
87 gl.bind_vertex_array(None);
89
90 let shader = ShaderProgram::create(gl, Self::VERTEX_GLSL, Self::FRAGMENT_GLSL)?;
92 shader.use_program(gl);
93
94 let ubo_vertex = UniformBufferObject::new(gl, CellVertexUbo::BINDING_POINT)?;
95 ubo_vertex.bind_to_shader(gl, &shader, "VertUbo")?;
96 let ubo_fragment = UniformBufferObject::new(gl, CellFragmentUbo::BINDING_POINT)?;
97 ubo_fragment.bind_to_shader(gl, &shader, "FragUbo")?;
98
99 let sampler_loc = gl
100 .get_uniform_location(&shader.program, "u_sampler")
101 .ok_or(Error::uniform_location_failed("u_sampler"))?;
102
103 Ok(Self {
104 shader,
105 buffers,
106 ubo_vertex,
107 ubo_fragment,
108 sampler_loc,
109 })
110 }
111}
112
113#[derive(Debug)]
114struct TerminalBuffers {
115 vao: web_sys::WebGlVertexArrayObject,
116 vertices: web_sys::WebGlBuffer,
117 instance_pos: web_sys::WebGlBuffer,
118 instance_cell: web_sys::WebGlBuffer,
119 indices: web_sys::WebGlBuffer,
120}
121
122impl TerminalBuffers {
123 fn upload_instance_data<T>(&self, gl: &WebGl2RenderingContext, cell_data: &[T]) {
124 gl.bind_vertex_array(Some(&self.vao));
125 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&self.instance_cell));
126
127 buffer_upload_array(gl, GL::ARRAY_BUFFER, cell_data, GL::DYNAMIC_DRAW);
128
129 gl.bind_vertex_array(None);
130 }
131}
132
133impl TerminalGrid {
134 pub fn new(
135 gl: &WebGl2RenderingContext,
136 atlas: FontAtlas,
137 screen_size: (i32, i32),
138 ) -> Result<Self, Error> {
139 let cell_size = atlas.cell_size();
140 let (cols, rows) = (screen_size.0 / cell_size.0, screen_size.1 / cell_size.1);
141
142 let cell_data = create_terminal_cell_data(cols, rows, &[' ' as u16]);
143 let cell_pos = CellStatic::create_grid(cols, rows);
144
145 let gpu = GpuResources::new(gl, &cell_pos, &cell_data, cell_size)?;
146
147 let grid = Self {
148 gpu,
149 terminal_size: (cols as u16, rows as u16),
150 canvas_size_px: screen_size,
151 cells: cell_data,
152 atlas,
153 fallback_glyph: ' ' as u16,
154 selection: SelectionTracker::new(),
155 cells_pending_flush: false,
156 };
157
158 grid.upload_ubo_data(gl);
159
160 Ok(grid)
161 }
162
163 pub fn set_fallback_glyph(&mut self, fallback: &str) {
165 self.fallback_glyph = self
166 .atlas
167 .get_base_glyph_id(fallback)
168 .unwrap_or(' ' as u16);
169 }
170
171 pub fn atlas(&self) -> &FontAtlas {
173 &self.atlas
174 }
175
176 pub(crate) fn canvas_size(&self) -> (i32, i32) {
178 self.canvas_size_px
179 }
180
181 pub fn cell_size(&self) -> (i32, i32) {
183 self.atlas.cell_size()
184 }
185
186 pub fn terminal_size(&self) -> (u16, u16) {
188 self.terminal_size
189 }
190
191 pub fn cell_data_mut(&mut self, x: u16, y: u16) -> Option<&mut CellDynamic> {
193 let (cols, _) = self.terminal_size;
194 let idx = y as usize * cols as usize + x as usize;
195 self.cells.get_mut(idx)
196 }
197
198 pub(crate) fn selection_tracker(&self) -> SelectionTracker {
200 self.selection.clone()
201 }
202
203 pub(super) fn get_symbols(&self, selection: CellIterator) -> CompactString {
205 let (cols, rows) = self.terminal_size;
206 let mut text = CompactString::new("");
207
208 for (idx, require_newline_after) in selection {
209 text.push_str(&self.get_cell_symbol(idx));
210 if require_newline_after {
211 text.push('\n'); }
213 }
214
215 text
216 }
217
218 fn get_cell_symbol(&self, idx: usize) -> Cow<'_, str> {
219 if idx < self.cells.len() {
220 let glyph_id = self.cells[idx].glyph_id();
221 self.atlas
222 .get_symbol(glyph_id)
223 .unwrap_or_else(|| self.fallback_symbol())
224 } else {
225 self.fallback_symbol()
226 }
227 }
228
229 fn upload_ubo_data(&self, gl: &WebGl2RenderingContext) {
238 let vertex_ubo = CellVertexUbo::new(self.canvas_size_px, self.cell_size());
239 self.gpu.ubo_vertex.upload_data(gl, &vertex_ubo);
240
241 let fragment_ubo = CellFragmentUbo::new(&self.atlas);
242 self.gpu
243 .ubo_fragment
244 .upload_data(gl, &fragment_ubo);
245 }
246
247 pub fn cell_count(&self) -> usize {
249 self.cells.len()
250 }
251
252 pub fn update_cells<'a>(
266 &mut self,
267 gl: &WebGl2RenderingContext,
268 cells: impl Iterator<Item = CellData<'a>>,
269 ) -> Result<(), Error> {
270 let atlas = &self.atlas;
272
273 let fallback_glyph = self.fallback_glyph;
274
275 let mut pending_cell: Option<CellDynamic> = None;
277 self.cells
278 .iter_mut()
279 .zip(cells)
280 .for_each(|(cell, data)| {
281 let base_glyph_id = atlas
282 .get_base_glyph_id(data.symbol)
283 .unwrap_or(fallback_glyph);
284
285 *cell = if let Some(second_cell) = pending_cell.take() {
286 second_cell
287 } else if base_glyph_id & Glyph::EMOJI_FLAG != 0 {
288 let glyph_id = base_glyph_id;
290 pending_cell = Some(CellDynamic::new(glyph_id + 1, data.fg, data.bg));
292 CellDynamic::new(glyph_id, data.fg, data.bg)
293 } else {
294 let glyph_id = base_glyph_id | data.style_bits;
296 CellDynamic::new(glyph_id, data.fg, data.bg)
297 }
298 });
299
300 self.cells_pending_flush = true;
301 Ok(())
302 }
303
304 pub(crate) fn update_cells_by_position<'a>(
305 &mut self,
306 gl: &WebGl2RenderingContext,
307 cells: impl Iterator<Item = (u16, u16, CellData<'a>)>,
308 ) -> Result<(), Error> {
309 let atlas = &self.atlas;
311
312 let cell_count = self.cells.len();
313 let fallback_glyph = self.fallback_glyph;
314 let w = self.terminal_size.0 as usize;
315
316 let mut skip_idx = None;
321
322 let last_halfwidth = atlas.get_max_halfwidth_base_glyph_id();
323 let is_doublewidth = |glyph_id: u16| {
324 (glyph_id & (Glyph::GLYPH_ID_MASK | Glyph::EMOJI_FLAG)) > last_halfwidth
325 };
326
327 cells
328 .map(|(x, y, cell)| (w * y as usize + x as usize, cell))
329 .filter(|(idx, _)| *idx < cell_count)
330 .for_each(|(idx, cell)| {
331 if skip_idx.take() == Some(idx) {
332 return;
334 }
335
336 let base_glyph_id = atlas
337 .get_base_glyph_id(cell.symbol)
338 .unwrap_or(fallback_glyph);
339
340 if is_doublewidth(base_glyph_id) {
341 let glyph_id = base_glyph_id;
342
343 self.cells[idx] = CellDynamic::new(glyph_id, cell.fg, cell.bg);
345
346 if let Some(c) = self.cells.get_mut(idx + 1) {
348 *c = CellDynamic::new(glyph_id + 1, cell.fg, cell.bg);
349 skip_idx = Some(idx + 1);
350 }
351 } else {
352 let glyph_id = base_glyph_id | cell.style_bits;
353 self.cells[idx] = CellDynamic::new(glyph_id, cell.fg, cell.bg);
354 }
355 });
356
357 self.cells_pending_flush = true;
358
359 Ok(())
360 }
361
362 pub(crate) fn update_cell(&mut self, x: u16, y: u16, cell_data: CellData) {
363 let (cols, _) = self.terminal_size;
364 let idx = y as usize * cols as usize + x as usize;
365 self.update_cell_by_index(idx, cell_data);
366
367 self.cells_pending_flush = true;
368 }
369
370 pub(crate) fn update_cell_by_index(&mut self, idx: usize, cell_data: CellData) {
371 if idx >= self.cells.len() {
372 return;
373 }
374
375 let atlas = &self.atlas;
376 let fallback_glyph = self.fallback_glyph;
377 let base_glyph_id = atlas
378 .get_base_glyph_id(cell_data.symbol)
379 .unwrap_or(fallback_glyph);
380
381 let last_halfwidth = atlas.get_max_halfwidth_base_glyph_id();
382 let is_doublewidth = |glyph_id: u16| {
383 (glyph_id & (Glyph::GLYPH_ID_MASK | Glyph::EMOJI_FLAG)) > last_halfwidth
384 };
385
386 if is_doublewidth(base_glyph_id) {
387 let glyph_id = base_glyph_id;
389 self.cells[idx] = CellDynamic::new(glyph_id, cell_data.fg, cell_data.bg);
390 if let Some(c) = self.cells.get_mut(idx + 1) {
391 *c = CellDynamic::new(glyph_id + 1, cell_data.fg, cell_data.bg);
392 }
393 } else {
394 let glyph_id = base_glyph_id | cell_data.style_bits;
396 self.cells[idx] = CellDynamic::new(glyph_id, cell_data.fg, cell_data.bg);
397 }
398
399 self.cells_pending_flush = true;
400 }
401
402 pub(crate) fn flush_cells(&mut self, gl: &WebGl2RenderingContext) -> Result<(), Error> {
404 if !self.cells_pending_flush {
405 return Ok(()); }
407
408 self.flip_selected_cell_colors();
412
413 self.gpu
414 .buffers
415 .upload_instance_data(gl, &self.cells);
416
417 self.flip_selected_cell_colors();
420
421 self.cells_pending_flush = false;
422 Ok(())
423 }
424
425 fn flip_selected_cell_colors(&mut self) {
426 if let Some(iter) = self.selected_cells_iter() {
427 iter.for_each(|(idx, _)| self.cells[idx].flip_colors());
428 }
429 }
430
431 fn selected_cells_iter(&self) -> Option<CellIterator> {
432 self.selection
433 .get_query()
434 .and_then(|query| query.range())
435 .map(|(start, end)| self.cell_iter(start, end, self.selection.mode()))
436 }
437
438 fn flip_cell_colors(&mut self, x: u16, y: u16) {
439 let (cols, _) = self.terminal_size;
440 let idx = y as usize * cols as usize + x as usize;
441 if idx < self.cells.len() {
442 self.cells[idx].flip_colors();
443 }
444 }
445
446 pub fn resize(
462 &mut self,
463 gl: &WebGl2RenderingContext,
464 canvas_size: (i32, i32),
465 ) -> Result<(), Error> {
466 self.canvas_size_px = canvas_size;
467
468 self.upload_ubo_data(gl);
470
471 let cell_size = self.atlas.cell_size();
472 let cols = canvas_size.0 / cell_size.0;
473 let rows = canvas_size.1 / cell_size.1;
474 if self.terminal_size == (cols as u16, rows as u16) {
475 return Ok(()); }
477
478 gl.bind_vertex_array(Some(&self.gpu.buffers.vao));
480
481 gl.delete_buffer(Some(&self.gpu.buffers.instance_cell));
483 gl.delete_buffer(Some(&self.gpu.buffers.instance_pos));
484
485 let current_size = (self.terminal_size.0 as i32, self.terminal_size.1 as i32);
487 let cell_data = resize_cell_grid(&self.cells, current_size, (cols, rows));
488 self.cells = cell_data;
489
490 let cell_pos = CellStatic::create_grid(cols, rows);
491
492 self.gpu.buffers.instance_cell = create_dynamic_instance_buffer(gl, &self.cells)?;
494 self.gpu.buffers.instance_pos = create_static_instance_buffer(gl, &cell_pos)?;
495
496 gl.bind_vertex_array(None);
498
499 self.terminal_size = (cols as u16, rows as u16);
500
501 Ok(())
502 }
503
504 pub fn recreate_resources(&mut self, gl: &WebGl2RenderingContext) -> Result<(), Error> {
521 let cell_size = self.atlas.cell_size();
522 let (cols, rows) = (self.terminal_size.0 as i32, self.terminal_size.1 as i32);
523 let cell_pos = CellStatic::create_grid(cols, rows);
524
525 self.gpu = GpuResources::new(gl, &cell_pos, &self.cells, cell_size)?;
527
528 self.upload_ubo_data(gl);
530
531 self.cells_pending_flush = true;
533
534 Ok(())
535 }
536
537 pub fn recreate_atlas_texture(&mut self, gl: &WebGl2RenderingContext) -> Result<(), Error> {
542 self.atlas.recreate_texture(gl)
543 }
544
545 pub fn base_glyph_id(&self, symbol: &str) -> Option<u16> {
547 self.atlas.get_base_glyph_id(symbol)
548 }
549
550 fn fallback_symbol(&self) -> Cow<'_, str> {
551 self.atlas
552 .get_symbol(self.fallback_glyph)
553 .unwrap_or(Cow::Borrowed(" "))
554 }
555
556 fn fill_glyphs(atlas: &FontAtlas) -> Vec<u16> {
557 [
558 ("🤫", FontStyle::Normal),
559 ("🙌", FontStyle::Normal),
560 ("n", FontStyle::Normal),
561 ("o", FontStyle::Normal),
562 ("r", FontStyle::Normal),
563 ("m", FontStyle::Normal),
564 ("a", FontStyle::Normal),
565 ("l", FontStyle::Normal),
566 ("b", FontStyle::Bold),
567 ("o", FontStyle::Bold),
568 ("l", FontStyle::Bold),
569 ("d", FontStyle::Bold),
570 ("i", FontStyle::Italic),
571 ("t", FontStyle::Italic),
572 ("a", FontStyle::Italic),
573 ("l", FontStyle::Italic),
574 ("i", FontStyle::Italic),
575 ("c", FontStyle::Italic),
576 ("b", FontStyle::BoldItalic),
577 ("-", FontStyle::BoldItalic),
578 ("i", FontStyle::BoldItalic),
579 ("t", FontStyle::BoldItalic),
580 ("a", FontStyle::BoldItalic),
581 ("l", FontStyle::BoldItalic),
582 ("i", FontStyle::BoldItalic),
583 ("c", FontStyle::BoldItalic),
584 ("🤪", FontStyle::Normal),
585 ("🤩", FontStyle::Normal),
586 ]
587 .into_iter()
588 .map(|(symbol, style)| {
589 atlas
590 .get_base_glyph_id(symbol)
591 .map(|g| g | style as u16)
592 })
593 .map(|g| g.unwrap_or(' ' as u16))
594 .collect()
595 }
596}
597
598fn resize_cell_grid(
599 cells: &[CellDynamic],
600 old_size: (i32, i32),
601 new_size: (i32, i32),
602) -> Vec<CellDynamic> {
603 let new_len = new_size.0 * new_size.1;
604
605 let mut new_cells = Vec::with_capacity(new_len as usize);
606 for _ in 0..new_len {
607 new_cells.push(CellDynamic::new(' ' as u16, 0xFFFFFF, 0x000000));
608 }
609
610 for y in 0..min(old_size.1, new_size.1) {
611 for x in 0..min(old_size.0, new_size.0) {
612 let new_idx = (y * new_size.0 + x) as usize;
613 let old_idx = (y * old_size.0 + x) as usize;
614 new_cells[new_idx] = cells[old_idx];
615 }
616 }
617
618 new_cells
619}
620
621fn create_vao(gl: &WebGl2RenderingContext) -> Result<web_sys::WebGlVertexArrayObject, Error> {
622 gl.create_vertex_array()
623 .ok_or(Error::vertex_array_creation_failed())
624}
625
626fn setup_buffers(
627 gl: &WebGl2RenderingContext,
628 vao: web_sys::WebGlVertexArrayObject,
629 cell_pos: &[CellStatic],
630 cell_data: &[CellDynamic],
631 cell_size: (i32, i32),
632) -> Result<TerminalBuffers, Error> {
633 let (w, h) = (cell_size.0 as f32, cell_size.1 as f32);
634
635 let overlap = 0.0; #[rustfmt::skip]
638 let vertices = [
639 w + overlap, -overlap, 1.0, 0.0, -overlap, h + overlap, 0.0, 1.0, w + overlap, h + overlap, 1.0, 1.0, -overlap, -overlap, 0.0, 0.0 ];
645 let indices = [0, 1, 2, 0, 3, 1];
646
647 Ok(TerminalBuffers {
648 vao,
649 vertices: create_buffer_f32(gl, GL::ARRAY_BUFFER, &vertices, GL::STATIC_DRAW)?,
650 instance_pos: create_static_instance_buffer(gl, cell_pos)?,
651 instance_cell: create_dynamic_instance_buffer(gl, cell_data)?,
652 indices: create_buffer_u8(gl, GL::ELEMENT_ARRAY_BUFFER, &indices, GL::STATIC_DRAW)?,
653 })
654}
655
656fn create_buffer_u8(
657 gl: &WebGl2RenderingContext,
658 target: u32,
659 data: &[u8],
660 usage: u32,
661) -> Result<web_sys::WebGlBuffer, Error> {
662 let index_buf = gl
663 .create_buffer()
664 .ok_or(Error::buffer_creation_failed("vbo-u8"))?;
665 gl.bind_buffer(target, Some(&index_buf));
666
667 gl.buffer_data_with_u8_array(target, data, usage);
668
669 Ok(index_buf)
670}
671
672fn create_buffer_f32(
673 gl: &WebGl2RenderingContext,
674 target: u32,
675 data: &[f32],
676 usage: u32,
677) -> Result<web_sys::WebGlBuffer, Error> {
678 let buffer = gl
679 .create_buffer()
680 .ok_or(Error::buffer_creation_failed("vbo-f32"))?;
681
682 gl.bind_buffer(target, Some(&buffer));
683
684 unsafe {
685 let view = js_sys::Float32Array::view(data);
686 gl.buffer_data_with_array_buffer_view(target, &view, usage);
687 }
688
689 const STRIDE: i32 = (2 + 2) * 4; enable_vertex_attrib(gl, attrib::POS, 2, GL::FLOAT, 0, STRIDE);
692 enable_vertex_attrib(gl, attrib::UV, 2, GL::FLOAT, 8, STRIDE);
693
694 Ok(buffer)
695}
696
697fn create_static_instance_buffer(
698 gl: &WebGl2RenderingContext,
699 instance_data: &[CellStatic],
700) -> Result<web_sys::WebGlBuffer, Error> {
701 let instance_buf = gl
702 .create_buffer()
703 .ok_or(Error::buffer_creation_failed("static-instance-buffer"))?;
704
705 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&instance_buf));
706 buffer_upload_array(gl, GL::ARRAY_BUFFER, instance_data, GL::STATIC_DRAW);
707
708 let stride = size_of::<CellStatic>() as i32;
709 enable_vertex_attrib_array(gl, attrib::GRID_XY, 2, GL::UNSIGNED_SHORT, 0, stride);
710
711 Ok(instance_buf)
712}
713
714fn create_dynamic_instance_buffer(
715 gl: &WebGl2RenderingContext,
716 instance_data: &[CellDynamic],
717) -> Result<web_sys::WebGlBuffer, Error> {
718 let instance_buf = gl
719 .create_buffer()
720 .ok_or(Error::buffer_creation_failed("dynamic-instance-buffer"))?;
721
722 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&instance_buf));
723 buffer_upload_array(gl, GL::ARRAY_BUFFER, instance_data, GL::DYNAMIC_DRAW);
724
725 let stride = size_of::<CellDynamic>() as i32;
726
727 enable_vertex_attrib_array(
729 gl,
730 attrib::PACKED_DEPTH_FG_BG,
731 2,
732 GL::UNSIGNED_INT,
733 0,
734 stride,
735 );
736
737 Ok(instance_buf)
738}
739
740fn enable_vertex_attrib_array(
741 gl: &WebGl2RenderingContext,
742 index: u32,
743 size: i32,
744 type_: u32,
745 offset: i32,
746 stride: i32,
747) {
748 enable_vertex_attrib(gl, index, size, type_, offset, stride);
749 gl.vertex_attrib_divisor(index, 1);
750}
751
752fn enable_vertex_attrib(
753 gl: &WebGl2RenderingContext,
754 index: u32,
755 size: i32,
756 type_: u32,
757 offset: i32,
758 stride: i32,
759) {
760 gl.enable_vertex_attrib_array(index);
761 if type_ == GL::FLOAT {
762 gl.vertex_attrib_pointer_with_i32(index, size, type_, false, stride, offset);
763 } else {
764 gl.vertex_attrib_i_pointer_with_i32(index, size, type_, stride, offset);
765 }
766}
767
768impl Drawable for TerminalGrid {
769 fn prepare(&self, context: &mut RenderContext) {
770 let gl = context.gl;
771
772 self.gpu.shader.use_program(gl);
773
774 gl.bind_vertex_array(Some(&self.gpu.buffers.vao));
775
776 self.atlas.bind(gl, 0);
777 self.gpu.ubo_vertex.bind(context.gl);
778 self.gpu.ubo_fragment.bind(context.gl);
779 gl.uniform1i(Some(&self.gpu.sampler_loc), 0);
780 }
781
782 fn draw(&self, context: &mut RenderContext) {
783 let gl = context.gl;
784 let cell_count = self.cells.len() as i32;
785
786 gl.draw_elements_instanced_with_i32(GL::TRIANGLES, 6, GL::UNSIGNED_BYTE, 0, cell_count);
787 }
788
789 fn cleanup(&self, context: &mut RenderContext) {
790 let gl = context.gl;
791 gl.bind_vertex_array(None);
792 gl.bind_texture(GL::TEXTURE_2D_ARRAY, None);
793
794 self.gpu.ubo_vertex.unbind(gl);
795 self.gpu.ubo_fragment.unbind(gl);
796 }
797}
798
799#[derive(Debug, Copy, Clone)]
811pub struct CellData<'a> {
812 symbol: &'a str,
813 style_bits: u16,
814 fg: u32,
815 bg: u32,
816}
817
818impl<'a> CellData<'a> {
819 pub fn new(symbol: &'a str, style: FontStyle, effect: GlyphEffect, fg: u32, bg: u32) -> Self {
831 Self::new_with_style_bits(symbol, style.style_mask() | effect as u16, fg, bg)
832 }
833
834 pub fn new_with_style_bits(symbol: &'a str, style_bits: u16, fg: u32, bg: u32) -> Self {
858 debug_assert!(
860 0x81FF & style_bits == 0,
861 "Invalid style bits: {style_bits:#04x}"
862 );
863 Self { symbol, style_bits, fg, bg }
864 }
865}
866
867#[repr(C, align(4))]
888struct CellStatic {
889 pub grid_xy: [u16; 2],
891}
892
893#[derive(Debug, Clone, Copy)]
921#[repr(C, align(4))]
922pub struct CellDynamic {
923 data: [u8; 8], }
936
937impl CellStatic {
938 fn create_grid(cols: i32, rows: i32) -> Vec<Self> {
939 debug_assert!(cols > 0 && cols < u16::MAX as i32, "cols: {cols}");
940 debug_assert!(rows > 0 && rows < u16::MAX as i32, "rows: {rows}");
941
942 (0..rows)
943 .flat_map(|row| (0..cols).map(move |col| (col, row)))
944 .map(|(col, row)| Self { grid_xy: [col as u16, row as u16] })
945 .collect()
946 }
947}
948
949impl CellDynamic {
950 const GLYPH_STYLE_MASK: u16 =
951 Glyph::BOLD_FLAG | Glyph::ITALIC_FLAG | Glyph::UNDERLINE_FLAG | Glyph::STRIKETHROUGH_FLAG;
952
953 #[inline]
954 pub fn new(glyph_id: u16, fg: u32, bg: u32) -> Self {
955 let mut data = [0; 8];
956
957 let glyph_id = glyph_id.to_le_bytes();
959 data[0] = glyph_id[0];
960 data[1] = glyph_id[1];
961
962 let fg = fg.to_le_bytes();
963 data[2] = fg[2]; data[3] = fg[1]; data[4] = fg[0]; let bg = bg.to_le_bytes();
968 data[5] = bg[2]; data[6] = bg[1]; data[7] = bg[0]; Self { data }
973 }
974
975 pub fn style(&mut self, style_bits: u16) {
977 let glyph_id = (self.glyph_id() & !Self::GLYPH_STYLE_MASK) | style_bits;
978 self.data[..2].copy_from_slice(&glyph_id.to_le_bytes());
979 }
980
981 pub fn flip_colors(&mut self) {
983 let fg = [self.data[2], self.data[3], self.data[4]];
985 self.data[2] = self.data[5]; self.data[3] = self.data[6]; self.data[4] = self.data[7]; self.data[5] = fg[0]; self.data[6] = fg[1]; self.data[7] = fg[2]; }
992
993 pub fn fg_color(&mut self, fg: u32) {
995 let fg = fg.to_le_bytes();
996 self.data[2] = fg[2]; self.data[3] = fg[1]; self.data[4] = fg[0]; }
1000
1001 pub fn bg_color(&mut self, bg: u32) {
1003 let bg = bg.to_le_bytes();
1004 self.data[5] = bg[2]; self.data[6] = bg[1]; self.data[7] = bg[0]; }
1008
1009 pub fn get_fg_color(&self) -> u32 {
1011 ((self.data[2] as u32) << 16) | ((self.data[3] as u32) << 8) | (self.data[4] as u32)
1013 }
1014
1015 pub fn get_bg_color(&self) -> u32 {
1017 ((self.data[5] as u32) << 16) | ((self.data[6] as u32) << 8) | (self.data[7] as u32)
1019 }
1020
1021 pub fn get_style(&self) -> u16 {
1023 self.glyph_id() & Self::GLYPH_STYLE_MASK
1024 }
1025
1026 pub fn is_emoji(&self) -> bool {
1028 self.glyph_id() & Glyph::EMOJI_FLAG != 0
1029 }
1030
1031 #[inline]
1032 fn glyph_id(&self) -> u16 {
1033 u16::from_le_bytes([self.data[0], self.data[1]])
1034 }
1035}
1036
1037#[repr(C, align(16))] struct CellVertexUbo {
1039 pub projection: [f32; 16], pub cell_size: [f32; 2], pub _padding: [f32; 2],
1042}
1043
1044#[repr(C, align(16))] struct CellFragmentUbo {
1046 pub padding_frac: [f32; 2], pub underline_pos: f32, pub underline_thickness: f32, pub strikethrough_pos: f32, pub strikethrough_thickness: f32, pub _padding: [f32; 2],
1052}
1053
1054impl CellVertexUbo {
1055 pub const BINDING_POINT: u32 = 0;
1056
1057 fn new(canvas_size: (i32, i32), cell_size: (i32, i32)) -> Self {
1058 let projection =
1059 Mat4::orthographic_from_size(canvas_size.0 as f32, canvas_size.1 as f32).data;
1060 Self {
1061 projection,
1062 cell_size: [cell_size.0 as f32, cell_size.1 as f32],
1063 _padding: [0.0; 2], }
1065 }
1066}
1067
1068impl CellFragmentUbo {
1069 pub const BINDING_POINT: u32 = 1;
1070
1071 fn new(atlas: &FontAtlas) -> Self {
1072 let cell_size = atlas.cell_size();
1073 let underline = atlas.underline();
1074 let strikethrough = atlas.strikethrough();
1075 Self {
1076 padding_frac: [
1077 FontAtlasData::PADDING as f32 / cell_size.0 as f32,
1078 FontAtlasData::PADDING as f32 / cell_size.1 as f32,
1079 ],
1080 underline_pos: underline.position,
1081 underline_thickness: underline.thickness,
1082 strikethrough_pos: strikethrough.position,
1083 strikethrough_thickness: strikethrough.thickness,
1084 _padding: [0.0; 2], }
1086 }
1087}
1088
1089fn create_terminal_cell_data(cols: i32, rows: i32, fill_glyph: &[u16]) -> Vec<CellDynamic> {
1090 let glyph_len = fill_glyph.len();
1091 (0..cols * rows)
1092 .map(|i| CellDynamic::new(fill_glyph[i as usize % glyph_len], 0x00ff_ffff, 0x0000_0000))
1093 .collect()
1094}
1095
1096mod attrib {
1097 pub const POS: u32 = 0;
1098 pub const UV: u32 = 1;
1099
1100 pub const GRID_XY: u32 = 2;
1101 pub const PACKED_DEPTH_FG_BG: u32 = 3;
1102}