1use std::{cmp::min, fmt::Debug};
2
3use beamterm_data::{CellSize, FontAtlasData, FontStyle, Glyph, GlyphEffect, TerminalSize};
4use compact_str::CompactString;
5use glow::HasContext;
6
7use crate::{
8 CursorPosition,
9 error::Error,
10 gl::{
11 CellIterator, CellQuery, Drawable, GlState, RenderContext, ShaderProgram,
12 atlas::{self, FontAtlas, GlyphSlot},
13 buffer_upload_array,
14 dirty_regions::DirtyRegions,
15 selection::SelectionTracker,
16 ubo::UniformBufferObject,
17 },
18 mat4::Mat4,
19};
20
21#[derive(Debug)]
28#[must_use = "call `delete(gl)` before dropping to avoid GPU resource leaks"]
29pub struct TerminalGrid {
30 gpu: GpuResources,
32 cells: Vec<CellDynamic>,
34 terminal_size: TerminalSize,
36 canvas_size_px: (i32, i32),
38 pixel_ratio: f32,
40 atlas: FontAtlas,
42 fallback_glyph: u16,
44 selection: SelectionTracker,
46 dirty_regions: DirtyRegions,
48 bg_alpha: f32,
50}
51
52#[derive(Debug)]
59struct GpuResources {
60 shader: ShaderProgram,
62 buffers: TerminalBuffers,
64 ubo_vertex: UniformBufferObject,
66 ubo_fragment: UniformBufferObject,
68 sampler_loc: glow::UniformLocation,
70}
71
72impl GpuResources {
73 const FRAGMENT_GLSL: &'static str = include_str!("../shaders/cell.frag");
74 const VERTEX_GLSL: &'static str = include_str!("../shaders/cell.vert");
75
76 fn delete(&self, gl: &glow::Context) {
77 self.shader.delete(gl);
78 self.buffers.delete(gl);
79 self.ubo_vertex.delete(gl);
80 self.ubo_fragment.delete(gl);
81 }
82
83 fn new(
92 gl: &glow::Context,
93 cell_pos: &[CellStatic],
94 cell_data: &[CellDynamic],
95 cell_size: CellSize,
96 glsl_version: crate::GlslVersion,
97 ) -> Result<Self, Error> {
98 let vao =
100 unsafe { gl.create_vertex_array() }.map_err(Error::vertex_array_creation_failed)?;
101 unsafe { gl.bind_vertex_array(Some(vao)) };
102
103 let buffers = setup_buffers(gl, vao, cell_pos, cell_data, cell_size)?;
105
106 unsafe { gl.bind_vertex_array(None) };
108
109 let vertex_source = format!("{}{}", glsl_version.vertex_preamble(), Self::VERTEX_GLSL);
111 let fragment_source = format!(
112 "{}{}",
113 glsl_version.fragment_preamble(),
114 Self::FRAGMENT_GLSL
115 );
116 let shader = ShaderProgram::create(gl, &vertex_source, &fragment_source)?;
117 shader.use_program(gl);
118
119 let ubo_vertex = UniformBufferObject::new(gl, CellVertexUbo::BINDING_POINT)?;
120 ubo_vertex.bind_to_shader(gl, &shader, "VertUbo")?;
121 let ubo_fragment = UniformBufferObject::new(gl, CellFragmentUbo::BINDING_POINT)?;
122 ubo_fragment.bind_to_shader(gl, &shader, "FragUbo")?;
123
124 let sampler_loc = unsafe { gl.get_uniform_location(shader.program, "u_sampler") }
125 .ok_or(Error::uniform_location_failed("u_sampler"))?;
126
127 Ok(Self {
128 shader,
129 buffers,
130 ubo_vertex,
131 ubo_fragment,
132 sampler_loc,
133 })
134 }
135}
136
137#[derive(Debug)]
138struct TerminalBuffers {
139 vao: glow::VertexArray,
140 vertices: glow::Buffer,
141 instance_pos: glow::Buffer,
142 instance_cell: glow::Buffer,
143 indices: glow::Buffer,
144}
145
146impl TerminalBuffers {
147 fn delete(&self, gl: &glow::Context) {
148 unsafe {
149 gl.delete_vertex_array(self.vao);
150 gl.delete_buffer(self.vertices);
151 gl.delete_buffer(self.instance_pos);
152 gl.delete_buffer(self.instance_cell);
153 gl.delete_buffer(self.indices);
154 }
155 }
156
157 fn bind_instance_buffer(&self, gl: &glow::Context) {
160 unsafe {
161 gl.bind_vertex_array(Some(self.vao));
162 gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instance_cell));
163 }
164 }
165
166 fn unbind_instance_buffer(&self, gl: &glow::Context) {
168 #![allow(clippy::unused_self)] unsafe { gl.bind_vertex_array(None) };
170 }
171
172 #[allow(clippy::unused_self)] fn upload_instance_data<T: Copy>(&self, gl: &glow::Context, cell_data: &[T]) {
178 unsafe { buffer_upload_array(gl, glow::ARRAY_BUFFER, cell_data, glow::DYNAMIC_DRAW) };
179 }
180
181 #[allow(clippy::unused_self)] fn upload_instance_data_range<T: Copy>(
187 &self,
188 gl: &glow::Context,
189 cell_data: &[T],
190 byte_offset: usize,
191 ) {
192 unsafe {
193 let bytes =
194 std::slice::from_raw_parts(cell_data.as_ptr() as *const u8, size_of_val(cell_data));
195 gl.buffer_sub_data_u8_slice(glow::ARRAY_BUFFER, byte_offset as i32, bytes);
196 }
197 }
198
199 fn update_vertex_buffer(&self, gl: &glow::Context, cell_size: CellSize) {
201 let (w, h) = (cell_size.width as f32, cell_size.height as f32);
202
203 #[rustfmt::skip]
204 let vertices: [f32; 16] = [
205 w, 0.0, 1.0, 0.0, 0.0, h, 0.0, 1.0, w, h, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0 ];
211
212 unsafe {
213 gl.bind_vertex_array(Some(self.vao));
214 gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertices));
215 let bytes = std::slice::from_raw_parts(
216 vertices.as_ptr() as *const u8,
217 vertices.len() * size_of::<f32>(),
218 );
219 gl.buffer_sub_data_u8_slice(glow::ARRAY_BUFFER, 0, bytes);
220 gl.bind_vertex_array(None);
221 }
222 }
223}
224
225impl TerminalGrid {
226 pub fn new(
232 gl: &glow::Context,
233 mut atlas: FontAtlas,
234 screen_size: (i32, i32),
235 pixel_ratio: f32,
236 glsl_version: &crate::GlslVersion,
237 ) -> Result<Self, Error> {
238 let cell_scale = atlas.cell_scale_for_dpr(pixel_ratio);
239 let cell_size = atlas.cell_size().scale(cell_scale);
240 let cols = screen_size.0 / cell_size.width;
241 let rows = screen_size.1 / cell_size.height;
242
243 let space_glyph = atlas.space_glyph_id();
244 let cell_data = create_terminal_cell_data(cols, rows, space_glyph);
245 let cell_pos = CellStatic::create_grid(cols, rows);
246
247 let grid = Self {
248 gpu: GpuResources::new(gl, &cell_pos, &cell_data, cell_size, *glsl_version)?,
249 terminal_size: TerminalSize::new(cols as u16, rows as u16),
250 canvas_size_px: screen_size,
251 pixel_ratio,
252 cells: cell_data,
253 atlas,
254 fallback_glyph: space_glyph,
255 selection: SelectionTracker::new(),
256 dirty_regions: DirtyRegions::new((cols * rows) as usize),
257 bg_alpha: 1.0,
258 };
259
260 grid.upload_ubo_data(gl);
261
262 Ok(grid)
263 }
264
265 pub fn delete(self, gl: &glow::Context) {
271 self.gpu.delete(gl);
272 self.atlas.delete(gl);
273 }
274
275 fn effective_cell_size(&self) -> CellSize {
277 let cell_scale = self.atlas.cell_scale_for_dpr(self.pixel_ratio);
278 self.atlas.cell_size().scale(cell_scale)
279 }
280
281 pub fn set_fallback_glyph(&mut self, fallback: &str) {
283 self.fallback_glyph = self
284 .atlas
285 .resolve_glyph_slot(fallback, FontStyle::Normal as u16)
286 .map_or(' ' as u16, |slot| slot.slot_id());
287 }
288
289 pub fn replace_atlas(&mut self, gl: &glow::Context, mut atlas: FontAtlas) {
298 let glyph_mask = atlas::GLYPH_SLOT_MASK as u16;
299 let style_mask = !glyph_mask;
300
301 let space_glyph = atlas.space_glyph_id();
303
304 self.fallback_glyph = self
306 .atlas
307 .get_symbol(self.fallback_glyph & glyph_mask)
308 .and_then(|symbol| {
309 let style_bits = self.fallback_glyph & style_mask;
310 atlas.resolve_glyph_slot(symbol.as_str(), style_bits)
311 })
312 .map_or(space_glyph, |slot| slot.slot_id());
313
314 let mut skip_next = false;
316 for idx in 0..self.cells.len() {
317 if skip_next {
318 skip_next = false;
319 continue;
320 }
321
322 let old_glyph_id = self.cells[idx].glyph_id();
323 let style_bits = old_glyph_id & style_mask;
324
325 let slot = self
326 .atlas
327 .get_symbol(old_glyph_id & glyph_mask)
328 .and_then(|symbol| atlas.resolve_glyph_slot(symbol.as_str(), style_bits));
329
330 match slot {
331 Some(GlyphSlot::Normal(id)) => {
332 self.cells[idx].set_glyph_id(id);
333 },
334 Some(GlyphSlot::Wide(id)) | Some(GlyphSlot::Emoji(id)) => {
335 self.cells[idx].set_glyph_id(id);
336 if let Some(next_cell) = self.cells.get_mut(idx + 1) {
338 next_cell.set_glyph_id(id + 1);
339 skip_next = true;
340 }
341 },
342 None => {
343 self.cells[idx].set_glyph_id(self.fallback_glyph);
344 },
345 }
346 }
347
348 self.selection.clear();
350
351 let old_atlas = std::mem::replace(&mut self.atlas, atlas);
353 old_atlas.delete(gl);
354 self.dirty_regions.mark_all();
355
356 self.gpu
358 .buffers
359 .update_vertex_buffer(gl, self.effective_cell_size());
360
361 let _ = self.resize(gl, self.canvas_size_px, self.pixel_ratio);
362 }
363
364 #[must_use]
366 pub fn atlas(&self) -> &FontAtlas {
367 &self.atlas
368 }
369
370 pub fn atlas_mut(&mut self) -> &mut FontAtlas {
372 &mut self.atlas
373 }
374
375 pub fn set_bg_alpha(&mut self, gl: &glow::Context, alpha: f32) {
384 self.bg_alpha = alpha.clamp(0.0, 1.0);
385 self.upload_ubo_data(gl);
386 }
387
388 #[must_use]
390 pub fn canvas_size(&self) -> (i32, i32) {
391 self.canvas_size_px
392 }
393
394 #[must_use]
396 pub fn cell_size(&self) -> CellSize {
397 self.effective_cell_size()
398 }
399
400 #[must_use]
405 pub fn css_cell_size(&self) -> (f32, f32) {
406 let cs = self.effective_cell_size();
407 if self.pixel_ratio <= 0.0 {
408 return (cs.width as f32, cs.height as f32);
409 }
410 (
411 cs.width as f32 / self.pixel_ratio,
412 cs.height as f32 / self.pixel_ratio,
413 )
414 }
415
416 #[must_use]
418 pub fn terminal_size(&self) -> TerminalSize {
419 self.terminal_size
420 }
421
422 pub fn render(&self, gl: &glow::Context, state: &mut GlState) -> Result<(), crate::Error> {
432 let mut ctx = RenderContext { gl, state };
433 self.prepare(&mut ctx)?;
434 self.draw(&mut ctx);
435 self.cleanup(&mut ctx);
436 Ok(())
437 }
438
439 pub fn cell_data_mut(&mut self, x: u16, y: u16) -> Option<&mut CellDynamic> {
441 let cols = self.terminal_size.cols;
442 let idx = y as usize * cols as usize + x as usize;
443 self.dirty_regions.mark(idx);
444 self.cells.get_mut(idx)
445 }
446
447 #[must_use]
449 pub fn selection_tracker(&self) -> SelectionTracker {
450 self.selection.clone()
451 }
452
453 pub(super) fn get_symbols(&self, selection: CellIterator) -> CompactString {
455 let mut text = CompactString::new("");
456
457 for (idx, require_newline_after) in selection {
458 let cell_symbol = self.get_cell_symbol(idx);
459 if cell_symbol.is_some() {
460 text.push_str(&cell_symbol.unwrap_or_default());
461 }
462
463 if require_newline_after {
464 text.push('\n'); }
466 }
467
468 text
469 }
470
471 pub(crate) fn get_ascii_char_at(&self, cursor: CursorPosition) -> Option<char> {
476 let idx = cursor.row as usize * self.terminal_size.cols as usize + cursor.col as usize;
477 if idx < self.cells.len() {
478 let glyph_id = self.cells[idx].glyph_id();
479 self.atlas.get_ascii_char(glyph_id)
480 } else {
481 None
482 }
483 }
484
485 #[doc(hidden)]
487 #[must_use]
488 pub fn hash_cells(&self, selection: CellQuery) -> u64 {
489 use std::hash::{Hash, Hasher};
490
491 use rustc_hash::FxHasher;
492
493 let mut hasher = FxHasher::default();
494 for (idx, _) in self.cell_iter(selection) {
495 self.cells[idx].hash(&mut hasher);
496 }
497
498 hasher.finish()
499 }
500
501 fn get_cell_symbol(&self, idx: usize) -> Option<CompactString> {
502 if idx < self.cells.len() {
503 let glyph_id = self.cells[idx].glyph_id();
504 let cell_symbol = self.atlas.get_symbol(glyph_id);
505 if cell_symbol.is_some() {
506 return cell_symbol;
507 }
508 }
509
510 self.fallback_symbol()
511 }
512
513 fn upload_ubo_data(&self, gl: &glow::Context) {
515 let vertex_ubo = CellVertexUbo::new(self.canvas_size_px, self.effective_cell_size());
516 self.gpu.ubo_vertex.upload_data(gl, &vertex_ubo);
517
518 let fragment_ubo = CellFragmentUbo::new(&self.atlas, self.bg_alpha);
519 self.gpu
520 .ubo_fragment
521 .upload_data(gl, &fragment_ubo);
522 }
523
524 #[must_use]
526 pub fn cell_count(&self) -> usize {
527 self.cells.len()
528 }
529
530 pub fn update_cells<'a>(
536 &mut self,
537 cells: impl Iterator<Item = CellData<'a>>,
538 ) -> Result<(), Error> {
539 let fallback_glyph = GlyphSlot::Normal(self.fallback_glyph);
540
541 let atlas = &mut self.atlas;
543 let cell_buf = &mut self.cells;
544
545 let mut pending_cell: Option<CellDynamic> = None;
547 cell_buf
548 .iter_mut()
549 .zip(cells)
550 .for_each(|(cell, data)| {
551 let glyph = atlas
552 .resolve_glyph_slot(data.symbol, data.style_bits)
553 .unwrap_or(fallback_glyph);
554
555 *cell = if let Some(second_cell) = pending_cell.take() {
556 second_cell
557 } else {
558 match glyph {
559 GlyphSlot::Normal(id) => CellDynamic::new(id, data.fg, data.bg),
560
561 GlyphSlot::Wide(id) | GlyphSlot::Emoji(id) => {
562 pending_cell = Some(CellDynamic::new(id + 1, data.fg, data.bg));
564 CellDynamic::new(id, data.fg, data.bg)
565 },
566 }
567 }
568 });
569
570 self.dirty_regions.mark_all();
571 Ok(())
572 }
573
574 pub fn update_cells_by_position<'a>(
580 &mut self,
581 cells: impl Iterator<Item = (u16, u16, CellData<'a>)>,
582 ) -> Result<(), Error> {
583 let cols = self.terminal_size.cols as usize;
584 let cells_by_index = cells.map(|(x, y, data)| (y as usize * cols + x as usize, data));
585
586 self.update_cells_by_index(cells_by_index)
587 }
588
589 pub fn update_cells_by_index<'a>(
595 &mut self,
596 cells: impl Iterator<Item = (usize, CellData<'a>)>,
597 ) -> Result<(), Error> {
598 let fallback_glyph = GlyphSlot::Normal(self.fallback_glyph);
599
600 let atlas = &mut self.atlas;
601 let cell_buf = &mut self.cells;
602 let dirty_regions = &mut self.dirty_regions;
603
604 let cell_count = cell_buf.len();
605
606 let mut skip_idx = None;
611
612 cells
613 .filter(|(idx, _)| *idx < cell_count)
614 .for_each(|(idx, cell)| {
615 if skip_idx.take() == Some(idx) {
616 return;
618 }
619
620 let glyph = atlas
621 .resolve_glyph_slot(cell.symbol, cell.style_bits)
622 .unwrap_or(fallback_glyph);
623
624 match glyph {
625 GlyphSlot::Normal(id) => {
626 cell_buf[idx] = CellDynamic::new(id, cell.fg, cell.bg);
627 dirty_regions.mark(idx);
628 },
629
630 GlyphSlot::Wide(id) | GlyphSlot::Emoji(id) => {
631 cell_buf[idx] = CellDynamic::new(id, cell.fg, cell.bg);
633 dirty_regions.mark(idx);
634
635 if let Some(c) = cell_buf.get_mut(idx + 1) {
637 *c = CellDynamic::new(id + 1, cell.fg, cell.bg);
638 dirty_regions.mark(idx + 1);
639 skip_idx = Some(idx + 1);
640 }
641 },
642 }
643 });
644
645 Ok(())
646 }
647
648 pub fn update_cell(&mut self, x: u16, y: u16, cell_data: CellData) -> Result<(), Error> {
654 let cols = self.terminal_size.cols;
655 let idx = y as usize * cols as usize + x as usize;
656 self.update_cell_by_index(idx, cell_data)
657 }
658
659 pub fn update_cell_by_index(&mut self, idx: usize, cell_data: CellData) -> Result<(), Error> {
665 self.update_cells_by_index(std::iter::once((idx, cell_data)))
666 }
667
668 pub fn flush_cells(&mut self, gl: &glow::Context) -> Result<(), Error> {
677 self.atlas.bind(gl);
679 self.atlas.flush(gl)?;
680
681 if self.dirty_regions.is_clean() {
682 return Ok(()); }
684
685 self.clear_stale_selection();
688
689 self.flip_selected_cell_colors();
693
694 self.gpu.buffers.bind_instance_buffer(gl);
695 if self.dirty_regions.is_all_active_dirty() {
696 self.gpu
698 .buffers
699 .upload_instance_data(gl, &self.cells);
700
701 self.dirty_regions.clear();
702 } else {
703 for (start, end) in self.dirty_regions.drain() {
705 self.gpu.buffers.upload_instance_data_range(
706 gl,
707 &self.cells[start..end],
708 start * CellDynamic::SIZE,
709 );
710 }
711 }
712 self.gpu.buffers.unbind_instance_buffer(gl);
713
714 self.flip_selected_cell_colors();
717
718 Ok(())
719 }
720
721 fn flip_selected_cell_colors(&mut self) {
722 if let Some(iter) = self.selected_cells_iter() {
723 iter.for_each(|(idx, _)| {
724 self.cells[idx].flip_colors();
725 self.dirty_regions.mark(idx);
726 });
727 }
728 }
729
730 fn selected_cells_iter(&self) -> Option<CellIterator> {
731 self.selection
732 .get_query()
733 .map(|query| self.cell_iter(query))
734 }
735
736 pub fn resize(
742 &mut self,
743 gl: &glow::Context,
744 canvas_size: (i32, i32),
745 pixel_ratio: f32,
746 ) -> Result<(), Error> {
747 self.canvas_size_px = canvas_size;
748 self.pixel_ratio = pixel_ratio;
749
750 let cell_size = self.effective_cell_size();
751
752 self.gpu
754 .buffers
755 .update_vertex_buffer(gl, cell_size);
756
757 self.upload_ubo_data(gl);
759
760 let cols = (canvas_size.0 / cell_size.width).max(1);
761 let rows = (canvas_size.1 / cell_size.height).max(1);
762 if self.terminal_size == TerminalSize::new(cols as u16, rows as u16) {
763 return Ok(()); }
765
766 unsafe {
768 gl.bind_vertex_array(Some(self.gpu.buffers.vao));
769
770 gl.delete_buffer(self.gpu.buffers.instance_cell);
772 gl.delete_buffer(self.gpu.buffers.instance_pos);
773 }
774
775 let current_size = (
777 self.terminal_size.cols as i32,
778 self.terminal_size.rows as i32,
779 );
780 let cell_data = self.resize_cell_grid(current_size, (cols, rows));
781 self.cells = cell_data;
782
783 let cell_pos = CellStatic::create_grid(cols, rows);
784
785 self.gpu.buffers.instance_cell = create_dynamic_instance_buffer(gl, &self.cells)?;
787 self.gpu.buffers.instance_pos = create_static_instance_buffer(gl, &cell_pos)?;
788
789 unsafe { gl.bind_vertex_array(None) };
791
792 self.terminal_size = TerminalSize::new(cols as u16, rows as u16);
793 self.dirty_regions = DirtyRegions::new(self.cells.len());
794
795 Ok(())
796 }
797
798 pub fn recreate_resources(
807 &mut self,
808 gl: &glow::Context,
809 glsl_version: &crate::GlslVersion,
810 ) -> Result<(), Error> {
811 let cell_size = self.effective_cell_size();
812 let (cols, rows) = (
813 self.terminal_size.cols as i32,
814 self.terminal_size.rows as i32,
815 );
816 let cell_pos = CellStatic::create_grid(cols, rows);
817
818 self.gpu = GpuResources::new(gl, &cell_pos, &self.cells, cell_size, *glsl_version)?;
820
821 self.upload_ubo_data(gl);
823
824 self.dirty_regions.mark_all();
826
827 Ok(())
828 }
829
830 pub fn recreate_atlas_texture(&mut self, gl: &glow::Context) -> Result<(), Error> {
835 self.atlas.recreate_texture(gl)
836 }
837
838 pub fn base_glyph_id(&mut self, symbol: &str) -> Option<u16> {
840 self.atlas.get_base_glyph_id(symbol)
841 }
842
843 fn fallback_symbol(&self) -> Option<CompactString> {
844 self.atlas.get_symbol(self.fallback_glyph)
845 }
846
847 fn clear_stale_selection(&self) {
848 if let Some(query) = self.selection_tracker().get_query()
849 && let Some(hash) = query.content_hash
850 && hash != self.hash_cells(query)
851 {
852 self.selection.clear();
853 }
854 }
855
856 fn resize_cell_grid(&mut self, old_size: (i32, i32), new_size: (i32, i32)) -> Vec<CellDynamic> {
857 let empty_cell = CellDynamic::new(self.atlas.space_glyph_id(), 0xFFFFFF, 0x000000);
858
859 let new_len = new_size.0 * new_size.1;
860 let mut new_cells = Vec::with_capacity(new_len as usize);
861 for _ in 0..new_len {
862 new_cells.push(empty_cell);
863 }
864
865 let cells = &self.cells;
866 for y in 0..min(old_size.1, new_size.1) {
867 for x in 0..min(old_size.0, new_size.0) {
868 let new_idx = (y * new_size.0 + x) as usize;
869 let old_idx = (y * old_size.0 + x) as usize;
870 new_cells[new_idx] = cells[old_idx];
871 }
872 }
873
874 new_cells
875 }
876}
877
878fn setup_buffers(
879 gl: &glow::Context,
880 vao: glow::VertexArray,
881 cell_pos: &[CellStatic],
882 cell_data: &[CellDynamic],
883 cell_size: CellSize,
884) -> Result<TerminalBuffers, Error> {
885 let (w, h) = (cell_size.width as f32, cell_size.height as f32);
886
887 #[rustfmt::skip]
888 let vertices = [
889 w, 0.0, 1.0, 0.0, 0.0, h, 0.0, 1.0, w, h, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0 ];
895 let indices = [0, 1, 2, 0, 3, 1];
896
897 Ok(TerminalBuffers {
898 vao,
899 vertices: create_buffer_f32(gl, glow::ARRAY_BUFFER, &vertices, glow::STATIC_DRAW)?,
900 instance_pos: create_static_instance_buffer(gl, cell_pos)?,
901 instance_cell: create_dynamic_instance_buffer(gl, cell_data)?,
902 indices: create_buffer_u8(gl, glow::ELEMENT_ARRAY_BUFFER, &indices, glow::STATIC_DRAW)?,
903 })
904}
905
906fn create_buffer_u8(
907 gl: &glow::Context,
908 target: u32,
909 data: &[u8],
910 usage: u32,
911) -> Result<glow::Buffer, Error> {
912 let buffer =
913 unsafe { gl.create_buffer() }.map_err(|e| Error::buffer_creation_failed("vbo-u8", e))?;
914 unsafe {
915 gl.bind_buffer(target, Some(buffer));
916 gl.buffer_data_u8_slice(target, data, usage);
917 }
918 Ok(buffer)
919}
920
921fn create_buffer_f32(
922 gl: &glow::Context,
923 target: u32,
924 data: &[f32],
925 usage: u32,
926) -> Result<glow::Buffer, Error> {
927 let buffer =
928 unsafe { gl.create_buffer() }.map_err(|e| Error::buffer_creation_failed("vbo-f32", e))?;
929
930 unsafe {
931 gl.bind_buffer(target, Some(buffer));
932 let bytes =
933 std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data));
934 gl.buffer_data_u8_slice(target, bytes, usage);
935 }
936
937 const STRIDE: i32 = (2 + 2) * 4; enable_vertex_attrib(gl, attrib::POS, 2, glow::FLOAT, 0, STRIDE);
940 enable_vertex_attrib(gl, attrib::UV, 2, glow::FLOAT, 8, STRIDE);
941
942 Ok(buffer)
943}
944
945fn create_static_instance_buffer(
946 gl: &glow::Context,
947 instance_data: &[CellStatic],
948) -> Result<glow::Buffer, Error> {
949 let buffer = unsafe { gl.create_buffer() }
950 .map_err(|e| Error::buffer_creation_failed("static-instance-buffer", e))?;
951
952 unsafe {
953 gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer));
954 buffer_upload_array(gl, glow::ARRAY_BUFFER, instance_data, glow::STATIC_DRAW);
955 }
956
957 let stride = size_of::<CellStatic>() as i32;
958 enable_vertex_attrib_array(gl, attrib::GRID_XY, 2, glow::UNSIGNED_SHORT, 0, stride);
959
960 Ok(buffer)
961}
962
963fn create_dynamic_instance_buffer(
964 gl: &glow::Context,
965 instance_data: &[CellDynamic],
966) -> Result<glow::Buffer, Error> {
967 let buffer = unsafe { gl.create_buffer() }
968 .map_err(|e| Error::buffer_creation_failed("dynamic-instance-buffer", e))?;
969
970 unsafe {
971 gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer));
972 buffer_upload_array(gl, glow::ARRAY_BUFFER, instance_data, glow::DYNAMIC_DRAW);
973 }
974
975 let stride = size_of::<CellDynamic>() as i32;
976
977 enable_vertex_attrib_array(
979 gl,
980 attrib::PACKED_DEPTH_FG_BG,
981 2,
982 glow::UNSIGNED_INT,
983 0,
984 stride,
985 );
986
987 Ok(buffer)
988}
989
990fn enable_vertex_attrib_array(
991 gl: &glow::Context,
992 index: u32,
993 size: i32,
994 type_: u32,
995 offset: i32,
996 stride: i32,
997) {
998 enable_vertex_attrib(gl, index, size, type_, offset, stride);
999 unsafe { gl.vertex_attrib_divisor(index, 1) };
1000}
1001
1002fn enable_vertex_attrib(
1003 gl: &glow::Context,
1004 index: u32,
1005 size: i32,
1006 type_: u32,
1007 offset: i32,
1008 stride: i32,
1009) {
1010 unsafe {
1011 gl.enable_vertex_attrib_array(index);
1012 if type_ == glow::FLOAT {
1013 gl.vertex_attrib_pointer_f32(index, size, type_, false, stride, offset);
1014 } else {
1015 gl.vertex_attrib_pointer_i32(index, size, type_, stride, offset);
1016 }
1017 }
1018}
1019
1020impl Drawable for TerminalGrid {
1021 fn prepare(&self, context: &mut RenderContext) -> Result<(), crate::Error> {
1022 let gl = context.gl;
1023
1024 self.gpu.shader.use_program(gl);
1025
1026 unsafe { gl.bind_vertex_array(Some(self.gpu.buffers.vao)) };
1027
1028 context.state.active_texture(gl, glow::TEXTURE0);
1029 self.atlas.bind(gl);
1030 self.gpu.ubo_vertex.bind(context.gl);
1031 self.gpu.ubo_fragment.bind(context.gl);
1032 unsafe { gl.uniform_1_i32(Some(&self.gpu.sampler_loc), 0) };
1033
1034 Ok(())
1035 }
1036
1037 fn draw(&self, context: &mut RenderContext) {
1038 let gl = context.gl;
1039 let cell_count = self.cells.len() as i32;
1040
1041 unsafe {
1042 gl.draw_elements_instanced(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0, cell_count);
1043 }
1044 }
1045
1046 fn cleanup(&self, context: &mut RenderContext) {
1047 let gl = context.gl;
1048 unsafe {
1049 gl.bind_vertex_array(None);
1050 gl.bind_texture(glow::TEXTURE_2D_ARRAY, None);
1051 gl.use_program(None);
1052 }
1053
1054 self.gpu.ubo_vertex.unbind(gl);
1055 self.gpu.ubo_fragment.unbind(gl);
1056 }
1057}
1058
1059#[derive(Debug, Copy, Clone)]
1071pub struct CellData<'a> {
1072 symbol: &'a str,
1073 style_bits: u16,
1074 fg: u32,
1075 bg: u32,
1076}
1077
1078impl<'a> CellData<'a> {
1079 #[must_use]
1081 pub fn new(symbol: &'a str, style: FontStyle, effect: GlyphEffect, fg: u32, bg: u32) -> Self {
1082 let style_bits = style.style_mask() | effect as u16;
1083
1084 debug_assert!(
1086 0x81FF & style_bits == 0,
1087 "Invalid style bits: {style_bits:#04x}"
1088 );
1089
1090 Self::new_with_style_bits(symbol, style_bits, fg, bg)
1091 }
1092
1093 #[must_use]
1095 pub const fn new_with_style_bits(symbol: &'a str, style_bits: u16, fg: u32, bg: u32) -> Self {
1096 Self { symbol, style_bits, fg, bg }
1097 }
1098}
1099
1100#[derive(Clone, Copy)]
1102#[repr(C, align(4))]
1103struct CellStatic {
1104 pub grid_xy: [u16; 2],
1106}
1107
1108#[derive(Debug, Clone, Copy, Hash)]
1119#[repr(C, align(4))]
1120pub struct CellDynamic {
1121 data: [u8; 8], }
1134
1135impl CellStatic {
1136 fn create_grid(cols: i32, rows: i32) -> Vec<Self> {
1137 debug_assert!(cols > 0 && cols < u16::MAX as i32, "cols: {cols}");
1138 debug_assert!(rows > 0 && rows < u16::MAX as i32, "rows: {rows}");
1139
1140 (0..rows)
1141 .flat_map(|row| (0..cols).map(move |col| (col, row)))
1142 .map(|(col, row)| Self { grid_xy: [col as u16, row as u16] })
1143 .collect()
1144 }
1145}
1146
1147impl CellDynamic {
1148 const SIZE: usize = size_of::<Self>();
1149
1150 const GLYPH_STYLE_MASK: u16 =
1151 Glyph::BOLD_FLAG | Glyph::ITALIC_FLAG | Glyph::UNDERLINE_FLAG | Glyph::STRIKETHROUGH_FLAG;
1152
1153 #[inline]
1155 #[must_use]
1156 pub fn new(glyph_id: u16, fg: u32, bg: u32) -> Self {
1157 let mut data = [0; 8];
1158
1159 let glyph_id = glyph_id.to_le_bytes();
1161 data[0] = glyph_id[0];
1162 data[1] = glyph_id[1];
1163
1164 let fg = fg.to_le_bytes();
1165 data[2] = fg[2]; data[3] = fg[1]; data[4] = fg[0]; let bg = bg.to_le_bytes();
1170 data[5] = bg[2]; data[6] = bg[1]; data[7] = bg[0]; Self { data }
1175 }
1176
1177 pub fn style(&mut self, style_bits: u16) {
1179 let glyph_id = (self.glyph_id() & !Self::GLYPH_STYLE_MASK) | style_bits;
1180 self.data[..2].copy_from_slice(&glyph_id.to_le_bytes());
1181 }
1182
1183 pub fn flip_colors(&mut self) {
1185 let fg = [self.data[2], self.data[3], self.data[4]];
1187 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]; }
1194
1195 pub fn fg_color(&mut self, fg: u32) {
1197 let fg = fg.to_le_bytes();
1198 self.data[2] = fg[2]; self.data[3] = fg[1]; self.data[4] = fg[0]; }
1202
1203 pub fn bg_color(&mut self, bg: u32) {
1205 let bg = bg.to_le_bytes();
1206 self.data[5] = bg[2]; self.data[6] = bg[1]; self.data[7] = bg[0]; }
1210
1211 #[must_use]
1213 pub fn get_fg_color(&self) -> u32 {
1214 ((self.data[2] as u32) << 16) | ((self.data[3] as u32) << 8) | (self.data[4] as u32)
1216 }
1217
1218 #[must_use]
1220 pub fn get_bg_color(&self) -> u32 {
1221 ((self.data[5] as u32) << 16) | ((self.data[6] as u32) << 8) | (self.data[7] as u32)
1223 }
1224
1225 #[must_use]
1227 pub fn get_style(&self) -> u16 {
1228 self.glyph_id() & Self::GLYPH_STYLE_MASK
1229 }
1230
1231 #[inline]
1232 fn glyph_id(self) -> u16 {
1233 u16::from_le_bytes([self.data[0], self.data[1]])
1234 }
1235
1236 fn set_glyph_id(&mut self, glyph_id: u16) {
1237 let bytes = glyph_id.to_le_bytes();
1238 self.data[0] = bytes[0];
1239 self.data[1] = bytes[1];
1240 }
1241}
1242
1243#[derive(Clone, Copy)]
1244#[repr(C, align(16))] struct CellVertexUbo {
1246 pub projection: [f32; 16], pub cell_size: [f32; 2], pub _padding: [f32; 2],
1249}
1250
1251#[derive(Clone, Copy)]
1252#[repr(C, align(16))] struct CellFragmentUbo {
1254 pub padding_frac: [f32; 2], pub underline_pos: f32, pub underline_thickness: f32, pub strikethrough_pos: f32, pub strikethrough_thickness: f32, pub emoji_bit: u32, pub bg_alpha: f32, }
1262
1263impl CellVertexUbo {
1264 pub const BINDING_POINT: u32 = 0;
1265
1266 fn new(canvas_size: (i32, i32), cell_size: CellSize) -> Self {
1267 let projection =
1268 Mat4::orthographic_from_size(canvas_size.0 as f32, canvas_size.1 as f32).data;
1269 Self {
1270 projection,
1271 cell_size: [cell_size.width as f32, cell_size.height as f32],
1272 _padding: [0.0; 2], }
1274 }
1275}
1276
1277impl CellFragmentUbo {
1278 pub const BINDING_POINT: u32 = 1;
1279
1280 fn new(atlas: &FontAtlas, bg_alpha: f32) -> Self {
1281 let tcs = atlas.texture_cell_size();
1283 let underline = atlas.underline();
1284 let strikethrough = atlas.strikethrough();
1285
1286 Self {
1287 padding_frac: [
1288 FontAtlasData::PADDING as f32 / tcs.width as f32,
1289 FontAtlasData::PADDING as f32 / tcs.height as f32,
1290 ],
1291 underline_pos: underline.position(),
1292 underline_thickness: underline.thickness(),
1293 strikethrough_pos: strikethrough.position(),
1294 strikethrough_thickness: strikethrough.thickness(),
1295 emoji_bit: atlas.emoji_bit(),
1296 bg_alpha,
1297 }
1298 }
1299}
1300
1301fn create_terminal_cell_data(cols: i32, rows: i32, fill_glyph: u16) -> Vec<CellDynamic> {
1302 (0..cols * rows)
1303 .map(|_i| CellDynamic::new(fill_glyph, 0x00ff_ffff, 0x0000_0000))
1304 .collect()
1305}
1306
1307mod attrib {
1308 pub const POS: u32 = 0;
1309 pub const UV: u32 = 1;
1310
1311 pub const GRID_XY: u32 = 2;
1312 pub const PACKED_DEPTH_FG_BG: u32 = 3;
1313}