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, CellQuery, Drawable, GL, RenderContext, ShaderProgram, StaticFontAtlas,
11 atlas::{FontAtlas, GlyphSlot},
12 buffer_upload_array,
13 selection::SelectionTracker,
14 ubo::UniformBufferObject,
15 },
16 mat4::Mat4,
17};
18
19#[derive(Debug)]
26pub struct TerminalGrid {
27 gpu: GpuResources,
29 cells: Vec<CellDynamic>,
31 terminal_size: (u16, u16),
33 canvas_size_px: (i32, i32),
35 atlas: FontAtlas,
37 fallback_glyph: u16,
39 selection: SelectionTracker,
41 cells_pending_flush: bool,
43}
44
45#[derive(Debug)]
52struct GpuResources {
53 shader: ShaderProgram,
55 buffers: TerminalBuffers,
57 ubo_vertex: UniformBufferObject,
59 ubo_fragment: UniformBufferObject,
61 sampler_loc: web_sys::WebGlUniformLocation,
63}
64
65impl GpuResources {
66 const FRAGMENT_GLSL: &'static str = include_str!("../shaders/cell.frag");
67 const VERTEX_GLSL: &'static str = include_str!("../shaders/cell.vert");
68
69 fn new(
78 gl: &WebGl2RenderingContext,
79 cell_pos: &[CellStatic],
80 cell_data: &[CellDynamic],
81 cell_size: (i32, i32),
82 ) -> Result<Self, Error> {
83 let vao = create_vao(gl)?;
85 gl.bind_vertex_array(Some(&vao));
86
87 let buffers = setup_buffers(gl, vao, cell_pos, cell_data, cell_size)?;
89
90 gl.bind_vertex_array(None);
92
93 let shader = ShaderProgram::create(gl, Self::VERTEX_GLSL, Self::FRAGMENT_GLSL)?;
95 shader.use_program(gl);
96
97 let ubo_vertex = UniformBufferObject::new(gl, CellVertexUbo::BINDING_POINT)?;
98 ubo_vertex.bind_to_shader(gl, &shader, "VertUbo")?;
99 let ubo_fragment = UniformBufferObject::new(gl, CellFragmentUbo::BINDING_POINT)?;
100 ubo_fragment.bind_to_shader(gl, &shader, "FragUbo")?;
101
102 let sampler_loc = gl
103 .get_uniform_location(&shader.program, "u_sampler")
104 .ok_or(Error::uniform_location_failed("u_sampler"))?;
105
106 Ok(Self {
107 shader,
108 buffers,
109 ubo_vertex,
110 ubo_fragment,
111 sampler_loc,
112 })
113 }
114}
115
116#[derive(Debug)]
117struct TerminalBuffers {
118 vao: web_sys::WebGlVertexArrayObject,
119 vertices: web_sys::WebGlBuffer,
120 instance_pos: web_sys::WebGlBuffer,
121 instance_cell: web_sys::WebGlBuffer,
122 indices: web_sys::WebGlBuffer,
123}
124
125impl TerminalBuffers {
126 fn upload_instance_data<T>(&self, gl: &WebGl2RenderingContext, cell_data: &[T]) {
127 gl.bind_vertex_array(Some(&self.vao));
128 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&self.instance_cell));
129
130 buffer_upload_array(gl, GL::ARRAY_BUFFER, cell_data, GL::DYNAMIC_DRAW);
131
132 gl.bind_vertex_array(None);
133 }
134}
135
136impl TerminalGrid {
137 pub(crate) fn new(
138 gl: &WebGl2RenderingContext,
139 atlas: FontAtlas,
140 screen_size: (i32, i32),
141 ) -> Result<Self, Error> {
142 let cell_size = atlas.cell_size();
143 let (cols, rows) = (screen_size.0 / cell_size.0, screen_size.1 / cell_size.1);
144
145 let cell_data = create_terminal_cell_data(cols, rows, &[' ' as u16]);
146 let cell_pos = CellStatic::create_grid(cols, rows);
147
148 let gpu = GpuResources::new(gl, &cell_pos, &cell_data, cell_size)?;
149
150 let grid = Self {
151 gpu,
152 terminal_size: (cols as u16, rows as u16),
153 canvas_size_px: screen_size,
154 cells: cell_data,
155 atlas,
156 fallback_glyph: ' ' as u16,
157 selection: SelectionTracker::new(),
158 cells_pending_flush: false,
159 };
160
161 grid.upload_ubo_data(gl);
162
163 Ok(grid)
164 }
165
166 pub fn set_fallback_glyph(&mut self, fallback: &str) {
168 self.fallback_glyph = self
169 .atlas
170 .get_glyph_id(fallback, FontStyle::Normal as u16)
171 .unwrap_or(' ' as u16);
172 }
173
174 pub(crate) fn atlas(&self) -> &FontAtlas {
176 &self.atlas
177 }
178
179 pub(crate) fn canvas_size(&self) -> (i32, i32) {
181 self.canvas_size_px
182 }
183
184 pub fn cell_size(&self) -> (i32, i32) {
186 self.atlas.cell_size()
187 }
188
189 pub fn terminal_size(&self) -> (u16, u16) {
191 self.terminal_size
192 }
193
194 pub fn cell_data_mut(&mut self, x: u16, y: u16) -> Option<&mut CellDynamic> {
196 let (cols, _) = self.terminal_size;
197 let idx = y as usize * cols as usize + x as usize;
198 self.cells.get_mut(idx)
199 }
200
201 pub(crate) fn selection_tracker(&self) -> SelectionTracker {
203 self.selection.clone()
204 }
205
206 pub(super) fn get_symbols(&self, selection: CellIterator) -> CompactString {
208 let (cols, rows) = self.terminal_size;
209 let mut text = CompactString::new("");
210
211 for (idx, require_newline_after) in selection {
212 let cell_symbol = self.get_cell_symbol(idx);
213 if cell_symbol.is_some() {
214 text.push_str(&cell_symbol.unwrap_or_default());
215 }
216
217 if require_newline_after {
218 text.push('\n'); }
220 }
221
222 text
223 }
224
225 pub(crate) fn hash_cells(&self, selection: CellQuery) -> u64 {
226 use std::hash::{Hash, Hasher};
227
228 use rustc_hash::FxHasher;
229
230 let mut hasher = FxHasher::default();
231 for (idx, _) in self.cell_iter(selection) {
232 self.cells[idx].hash(&mut hasher);
233 }
234
235 hasher.finish()
236 }
237
238 fn get_cell_symbol(&self, idx: usize) -> Option<CompactString> {
239 if idx < self.cells.len() {
240 let glyph_id = self.cells[idx].glyph_id();
241 let cell_symbol = self.atlas.get_symbol(glyph_id);
242 if cell_symbol.is_some() {
243 return cell_symbol;
244 }
245 }
246
247 self.fallback_symbol()
248 }
249
250 fn upload_ubo_data(&self, gl: &WebGl2RenderingContext) {
259 let vertex_ubo = CellVertexUbo::new(self.canvas_size_px, self.cell_size());
260 self.gpu.ubo_vertex.upload_data(gl, &vertex_ubo);
261
262 let fragment_ubo = CellFragmentUbo::new(&self.atlas);
263 self.gpu
264 .ubo_fragment
265 .upload_data(gl, &fragment_ubo);
266 }
267
268 pub fn cell_count(&self) -> usize {
270 self.cells.len()
271 }
272
273 pub fn update_cells<'a>(
287 &mut self,
288 gl: &WebGl2RenderingContext,
289 cells: impl Iterator<Item = CellData<'a>>,
290 ) -> Result<(), Error> {
291 let atlas = &self.atlas;
293
294 let fallback_glyph = GlyphSlot::Normal(self.fallback_glyph);
295
296 let mut pending_cell: Option<CellDynamic> = None;
298 self.cells
299 .iter_mut()
300 .zip(cells)
301 .for_each(|(cell, data)| {
302 let glyph = atlas
303 .resolve_glyph_slot(data.symbol, data.style_bits)
304 .unwrap_or(fallback_glyph);
305
306 *cell = if let Some(second_cell) = pending_cell.take() {
307 second_cell
308 } else {
309 match glyph {
310 GlyphSlot::Normal(id) => CellDynamic::new(id, data.fg, data.bg),
311
312 GlyphSlot::Wide(id) | GlyphSlot::Emoji(id) => {
313 pending_cell = Some(CellDynamic::new(id + 1, data.fg, data.bg));
315 CellDynamic::new(id, data.fg, data.bg)
316 },
317 }
318 }
319 });
320
321 self.cells_pending_flush = true;
322 Ok(())
323 }
324
325 pub(crate) fn update_cells_by_position<'a>(
326 &mut self,
327 cells: impl Iterator<Item = (u16, u16, CellData<'a>)>,
328 ) -> Result<(), Error> {
329 let cols = self.terminal_size.0 as usize;
330 let cells_by_index = cells.map(|(x, y, data)| (y as usize * cols + x as usize, data));
331
332 self.update_cells_by_index(cells_by_index)
333 }
334
335 pub(crate) fn update_cells_by_index<'a>(
336 &mut self,
337 cells: impl Iterator<Item = (usize, CellData<'a>)>,
338 ) -> Result<(), Error> {
339 let atlas = &self.atlas;
341
342 let cell_count = self.cells.len();
343 let fallback_glyph = GlyphSlot::Normal(self.fallback_glyph);
344
345 let mut skip_idx = None;
350
351 cells
352 .filter(|(idx, _)| *idx < cell_count)
353 .for_each(|(idx, cell)| {
354 if skip_idx.take() == Some(idx) {
355 return;
357 }
358
359 let glyph = atlas
360 .resolve_glyph_slot(cell.symbol, cell.style_bits)
361 .unwrap_or(fallback_glyph);
362
363 match glyph {
364 GlyphSlot::Normal(id) => {
365 self.cells[idx] = CellDynamic::new(id, cell.fg, cell.bg);
366 },
367
368 GlyphSlot::Wide(id) | GlyphSlot::Emoji(id) => {
369 self.cells[idx] = CellDynamic::new(id, cell.fg, cell.bg);
371
372 if let Some(c) = self.cells.get_mut(idx + 1) {
374 *c = CellDynamic::new(id + 1, cell.fg, cell.bg);
375 skip_idx = Some(idx + 1);
376 }
377 },
378 }
379 });
380
381 self.cells_pending_flush = true;
382
383 Ok(())
384 }
385
386 pub(crate) fn update_cell(&mut self, x: u16, y: u16, cell_data: CellData) -> Result<(), Error> {
387 let (cols, _) = self.terminal_size;
388 let idx = y as usize * cols as usize + x as usize;
389 self.update_cell_by_index(idx, cell_data)
390 }
391
392 pub(crate) fn update_cell_by_index(
393 &mut self,
394 idx: usize,
395 cell_data: CellData,
396 ) -> Result<(), Error> {
397 self.update_cells_by_index(std::iter::once((idx, cell_data)))
398 }
399
400 pub(crate) fn flush_cells(&mut self, gl: &WebGl2RenderingContext) -> Result<(), Error> {
402 if !self.cells_pending_flush {
403 return Ok(()); }
405
406 self.clear_stale_selection();
409
410 self.flip_selected_cell_colors();
414
415 self.gpu
416 .buffers
417 .upload_instance_data(gl, &self.cells);
418
419 self.flip_selected_cell_colors();
422
423 self.cells_pending_flush = false;
424 Ok(())
425 }
426
427 fn flip_selected_cell_colors(&mut self) {
428 if let Some(iter) = self.selected_cells_iter() {
429 iter.for_each(|(idx, _)| self.cells[idx].flip_colors());
430 }
431 }
432
433 fn selected_cells_iter(&self) -> Option<CellIterator> {
434 self.selection
435 .get_query()
436 .map(|query| self.cell_iter(query))
437 }
438
439 fn flip_cell_colors(&mut self, x: u16, y: u16) {
440 let (cols, _) = self.terminal_size;
441 let idx = y as usize * cols as usize + x as usize;
442 if idx < self.cells.len() {
443 self.cells[idx].flip_colors();
444 }
445 }
446
447 pub fn resize(
463 &mut self,
464 gl: &WebGl2RenderingContext,
465 canvas_size: (i32, i32),
466 ) -> Result<(), Error> {
467 self.canvas_size_px = canvas_size;
468
469 self.upload_ubo_data(gl);
471
472 let cell_size = self.atlas.cell_size();
473 let cols = canvas_size.0 / cell_size.0;
474 let rows = canvas_size.1 / cell_size.1;
475 if self.terminal_size == (cols as u16, rows as u16) {
476 return Ok(()); }
478
479 gl.bind_vertex_array(Some(&self.gpu.buffers.vao));
481
482 gl.delete_buffer(Some(&self.gpu.buffers.instance_cell));
484 gl.delete_buffer(Some(&self.gpu.buffers.instance_pos));
485
486 let current_size = (self.terminal_size.0 as i32, self.terminal_size.1 as i32);
488 let cell_data = resize_cell_grid(&self.cells, current_size, (cols, rows));
489 self.cells = cell_data;
490
491 let cell_pos = CellStatic::create_grid(cols, rows);
492
493 self.gpu.buffers.instance_cell = create_dynamic_instance_buffer(gl, &self.cells)?;
495 self.gpu.buffers.instance_pos = create_static_instance_buffer(gl, &cell_pos)?;
496
497 gl.bind_vertex_array(None);
499
500 self.terminal_size = (cols as u16, rows as u16);
501
502 Ok(())
503 }
504
505 pub fn recreate_resources(&mut self, gl: &WebGl2RenderingContext) -> Result<(), Error> {
522 let cell_size = self.atlas.cell_size();
523 let (cols, rows) = (self.terminal_size.0 as i32, self.terminal_size.1 as i32);
524 let cell_pos = CellStatic::create_grid(cols, rows);
525
526 self.gpu = GpuResources::new(gl, &cell_pos, &self.cells, cell_size)?;
528
529 self.upload_ubo_data(gl);
531
532 self.cells_pending_flush = true;
534
535 Ok(())
536 }
537
538 pub fn recreate_atlas_texture(&mut self, gl: &WebGl2RenderingContext) -> Result<(), Error> {
543 self.atlas.recreate_texture(gl)
544 }
545
546 pub fn base_glyph_id(&self, symbol: &str) -> Option<u16> {
548 self.atlas.get_base_glyph_id(symbol)
549 }
550
551 fn fallback_symbol(&self) -> Option<CompactString> {
552 self.atlas.get_symbol(self.fallback_glyph)
553 }
554
555 fn clear_stale_selection(&self) {
556 if let Some(query) = self.selection_tracker().get_query()
557 && let Some(hash) = query.content_hash
558 && hash != self.hash_cells(query)
559 {
560 self.selection.clear();
561 }
562 }
563}
564
565fn resize_cell_grid(
566 cells: &[CellDynamic],
567 old_size: (i32, i32),
568 new_size: (i32, i32),
569) -> Vec<CellDynamic> {
570 let new_len = new_size.0 * new_size.1;
571
572 let mut new_cells = Vec::with_capacity(new_len as usize);
573 for _ in 0..new_len {
574 new_cells.push(CellDynamic::new(' ' as u16, 0xFFFFFF, 0x000000));
575 }
576
577 for y in 0..min(old_size.1, new_size.1) {
578 for x in 0..min(old_size.0, new_size.0) {
579 let new_idx = (y * new_size.0 + x) as usize;
580 let old_idx = (y * old_size.0 + x) as usize;
581 new_cells[new_idx] = cells[old_idx];
582 }
583 }
584
585 new_cells
586}
587
588fn create_vao(gl: &WebGl2RenderingContext) -> Result<web_sys::WebGlVertexArrayObject, Error> {
589 gl.create_vertex_array()
590 .ok_or(Error::vertex_array_creation_failed())
591}
592
593fn setup_buffers(
594 gl: &WebGl2RenderingContext,
595 vao: web_sys::WebGlVertexArrayObject,
596 cell_pos: &[CellStatic],
597 cell_data: &[CellDynamic],
598 cell_size: (i32, i32),
599) -> Result<TerminalBuffers, Error> {
600 let (w, h) = (cell_size.0 as f32, cell_size.1 as f32);
601
602 let overlap = 0.0; #[rustfmt::skip]
605 let vertices = [
606 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 ];
612 let indices = [0, 1, 2, 0, 3, 1];
613
614 Ok(TerminalBuffers {
615 vao,
616 vertices: create_buffer_f32(gl, GL::ARRAY_BUFFER, &vertices, GL::STATIC_DRAW)?,
617 instance_pos: create_static_instance_buffer(gl, cell_pos)?,
618 instance_cell: create_dynamic_instance_buffer(gl, cell_data)?,
619 indices: create_buffer_u8(gl, GL::ELEMENT_ARRAY_BUFFER, &indices, GL::STATIC_DRAW)?,
620 })
621}
622
623fn create_buffer_u8(
624 gl: &WebGl2RenderingContext,
625 target: u32,
626 data: &[u8],
627 usage: u32,
628) -> Result<web_sys::WebGlBuffer, Error> {
629 let index_buf = gl
630 .create_buffer()
631 .ok_or(Error::buffer_creation_failed("vbo-u8"))?;
632 gl.bind_buffer(target, Some(&index_buf));
633
634 gl.buffer_data_with_u8_array(target, data, usage);
635
636 Ok(index_buf)
637}
638
639fn create_buffer_f32(
640 gl: &WebGl2RenderingContext,
641 target: u32,
642 data: &[f32],
643 usage: u32,
644) -> Result<web_sys::WebGlBuffer, Error> {
645 let buffer = gl
646 .create_buffer()
647 .ok_or(Error::buffer_creation_failed("vbo-f32"))?;
648
649 gl.bind_buffer(target, Some(&buffer));
650
651 unsafe {
652 let view = js_sys::Float32Array::view(data);
653 gl.buffer_data_with_array_buffer_view(target, &view, usage);
654 }
655
656 const STRIDE: i32 = (2 + 2) * 4; enable_vertex_attrib(gl, attrib::POS, 2, GL::FLOAT, 0, STRIDE);
659 enable_vertex_attrib(gl, attrib::UV, 2, GL::FLOAT, 8, STRIDE);
660
661 Ok(buffer)
662}
663
664fn create_static_instance_buffer(
665 gl: &WebGl2RenderingContext,
666 instance_data: &[CellStatic],
667) -> Result<web_sys::WebGlBuffer, Error> {
668 let instance_buf = gl
669 .create_buffer()
670 .ok_or(Error::buffer_creation_failed("static-instance-buffer"))?;
671
672 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&instance_buf));
673 buffer_upload_array(gl, GL::ARRAY_BUFFER, instance_data, GL::STATIC_DRAW);
674
675 let stride = size_of::<CellStatic>() as i32;
676 enable_vertex_attrib_array(gl, attrib::GRID_XY, 2, GL::UNSIGNED_SHORT, 0, stride);
677
678 Ok(instance_buf)
679}
680
681fn create_dynamic_instance_buffer(
682 gl: &WebGl2RenderingContext,
683 instance_data: &[CellDynamic],
684) -> Result<web_sys::WebGlBuffer, Error> {
685 let instance_buf = gl
686 .create_buffer()
687 .ok_or(Error::buffer_creation_failed("dynamic-instance-buffer"))?;
688
689 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&instance_buf));
690 buffer_upload_array(gl, GL::ARRAY_BUFFER, instance_data, GL::DYNAMIC_DRAW);
691
692 let stride = size_of::<CellDynamic>() as i32;
693
694 enable_vertex_attrib_array(
696 gl,
697 attrib::PACKED_DEPTH_FG_BG,
698 2,
699 GL::UNSIGNED_INT,
700 0,
701 stride,
702 );
703
704 Ok(instance_buf)
705}
706
707fn enable_vertex_attrib_array(
708 gl: &WebGl2RenderingContext,
709 index: u32,
710 size: i32,
711 type_: u32,
712 offset: i32,
713 stride: i32,
714) {
715 enable_vertex_attrib(gl, index, size, type_, offset, stride);
716 gl.vertex_attrib_divisor(index, 1);
717}
718
719fn enable_vertex_attrib(
720 gl: &WebGl2RenderingContext,
721 index: u32,
722 size: i32,
723 type_: u32,
724 offset: i32,
725 stride: i32,
726) {
727 gl.enable_vertex_attrib_array(index);
728 if type_ == GL::FLOAT {
729 gl.vertex_attrib_pointer_with_i32(index, size, type_, false, stride, offset);
730 } else {
731 gl.vertex_attrib_i_pointer_with_i32(index, size, type_, stride, offset);
732 }
733}
734
735impl Drawable for TerminalGrid {
736 fn prepare(&self, context: &mut RenderContext) {
737 let gl = context.gl;
738
739 self.gpu.shader.use_program(gl);
740
741 gl.bind_vertex_array(Some(&self.gpu.buffers.vao));
742
743 self.atlas.bind(gl, 0);
744 self.atlas.flush(gl).unwrap(); self.gpu.ubo_vertex.bind(context.gl);
746 self.gpu.ubo_fragment.bind(context.gl);
747 gl.uniform1i(Some(&self.gpu.sampler_loc), 0);
748 }
749
750 fn draw(&self, context: &mut RenderContext) {
751 let gl = context.gl;
752 let cell_count = self.cells.len() as i32;
753
754 gl.draw_elements_instanced_with_i32(GL::TRIANGLES, 6, GL::UNSIGNED_BYTE, 0, cell_count);
755 }
756
757 fn cleanup(&self, context: &mut RenderContext) {
758 let gl = context.gl;
759 gl.bind_vertex_array(None);
760 gl.bind_texture(GL::TEXTURE_2D_ARRAY, None);
761
762 self.gpu.ubo_vertex.unbind(gl);
763 self.gpu.ubo_fragment.unbind(gl);
764 }
765}
766
767#[derive(Debug, Copy, Clone)]
779pub struct CellData<'a> {
780 symbol: &'a str,
781 style_bits: u16,
782 fg: u32,
783 bg: u32,
784}
785
786impl<'a> CellData<'a> {
787 pub fn new(symbol: &'a str, style: FontStyle, effect: GlyphEffect, fg: u32, bg: u32) -> Self {
799 Self::new_with_style_bits(symbol, style.style_mask() | effect as u16, fg, bg)
800 }
801
802 pub fn new_with_style_bits(symbol: &'a str, style_bits: u16, fg: u32, bg: u32) -> Self {
826 debug_assert!(
828 0x81FF & style_bits == 0,
829 "Invalid style bits: {style_bits:#04x}"
830 );
831 Self { symbol, style_bits, fg, bg }
832 }
833}
834
835#[repr(C, align(4))]
856struct CellStatic {
857 pub grid_xy: [u16; 2],
859}
860
861#[derive(Debug, Clone, Copy, Hash)]
889#[repr(C, align(4))]
890pub struct CellDynamic {
891 data: [u8; 8], }
904
905impl CellStatic {
906 fn create_grid(cols: i32, rows: i32) -> Vec<Self> {
907 debug_assert!(cols > 0 && cols < u16::MAX as i32, "cols: {cols}");
908 debug_assert!(rows > 0 && rows < u16::MAX as i32, "rows: {rows}");
909
910 (0..rows)
911 .flat_map(|row| (0..cols).map(move |col| (col, row)))
912 .map(|(col, row)| Self { grid_xy: [col as u16, row as u16] })
913 .collect()
914 }
915}
916
917impl CellDynamic {
918 const GLYPH_STYLE_MASK: u16 =
919 Glyph::BOLD_FLAG | Glyph::ITALIC_FLAG | Glyph::UNDERLINE_FLAG | Glyph::STRIKETHROUGH_FLAG;
920
921 #[inline]
922 pub fn new(glyph_id: u16, fg: u32, bg: u32) -> Self {
923 let mut data = [0; 8];
924
925 let glyph_id = glyph_id.to_le_bytes();
927 data[0] = glyph_id[0];
928 data[1] = glyph_id[1];
929
930 let fg = fg.to_le_bytes();
931 data[2] = fg[2]; data[3] = fg[1]; data[4] = fg[0]; let bg = bg.to_le_bytes();
936 data[5] = bg[2]; data[6] = bg[1]; data[7] = bg[0]; Self { data }
941 }
942
943 pub fn style(&mut self, style_bits: u16) {
945 let glyph_id = (self.glyph_id() & !Self::GLYPH_STYLE_MASK) | style_bits;
946 self.data[..2].copy_from_slice(&glyph_id.to_le_bytes());
947 }
948
949 pub fn flip_colors(&mut self) {
951 let fg = [self.data[2], self.data[3], self.data[4]];
953 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]; }
960
961 pub fn fg_color(&mut self, fg: u32) {
963 let fg = fg.to_le_bytes();
964 self.data[2] = fg[2]; self.data[3] = fg[1]; self.data[4] = fg[0]; }
968
969 pub fn bg_color(&mut self, bg: u32) {
971 let bg = bg.to_le_bytes();
972 self.data[5] = bg[2]; self.data[6] = bg[1]; self.data[7] = bg[0]; }
976
977 pub fn get_fg_color(&self) -> u32 {
979 ((self.data[2] as u32) << 16) | ((self.data[3] as u32) << 8) | (self.data[4] as u32)
981 }
982
983 pub fn get_bg_color(&self) -> u32 {
985 ((self.data[5] as u32) << 16) | ((self.data[6] as u32) << 8) | (self.data[7] as u32)
987 }
988
989 pub fn get_style(&self) -> u16 {
991 self.glyph_id() & Self::GLYPH_STYLE_MASK
992 }
993
994 pub fn is_emoji(&self) -> bool {
996 self.glyph_id() & Glyph::EMOJI_FLAG != 0
997 }
998
999 #[inline]
1000 fn glyph_id(&self) -> u16 {
1001 u16::from_le_bytes([self.data[0], self.data[1]])
1002 }
1003}
1004
1005#[repr(C, align(16))] struct CellVertexUbo {
1007 pub projection: [f32; 16], pub cell_size: [f32; 2], pub _padding: [f32; 2],
1010}
1011
1012#[repr(C, align(16))] struct CellFragmentUbo {
1014 pub padding_frac: [f32; 2], pub underline_pos: f32, pub underline_thickness: f32, pub strikethrough_pos: f32, pub strikethrough_thickness: f32, pub texture_lookup_mask: u32, pub _padding: f32,
1021}
1022
1023impl CellVertexUbo {
1024 pub const BINDING_POINT: u32 = 0;
1025
1026 fn new(canvas_size: (i32, i32), cell_size: (i32, i32)) -> Self {
1027 let projection =
1028 Mat4::orthographic_from_size(canvas_size.0 as f32, canvas_size.1 as f32).data;
1029 Self {
1030 projection,
1031 cell_size: [cell_size.0 as f32, cell_size.1 as f32],
1032 _padding: [0.0; 2], }
1034 }
1035}
1036
1037impl CellFragmentUbo {
1038 pub const BINDING_POINT: u32 = 1;
1039
1040 fn new(atlas: &FontAtlas) -> Self {
1041 let cell_size = atlas.cell_size();
1042 let underline = atlas.underline();
1043 let strikethrough = atlas.strikethrough();
1044
1045 Self {
1046 padding_frac: [
1047 FontAtlasData::PADDING as f32 / cell_size.0 as f32,
1048 FontAtlasData::PADDING as f32 / cell_size.1 as f32,
1049 ],
1050 underline_pos: underline.position,
1051 underline_thickness: underline.thickness,
1052 strikethrough_pos: strikethrough.position,
1053 strikethrough_thickness: strikethrough.thickness,
1054 texture_lookup_mask: atlas.base_lookup_mask(),
1055 _padding: 0.0, }
1057 }
1058}
1059
1060fn create_terminal_cell_data(cols: i32, rows: i32, fill_glyph: &[u16]) -> Vec<CellDynamic> {
1061 let glyph_len = fill_glyph.len();
1062 (0..cols * rows)
1063 .map(|i| CellDynamic::new(fill_glyph[i as usize % glyph_len], 0x00ff_ffff, 0x0000_0000))
1064 .collect()
1065}
1066
1067mod attrib {
1068 pub const POS: u32 = 0;
1069 pub const UV: u32 = 1;
1070
1071 pub const GRID_XY: u32 = 2;
1072 pub const PACKED_DEPTH_FG_BG: u32 = 3;
1073}