1use std::{cmp::min, fmt::Debug};
2
3use beamterm_data::{FontAtlasData, FontStyle, Glyph, GlyphEffect};
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::{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: (u16, u16),
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: (i32, i32),
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 unsafe { gl.bind_vertex_array(None) };
169 }
170
171 fn upload_instance_data<T: Copy>(&self, gl: &glow::Context, cell_data: &[T]) {
176 unsafe { buffer_upload_array(gl, glow::ARRAY_BUFFER, cell_data, glow::DYNAMIC_DRAW) };
177 }
178
179 fn upload_instance_data_range<T: Copy>(
184 &self,
185 gl: &glow::Context,
186 cell_data: &[T],
187 byte_offset: usize,
188 ) {
189 unsafe {
190 let bytes =
191 std::slice::from_raw_parts(cell_data.as_ptr() as *const u8, size_of_val(cell_data));
192 gl.buffer_sub_data_u8_slice(glow::ARRAY_BUFFER, byte_offset as i32, bytes);
193 }
194 }
195
196 fn update_vertex_buffer(&self, gl: &glow::Context, cell_size: (i32, i32)) {
198 let (w, h) = (cell_size.0 as f32, cell_size.1 as f32);
199
200 #[rustfmt::skip]
201 let vertices: [f32; 16] = [
202 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 ];
208
209 unsafe {
210 gl.bind_vertex_array(Some(self.vao));
211 gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertices));
212 let bytes = std::slice::from_raw_parts(
213 vertices.as_ptr() as *const u8,
214 vertices.len() * size_of::<f32>(),
215 );
216 gl.buffer_sub_data_u8_slice(glow::ARRAY_BUFFER, 0, bytes);
217 gl.bind_vertex_array(None);
218 }
219 }
220}
221
222impl TerminalGrid {
223 pub fn new(
224 gl: &glow::Context,
225 atlas: FontAtlas,
226 screen_size: (i32, i32),
227 pixel_ratio: f32,
228 glsl_version: &crate::GlslVersion,
229 ) -> Result<Self, Error> {
230 let cell_scale = atlas.cell_scale_for_dpr(pixel_ratio);
231 let base_cell_size = atlas.cell_size();
232 let cell_size = (
233 (base_cell_size.0 as f32 * cell_scale).round() as i32,
234 (base_cell_size.1 as f32 * cell_scale).round() as i32,
235 );
236 let (cols, rows) = (screen_size.0 / cell_size.0, screen_size.1 / cell_size.1);
237
238 let space_glyph = atlas.space_glyph_id();
239 let cell_data = create_terminal_cell_data(cols, rows, space_glyph);
240 let cell_pos = CellStatic::create_grid(cols, rows);
241
242 let grid = Self {
243 gpu: GpuResources::new(gl, &cell_pos, &cell_data, cell_size, glsl_version)?,
244 terminal_size: (cols as u16, rows as u16),
245 canvas_size_px: screen_size,
246 pixel_ratio,
247 cells: cell_data,
248 atlas,
249 fallback_glyph: space_glyph,
250 selection: SelectionTracker::new(),
251 dirty_regions: DirtyRegions::new((cols * rows) as usize),
252 bg_alpha: 1.0,
253 };
254
255 grid.upload_ubo_data(gl);
256
257 Ok(grid)
258 }
259
260 pub fn delete(self, gl: &glow::Context) {
266 self.gpu.delete(gl);
267 self.atlas.delete(gl);
268 }
269
270 fn effective_cell_size(&self) -> (i32, i32) {
272 let cell_scale = self.atlas.cell_scale_for_dpr(self.pixel_ratio);
273 let base = self.atlas.cell_size();
274 (
275 (base.0 as f32 * cell_scale).round() as i32,
276 (base.1 as f32 * cell_scale).round() as i32,
277 )
278 }
279
280 pub fn set_fallback_glyph(&mut self, fallback: &str) {
282 self.fallback_glyph = self
283 .atlas
284 .get_glyph_id(fallback, FontStyle::Normal as u16)
285 .unwrap_or(' ' as u16);
286 }
287
288 pub fn replace_atlas(&mut self, gl: &glow::Context, atlas: FontAtlas) {
297 let glyph_mask = self.atlas.base_lookup_mask() as u16;
298 let style_mask = !glyph_mask;
299
300 self.fallback_glyph = self
302 .atlas
303 .get_symbol(self.fallback_glyph & glyph_mask)
304 .and_then(|symbol| {
305 let style_bits = self.fallback_glyph & style_mask;
306 atlas.resolve_glyph_slot(symbol.as_str(), style_bits)
307 })
308 .map(|slot| slot.slot_id())
309 .unwrap_or(atlas.space_glyph_id());
310
311 let mut skip_next = false;
313 for idx in 0..self.cells.len() {
314 if skip_next {
315 skip_next = false;
316 continue;
317 }
318
319 let old_glyph_id = self.cells[idx].glyph_id();
320 let style_bits = old_glyph_id & style_mask;
321
322 let slot = self
323 .atlas
324 .get_symbol(old_glyph_id & glyph_mask)
325 .and_then(|symbol| atlas.resolve_glyph_slot(symbol.as_str(), style_bits));
326
327 match slot {
328 Some(GlyphSlot::Normal(id)) => {
329 self.cells[idx].set_glyph_id(id);
330 },
331 Some(GlyphSlot::Wide(id)) | Some(GlyphSlot::Emoji(id)) => {
332 self.cells[idx].set_glyph_id(id);
333 if let Some(next_cell) = self.cells.get_mut(idx + 1) {
335 next_cell.set_glyph_id(id + 1);
336 skip_next = true;
337 }
338 },
339 None => {
340 self.cells[idx].set_glyph_id(self.fallback_glyph);
341 },
342 }
343 }
344
345 self.selection.clear();
347
348 let old_atlas = std::mem::replace(&mut self.atlas, atlas);
350 old_atlas.delete(gl);
351 self.dirty_regions.mark_all();
352
353 self.gpu
355 .buffers
356 .update_vertex_buffer(gl, self.effective_cell_size());
357
358 let _ = self.resize(gl, self.canvas_size_px, self.pixel_ratio);
359 }
360
361 pub fn atlas(&self) -> &FontAtlas {
363 &self.atlas
364 }
365
366 pub fn atlas_mut(&mut self) -> &mut FontAtlas {
368 &mut self.atlas
369 }
370
371 pub fn set_bg_alpha(&mut self, gl: &glow::Context, alpha: f32) {
380 self.bg_alpha = alpha.clamp(0.0, 1.0);
381 self.upload_ubo_data(gl);
382 }
383
384 pub fn canvas_size(&self) -> (i32, i32) {
386 self.canvas_size_px
387 }
388
389 pub fn cell_size(&self) -> (i32, i32) {
391 self.effective_cell_size()
392 }
393
394 pub fn css_cell_size(&self) -> (f32, f32) {
399 let (w, h) = self.effective_cell_size();
400 if self.pixel_ratio <= 0.0 {
401 return (w as f32, h as f32);
402 }
403 (w as f32 / self.pixel_ratio, h as f32 / self.pixel_ratio)
404 }
405
406 pub fn terminal_size(&self) -> (u16, u16) {
408 self.terminal_size
409 }
410
411 pub fn render(&self, gl: &glow::Context, state: &mut GlState) -> Result<(), crate::Error> {
418 let mut ctx = RenderContext { gl, state };
419 self.prepare(&mut ctx)?;
420 self.draw(&mut ctx);
421 self.cleanup(&mut ctx);
422 Ok(())
423 }
424
425 pub fn cell_data_mut(&mut self, x: u16, y: u16) -> Option<&mut CellDynamic> {
427 let (cols, _) = self.terminal_size;
428 let idx = y as usize * cols as usize + x as usize;
429 self.dirty_regions.mark(idx);
430 self.cells.get_mut(idx)
431 }
432
433 pub fn selection_tracker(&self) -> SelectionTracker {
435 self.selection.clone()
436 }
437
438 pub(super) fn get_symbols(&self, selection: CellIterator) -> CompactString {
440 let mut text = CompactString::new("");
441
442 for (idx, require_newline_after) in selection {
443 let cell_symbol = self.get_cell_symbol(idx);
444 if cell_symbol.is_some() {
445 text.push_str(&cell_symbol.unwrap_or_default());
446 }
447
448 if require_newline_after {
449 text.push('\n'); }
451 }
452
453 text
454 }
455
456 pub(crate) fn get_ascii_char_at(&self, cursor: CursorPosition) -> Option<char> {
461 let idx = cursor.row as usize * self.terminal_size.0 as usize + cursor.col as usize;
462 if idx < self.cells.len() {
463 let glyph_id = self.cells[idx].glyph_id();
464 self.atlas.get_ascii_char(glyph_id)
465 } else {
466 None
467 }
468 }
469
470 #[doc(hidden)]
471 pub fn hash_cells(&self, selection: CellQuery) -> u64 {
472 use std::hash::{Hash, Hasher};
473
474 use rustc_hash::FxHasher;
475
476 let mut hasher = FxHasher::default();
477 for (idx, _) in self.cell_iter(selection) {
478 self.cells[idx].hash(&mut hasher);
479 }
480
481 hasher.finish()
482 }
483
484 fn get_cell_symbol(&self, idx: usize) -> Option<CompactString> {
485 if idx < self.cells.len() {
486 let glyph_id = self.cells[idx].glyph_id();
487 let cell_symbol = self.atlas.get_symbol(glyph_id);
488 if cell_symbol.is_some() {
489 return cell_symbol;
490 }
491 }
492
493 self.fallback_symbol()
494 }
495
496 fn upload_ubo_data(&self, gl: &glow::Context) {
498 let vertex_ubo = CellVertexUbo::new(self.canvas_size_px, self.effective_cell_size());
499 self.gpu.ubo_vertex.upload_data(gl, &vertex_ubo);
500
501 let fragment_ubo = CellFragmentUbo::new(&self.atlas, self.bg_alpha);
502 self.gpu
503 .ubo_fragment
504 .upload_data(gl, &fragment_ubo);
505 }
506
507 pub fn cell_count(&self) -> usize {
509 self.cells.len()
510 }
511
512 pub fn update_cells<'a>(
514 &mut self,
515 cells: impl Iterator<Item = CellData<'a>>,
516 ) -> Result<(), Error> {
517 let atlas = &self.atlas;
519
520 let fallback_glyph = GlyphSlot::Normal(self.fallback_glyph);
521
522 let mut pending_cell: Option<CellDynamic> = None;
524 self.cells
525 .iter_mut()
526 .zip(cells)
527 .for_each(|(cell, data)| {
528 let glyph = atlas
529 .resolve_glyph_slot(data.symbol, data.style_bits)
530 .unwrap_or(fallback_glyph);
531
532 *cell = if let Some(second_cell) = pending_cell.take() {
533 second_cell
534 } else {
535 match glyph {
536 GlyphSlot::Normal(id) => CellDynamic::new(id, data.fg, data.bg),
537
538 GlyphSlot::Wide(id) | GlyphSlot::Emoji(id) => {
539 pending_cell = Some(CellDynamic::new(id + 1, data.fg, data.bg));
541 CellDynamic::new(id, data.fg, data.bg)
542 },
543 }
544 }
545 });
546
547 self.dirty_regions.mark_all();
548 Ok(())
549 }
550
551 pub fn update_cells_by_position<'a>(
552 &mut self,
553 cells: impl Iterator<Item = (u16, u16, CellData<'a>)>,
554 ) -> Result<(), Error> {
555 let cols = self.terminal_size.0 as usize;
556 let cells_by_index = cells.map(|(x, y, data)| (y as usize * cols + x as usize, data));
557
558 self.update_cells_by_index(cells_by_index)
559 }
560
561 pub fn update_cells_by_index<'a>(
562 &mut self,
563 cells: impl Iterator<Item = (usize, CellData<'a>)>,
564 ) -> Result<(), Error> {
565 let atlas = &self.atlas;
567
568 let cell_count = self.cells.len();
569 let fallback_glyph = GlyphSlot::Normal(self.fallback_glyph);
570
571 let mut skip_idx = None;
576
577 cells
578 .filter(|(idx, _)| *idx < cell_count)
579 .for_each(|(idx, cell)| {
580 if skip_idx.take() == Some(idx) {
581 return;
583 }
584
585 let glyph = atlas
586 .resolve_glyph_slot(cell.symbol, cell.style_bits)
587 .unwrap_or(fallback_glyph);
588
589 match glyph {
590 GlyphSlot::Normal(id) => {
591 self.cells[idx] = CellDynamic::new(id, cell.fg, cell.bg);
592 self.dirty_regions.mark(idx);
593 },
594
595 GlyphSlot::Wide(id) | GlyphSlot::Emoji(id) => {
596 self.cells[idx] = CellDynamic::new(id, cell.fg, cell.bg);
598 self.dirty_regions.mark(idx);
599
600 if let Some(c) = self.cells.get_mut(idx + 1) {
602 *c = CellDynamic::new(id + 1, cell.fg, cell.bg);
603 self.dirty_regions.mark(idx + 1);
604 skip_idx = Some(idx + 1);
605 }
606 },
607 }
608 });
609
610 Ok(())
611 }
612
613 pub fn update_cell(&mut self, x: u16, y: u16, cell_data: CellData) -> Result<(), Error> {
614 let (cols, _) = self.terminal_size;
615 let idx = y as usize * cols as usize + x as usize;
616 self.update_cell_by_index(idx, cell_data)
617 }
618
619 pub fn update_cell_by_index(&mut self, idx: usize, cell_data: CellData) -> Result<(), Error> {
620 self.update_cells_by_index(std::iter::once((idx, cell_data)))
621 }
622
623 pub fn flush_cells(&mut self, gl: &glow::Context) -> Result<(), Error> {
625 if self.dirty_regions.is_clean() {
626 return Ok(()); }
628
629 self.clear_stale_selection();
632
633 self.flip_selected_cell_colors();
637
638 self.gpu.buffers.bind_instance_buffer(gl);
639 if self.dirty_regions.is_all_active_dirty() {
640 self.gpu
642 .buffers
643 .upload_instance_data(gl, &self.cells);
644
645 self.dirty_regions.clear();
646 } else {
647 for (start, end) in self.dirty_regions.drain() {
649 self.gpu.buffers.upload_instance_data_range(
650 gl,
651 &self.cells[start..end],
652 start * CellDynamic::SIZE,
653 );
654 }
655 }
656 self.gpu.buffers.unbind_instance_buffer(gl);
657
658 self.flip_selected_cell_colors();
661
662 Ok(())
663 }
664
665 fn flip_selected_cell_colors(&mut self) {
666 if let Some(iter) = self.selected_cells_iter() {
667 iter.for_each(|(idx, _)| {
668 self.cells[idx].flip_colors();
669 self.dirty_regions.mark(idx);
670 });
671 }
672 }
673
674 fn selected_cells_iter(&self) -> Option<CellIterator> {
675 self.selection
676 .get_query()
677 .map(|query| self.cell_iter(query))
678 }
679
680 pub fn resize(
682 &mut self,
683 gl: &glow::Context,
684 canvas_size: (i32, i32),
685 pixel_ratio: f32,
686 ) -> Result<(), Error> {
687 self.canvas_size_px = canvas_size;
688 self.pixel_ratio = pixel_ratio;
689
690 let cell_size = self.effective_cell_size();
691
692 self.gpu
694 .buffers
695 .update_vertex_buffer(gl, cell_size);
696
697 self.upload_ubo_data(gl);
699
700 let cols = (canvas_size.0 / cell_size.0).max(1);
701 let rows = (canvas_size.1 / cell_size.1).max(1);
702 if self.terminal_size == (cols as u16, rows as u16) {
703 return Ok(()); }
705
706 unsafe {
708 gl.bind_vertex_array(Some(self.gpu.buffers.vao));
709
710 gl.delete_buffer(self.gpu.buffers.instance_cell);
712 gl.delete_buffer(self.gpu.buffers.instance_pos);
713 }
714
715 let current_size = (self.terminal_size.0 as i32, self.terminal_size.1 as i32);
717 let cell_data = self.resize_cell_grid(current_size, (cols, rows));
718 self.cells = cell_data;
719
720 let cell_pos = CellStatic::create_grid(cols, rows);
721
722 self.gpu.buffers.instance_cell = create_dynamic_instance_buffer(gl, &self.cells)?;
724 self.gpu.buffers.instance_pos = create_static_instance_buffer(gl, &cell_pos)?;
725
726 unsafe { gl.bind_vertex_array(None) };
728
729 self.terminal_size = (cols as u16, rows as u16);
730 self.dirty_regions = DirtyRegions::new(self.cells.len());
731
732 Ok(())
733 }
734
735 pub fn recreate_resources(
740 &mut self,
741 gl: &glow::Context,
742 glsl_version: &crate::GlslVersion,
743 ) -> Result<(), Error> {
744 let cell_size = self.effective_cell_size();
745 let (cols, rows) = (self.terminal_size.0 as i32, self.terminal_size.1 as i32);
746 let cell_pos = CellStatic::create_grid(cols, rows);
747
748 self.gpu = GpuResources::new(gl, &cell_pos, &self.cells, cell_size, glsl_version)?;
750
751 self.upload_ubo_data(gl);
753
754 self.dirty_regions.mark_all();
756
757 Ok(())
758 }
759
760 pub fn recreate_atlas_texture(&mut self, gl: &glow::Context) -> Result<(), Error> {
762 self.atlas.recreate_texture(gl)
763 }
764
765 pub fn base_glyph_id(&self, symbol: &str) -> Option<u16> {
767 self.atlas.get_base_glyph_id(symbol)
768 }
769
770 fn fallback_symbol(&self) -> Option<CompactString> {
771 self.atlas.get_symbol(self.fallback_glyph)
772 }
773
774 fn clear_stale_selection(&self) {
775 if let Some(query) = self.selection_tracker().get_query()
776 && let Some(hash) = query.content_hash
777 && hash != self.hash_cells(query)
778 {
779 self.selection.clear();
780 }
781 }
782
783 fn resize_cell_grid(&self, old_size: (i32, i32), new_size: (i32, i32)) -> Vec<CellDynamic> {
784 let empty_cell = CellDynamic::new(self.atlas.space_glyph_id(), 0xFFFFFF, 0x000000);
785
786 let new_len = new_size.0 * new_size.1;
787 let mut new_cells = Vec::with_capacity(new_len as usize);
788 for _ in 0..new_len {
789 new_cells.push(empty_cell);
790 }
791
792 let cells = &self.cells;
793 for y in 0..min(old_size.1, new_size.1) {
794 for x in 0..min(old_size.0, new_size.0) {
795 let new_idx = (y * new_size.0 + x) as usize;
796 let old_idx = (y * old_size.0 + x) as usize;
797 new_cells[new_idx] = cells[old_idx];
798 }
799 }
800
801 new_cells
802 }
803}
804
805fn setup_buffers(
806 gl: &glow::Context,
807 vao: glow::VertexArray,
808 cell_pos: &[CellStatic],
809 cell_data: &[CellDynamic],
810 cell_size: (i32, i32),
811) -> Result<TerminalBuffers, Error> {
812 let (w, h) = (cell_size.0 as f32, cell_size.1 as f32);
813
814 #[rustfmt::skip]
815 let vertices = [
816 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 ];
822 let indices = [0, 1, 2, 0, 3, 1];
823
824 Ok(TerminalBuffers {
825 vao,
826 vertices: create_buffer_f32(gl, glow::ARRAY_BUFFER, &vertices, glow::STATIC_DRAW)?,
827 instance_pos: create_static_instance_buffer(gl, cell_pos)?,
828 instance_cell: create_dynamic_instance_buffer(gl, cell_data)?,
829 indices: create_buffer_u8(gl, glow::ELEMENT_ARRAY_BUFFER, &indices, glow::STATIC_DRAW)?,
830 })
831}
832
833fn create_buffer_u8(
834 gl: &glow::Context,
835 target: u32,
836 data: &[u8],
837 usage: u32,
838) -> Result<glow::Buffer, Error> {
839 let buffer =
840 unsafe { gl.create_buffer() }.map_err(|e| Error::buffer_creation_failed("vbo-u8", e))?;
841 unsafe {
842 gl.bind_buffer(target, Some(buffer));
843 gl.buffer_data_u8_slice(target, data, usage);
844 }
845 Ok(buffer)
846}
847
848fn create_buffer_f32(
849 gl: &glow::Context,
850 target: u32,
851 data: &[f32],
852 usage: u32,
853) -> Result<glow::Buffer, Error> {
854 let buffer =
855 unsafe { gl.create_buffer() }.map_err(|e| Error::buffer_creation_failed("vbo-f32", e))?;
856
857 unsafe {
858 gl.bind_buffer(target, Some(buffer));
859 let bytes =
860 std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data));
861 gl.buffer_data_u8_slice(target, bytes, usage);
862 }
863
864 const STRIDE: i32 = (2 + 2) * 4; enable_vertex_attrib(gl, attrib::POS, 2, glow::FLOAT, 0, STRIDE);
867 enable_vertex_attrib(gl, attrib::UV, 2, glow::FLOAT, 8, STRIDE);
868
869 Ok(buffer)
870}
871
872fn create_static_instance_buffer(
873 gl: &glow::Context,
874 instance_data: &[CellStatic],
875) -> Result<glow::Buffer, Error> {
876 let buffer = unsafe { gl.create_buffer() }
877 .map_err(|e| Error::buffer_creation_failed("static-instance-buffer", e))?;
878
879 unsafe {
880 gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer));
881 buffer_upload_array(gl, glow::ARRAY_BUFFER, instance_data, glow::STATIC_DRAW);
882 }
883
884 let stride = size_of::<CellStatic>() as i32;
885 enable_vertex_attrib_array(gl, attrib::GRID_XY, 2, glow::UNSIGNED_SHORT, 0, stride);
886
887 Ok(buffer)
888}
889
890fn create_dynamic_instance_buffer(
891 gl: &glow::Context,
892 instance_data: &[CellDynamic],
893) -> Result<glow::Buffer, Error> {
894 let buffer = unsafe { gl.create_buffer() }
895 .map_err(|e| Error::buffer_creation_failed("dynamic-instance-buffer", e))?;
896
897 unsafe {
898 gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer));
899 buffer_upload_array(gl, glow::ARRAY_BUFFER, instance_data, glow::DYNAMIC_DRAW);
900 }
901
902 let stride = size_of::<CellDynamic>() as i32;
903
904 enable_vertex_attrib_array(
906 gl,
907 attrib::PACKED_DEPTH_FG_BG,
908 2,
909 glow::UNSIGNED_INT,
910 0,
911 stride,
912 );
913
914 Ok(buffer)
915}
916
917fn enable_vertex_attrib_array(
918 gl: &glow::Context,
919 index: u32,
920 size: i32,
921 type_: u32,
922 offset: i32,
923 stride: i32,
924) {
925 enable_vertex_attrib(gl, index, size, type_, offset, stride);
926 unsafe { gl.vertex_attrib_divisor(index, 1) };
927}
928
929fn enable_vertex_attrib(
930 gl: &glow::Context,
931 index: u32,
932 size: i32,
933 type_: u32,
934 offset: i32,
935 stride: i32,
936) {
937 unsafe {
938 gl.enable_vertex_attrib_array(index);
939 if type_ == glow::FLOAT {
940 gl.vertex_attrib_pointer_f32(index, size, type_, false, stride, offset);
941 } else {
942 gl.vertex_attrib_pointer_i32(index, size, type_, stride, offset);
943 }
944 }
945}
946
947impl Drawable for TerminalGrid {
948 fn prepare(&self, context: &mut RenderContext) -> Result<(), crate::Error> {
949 let gl = context.gl;
950
951 self.gpu.shader.use_program(gl);
952
953 unsafe { gl.bind_vertex_array(Some(self.gpu.buffers.vao)) };
954
955 context.state.active_texture(gl, glow::TEXTURE0);
956 self.atlas.bind(gl);
957 self.atlas.flush(gl)?;
958 self.gpu.ubo_vertex.bind(context.gl);
959 self.gpu.ubo_fragment.bind(context.gl);
960 unsafe { gl.uniform_1_i32(Some(&self.gpu.sampler_loc), 0) };
961
962 Ok(())
963 }
964
965 fn draw(&self, context: &mut RenderContext) {
966 let gl = context.gl;
967 let cell_count = self.cells.len() as i32;
968
969 unsafe {
970 gl.draw_elements_instanced(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0, cell_count);
971 }
972 }
973
974 fn cleanup(&self, context: &mut RenderContext) {
975 let gl = context.gl;
976 unsafe {
977 gl.bind_vertex_array(None);
978 gl.bind_texture(glow::TEXTURE_2D_ARRAY, None);
979 gl.use_program(None);
980 }
981
982 self.gpu.ubo_vertex.unbind(gl);
983 self.gpu.ubo_fragment.unbind(gl);
984 }
985}
986
987#[derive(Debug, Copy, Clone)]
999pub struct CellData<'a> {
1000 symbol: &'a str,
1001 style_bits: u16,
1002 fg: u32,
1003 bg: u32,
1004}
1005
1006impl<'a> CellData<'a> {
1007 pub fn new(symbol: &'a str, style: FontStyle, effect: GlyphEffect, fg: u32, bg: u32) -> Self {
1009 Self::new_with_style_bits(symbol, style.style_mask() | effect as u16, fg, bg)
1010 }
1011
1012 pub fn new_with_style_bits(symbol: &'a str, style_bits: u16, fg: u32, bg: u32) -> Self {
1014 debug_assert!(
1016 0x81FF & style_bits == 0,
1017 "Invalid style bits: {style_bits:#04x}"
1018 );
1019 Self { symbol, style_bits, fg, bg }
1020 }
1021}
1022
1023#[derive(Clone, Copy)]
1025#[repr(C, align(4))]
1026struct CellStatic {
1027 pub grid_xy: [u16; 2],
1029}
1030
1031#[derive(Debug, Clone, Copy, Hash)]
1042#[repr(C, align(4))]
1043pub struct CellDynamic {
1044 data: [u8; 8], }
1057
1058impl CellStatic {
1059 fn create_grid(cols: i32, rows: i32) -> Vec<Self> {
1060 debug_assert!(cols > 0 && cols < u16::MAX as i32, "cols: {cols}");
1061 debug_assert!(rows > 0 && rows < u16::MAX as i32, "rows: {rows}");
1062
1063 (0..rows)
1064 .flat_map(|row| (0..cols).map(move |col| (col, row)))
1065 .map(|(col, row)| Self { grid_xy: [col as u16, row as u16] })
1066 .collect()
1067 }
1068}
1069
1070impl CellDynamic {
1071 const SIZE: usize = size_of::<Self>();
1072
1073 const GLYPH_STYLE_MASK: u16 =
1074 Glyph::BOLD_FLAG | Glyph::ITALIC_FLAG | Glyph::UNDERLINE_FLAG | Glyph::STRIKETHROUGH_FLAG;
1075
1076 #[inline]
1077 pub fn new(glyph_id: u16, fg: u32, bg: u32) -> Self {
1078 let mut data = [0; 8];
1079
1080 let glyph_id = glyph_id.to_le_bytes();
1082 data[0] = glyph_id[0];
1083 data[1] = glyph_id[1];
1084
1085 let fg = fg.to_le_bytes();
1086 data[2] = fg[2]; data[3] = fg[1]; data[4] = fg[0]; let bg = bg.to_le_bytes();
1091 data[5] = bg[2]; data[6] = bg[1]; data[7] = bg[0]; Self { data }
1096 }
1097
1098 pub fn style(&mut self, style_bits: u16) {
1100 let glyph_id = (self.glyph_id() & !Self::GLYPH_STYLE_MASK) | style_bits;
1101 self.data[..2].copy_from_slice(&glyph_id.to_le_bytes());
1102 }
1103
1104 pub fn flip_colors(&mut self) {
1106 let fg = [self.data[2], self.data[3], self.data[4]];
1108 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]; }
1115
1116 pub fn fg_color(&mut self, fg: u32) {
1118 let fg = fg.to_le_bytes();
1119 self.data[2] = fg[2]; self.data[3] = fg[1]; self.data[4] = fg[0]; }
1123
1124 pub fn bg_color(&mut self, bg: u32) {
1126 let bg = bg.to_le_bytes();
1127 self.data[5] = bg[2]; self.data[6] = bg[1]; self.data[7] = bg[0]; }
1131
1132 pub fn get_fg_color(&self) -> u32 {
1134 ((self.data[2] as u32) << 16) | ((self.data[3] as u32) << 8) | (self.data[4] as u32)
1136 }
1137
1138 pub fn get_bg_color(&self) -> u32 {
1140 ((self.data[5] as u32) << 16) | ((self.data[6] as u32) << 8) | (self.data[7] as u32)
1142 }
1143
1144 pub fn get_style(&self) -> u16 {
1146 self.glyph_id() & Self::GLYPH_STYLE_MASK
1147 }
1148
1149 pub fn is_emoji(&self) -> bool {
1151 self.glyph_id() & Glyph::EMOJI_FLAG != 0
1152 }
1153
1154 #[inline]
1155 fn glyph_id(&self) -> u16 {
1156 u16::from_le_bytes([self.data[0], self.data[1]])
1157 }
1158
1159 fn set_glyph_id(&mut self, glyph_id: u16) {
1160 let bytes = glyph_id.to_le_bytes();
1161 self.data[0] = bytes[0];
1162 self.data[1] = bytes[1];
1163 }
1164}
1165
1166#[derive(Clone, Copy)]
1167#[repr(C, align(16))] struct CellVertexUbo {
1169 pub projection: [f32; 16], pub cell_size: [f32; 2], pub _padding: [f32; 2],
1172}
1173
1174#[derive(Clone, Copy)]
1175#[repr(C, align(16))] struct CellFragmentUbo {
1177 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 bg_alpha: f32, }
1185
1186impl CellVertexUbo {
1187 pub const BINDING_POINT: u32 = 0;
1188
1189 fn new(canvas_size: (i32, i32), cell_size: (i32, i32)) -> Self {
1190 let projection =
1191 Mat4::orthographic_from_size(canvas_size.0 as f32, canvas_size.1 as f32).data;
1192 Self {
1193 projection,
1194 cell_size: [cell_size.0 as f32, cell_size.1 as f32],
1195 _padding: [0.0; 2], }
1197 }
1198}
1199
1200impl CellFragmentUbo {
1201 pub const BINDING_POINT: u32 = 1;
1202
1203 fn new(atlas: &FontAtlas, bg_alpha: f32) -> Self {
1204 let texture_cell_size = atlas.texture_cell_size();
1206 let underline = atlas.underline();
1207 let strikethrough = atlas.strikethrough();
1208
1209 Self {
1210 padding_frac: [
1211 FontAtlasData::PADDING as f32 / texture_cell_size.0 as f32,
1212 FontAtlasData::PADDING as f32 / texture_cell_size.1 as f32,
1213 ],
1214 underline_pos: underline.position,
1215 underline_thickness: underline.thickness,
1216 strikethrough_pos: strikethrough.position,
1217 strikethrough_thickness: strikethrough.thickness,
1218 texture_lookup_mask: atlas.base_lookup_mask(),
1219 bg_alpha,
1220 }
1221 }
1222}
1223
1224fn create_terminal_cell_data(cols: i32, rows: i32, fill_glyph: u16) -> Vec<CellDynamic> {
1225 (0..cols * rows)
1226 .map(|_i| CellDynamic::new(fill_glyph, 0x00ff_ffff, 0x0000_0000))
1227 .collect()
1228}
1229
1230mod attrib {
1231 pub const POS: u32 = 0;
1232 pub const UV: u32 = 1;
1233
1234 pub const GRID_XY: u32 = 2;
1235 pub const PACKED_DEPTH_FG_BG: u32 = 3;
1236}