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 selection::SelectionTracker,
15 ubo::UniformBufferObject,
16 },
17 mat4::Mat4,
18};
19
20#[derive(Debug)]
27#[must_use = "call `delete(gl)` before dropping to avoid GPU resource leaks"]
28pub struct TerminalGrid {
29 gpu: GpuResources,
31 cells: Vec<CellDynamic>,
33 terminal_size: (u16, u16),
35 canvas_size_px: (i32, i32),
37 pixel_ratio: f32,
39 atlas: FontAtlas,
41 fallback_glyph: u16,
43 selection: SelectionTracker,
45 cells_pending_flush: bool,
47 bg_alpha: f32,
49}
50
51#[derive(Debug)]
58struct GpuResources {
59 shader: ShaderProgram,
61 buffers: TerminalBuffers,
63 ubo_vertex: UniformBufferObject,
65 ubo_fragment: UniformBufferObject,
67 sampler_loc: glow::UniformLocation,
69}
70
71impl GpuResources {
72 const FRAGMENT_GLSL: &'static str = include_str!("../shaders/cell.frag");
73 const VERTEX_GLSL: &'static str = include_str!("../shaders/cell.vert");
74
75 fn delete(&self, gl: &glow::Context) {
76 self.shader.delete(gl);
77 self.buffers.delete(gl);
78 self.ubo_vertex.delete(gl);
79 self.ubo_fragment.delete(gl);
80 }
81
82 fn new(
91 gl: &glow::Context,
92 cell_pos: &[CellStatic],
93 cell_data: &[CellDynamic],
94 cell_size: (i32, i32),
95 glsl_version: &crate::GlslVersion,
96 ) -> Result<Self, Error> {
97 let vao =
99 unsafe { gl.create_vertex_array() }.map_err(Error::vertex_array_creation_failed)?;
100 unsafe { gl.bind_vertex_array(Some(vao)) };
101
102 let buffers = setup_buffers(gl, vao, cell_pos, cell_data, cell_size)?;
104
105 unsafe { gl.bind_vertex_array(None) };
107
108 let vertex_source = format!("{}{}", glsl_version.vertex_preamble(), Self::VERTEX_GLSL);
110 let fragment_source = format!(
111 "{}{}",
112 glsl_version.fragment_preamble(),
113 Self::FRAGMENT_GLSL
114 );
115 let shader = ShaderProgram::create(gl, &vertex_source, &fragment_source)?;
116 shader.use_program(gl);
117
118 let ubo_vertex = UniformBufferObject::new(gl, CellVertexUbo::BINDING_POINT)?;
119 ubo_vertex.bind_to_shader(gl, &shader, "VertUbo")?;
120 let ubo_fragment = UniformBufferObject::new(gl, CellFragmentUbo::BINDING_POINT)?;
121 ubo_fragment.bind_to_shader(gl, &shader, "FragUbo")?;
122
123 let sampler_loc = unsafe { gl.get_uniform_location(shader.program, "u_sampler") }
124 .ok_or(Error::uniform_location_failed("u_sampler"))?;
125
126 Ok(Self {
127 shader,
128 buffers,
129 ubo_vertex,
130 ubo_fragment,
131 sampler_loc,
132 })
133 }
134}
135
136#[derive(Debug)]
137struct TerminalBuffers {
138 vao: glow::VertexArray,
139 vertices: glow::Buffer,
140 instance_pos: glow::Buffer,
141 instance_cell: glow::Buffer,
142 indices: glow::Buffer,
143}
144
145impl TerminalBuffers {
146 fn delete(&self, gl: &glow::Context) {
147 unsafe {
148 gl.delete_vertex_array(self.vao);
149 gl.delete_buffer(self.vertices);
150 gl.delete_buffer(self.instance_pos);
151 gl.delete_buffer(self.instance_cell);
152 gl.delete_buffer(self.indices);
153 }
154 }
155
156 fn upload_instance_data<T: Copy>(&self, gl: &glow::Context, cell_data: &[T]) {
157 unsafe {
158 gl.bind_vertex_array(Some(self.vao));
159 gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instance_cell));
160 }
161
162 unsafe { buffer_upload_array(gl, glow::ARRAY_BUFFER, cell_data, glow::DYNAMIC_DRAW) };
163
164 unsafe { gl.bind_vertex_array(None) };
165 }
166
167 fn update_vertex_buffer(&self, gl: &glow::Context, cell_size: (i32, i32)) {
169 let (w, h) = (cell_size.0 as f32, cell_size.1 as f32);
170
171 #[rustfmt::skip]
172 let vertices: [f32; 16] = [
173 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 ];
179
180 unsafe {
181 gl.bind_vertex_array(Some(self.vao));
182 gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertices));
183 let bytes = std::slice::from_raw_parts(
184 vertices.as_ptr() as *const u8,
185 vertices.len() * size_of::<f32>(),
186 );
187 gl.buffer_sub_data_u8_slice(glow::ARRAY_BUFFER, 0, bytes);
188 gl.bind_vertex_array(None);
189 }
190 }
191}
192
193impl TerminalGrid {
194 pub fn new(
195 gl: &glow::Context,
196 atlas: FontAtlas,
197 screen_size: (i32, i32),
198 pixel_ratio: f32,
199 glsl_version: &crate::GlslVersion,
200 ) -> Result<Self, Error> {
201 let cell_scale = atlas.cell_scale_for_dpr(pixel_ratio);
202 let base_cell_size = atlas.cell_size();
203 let cell_size = (
204 (base_cell_size.0 as f32 * cell_scale).round() as i32,
205 (base_cell_size.1 as f32 * cell_scale).round() as i32,
206 );
207 let (cols, rows) = (screen_size.0 / cell_size.0, screen_size.1 / cell_size.1);
208
209 let space_glyph = atlas.space_glyph_id();
210 let cell_data = create_terminal_cell_data(cols, rows, space_glyph);
211 let cell_pos = CellStatic::create_grid(cols, rows);
212
213 let grid = Self {
214 gpu: GpuResources::new(gl, &cell_pos, &cell_data, cell_size, glsl_version)?,
215 terminal_size: (cols as u16, rows as u16),
216 canvas_size_px: screen_size,
217 pixel_ratio,
218 cells: cell_data,
219 atlas,
220 fallback_glyph: space_glyph,
221 selection: SelectionTracker::new(),
222 cells_pending_flush: false,
223 bg_alpha: 1.0,
224 };
225
226 grid.upload_ubo_data(gl);
227
228 Ok(grid)
229 }
230
231 pub fn delete(self, gl: &glow::Context) {
237 self.gpu.delete(gl);
238 self.atlas.delete(gl);
239 }
240
241 fn effective_cell_size(&self) -> (i32, i32) {
243 let cell_scale = self.atlas.cell_scale_for_dpr(self.pixel_ratio);
244 let base = self.atlas.cell_size();
245 (
246 (base.0 as f32 * cell_scale).round() as i32,
247 (base.1 as f32 * cell_scale).round() as i32,
248 )
249 }
250
251 pub fn set_fallback_glyph(&mut self, fallback: &str) {
253 self.fallback_glyph = self
254 .atlas
255 .get_glyph_id(fallback, FontStyle::Normal as u16)
256 .unwrap_or(' ' as u16);
257 }
258
259 pub fn replace_atlas(&mut self, gl: &glow::Context, atlas: FontAtlas) {
268 let glyph_mask = self.atlas.base_lookup_mask() as u16;
269 let style_mask = !glyph_mask;
270
271 self.fallback_glyph = self
273 .atlas
274 .get_symbol(self.fallback_glyph & glyph_mask)
275 .and_then(|symbol| {
276 let style_bits = self.fallback_glyph & style_mask;
277 atlas.resolve_glyph_slot(symbol.as_str(), style_bits)
278 })
279 .map(|slot| slot.slot_id())
280 .unwrap_or(atlas.space_glyph_id());
281
282 let mut skip_next = false;
284 for idx in 0..self.cells.len() {
285 if skip_next {
286 skip_next = false;
287 continue;
288 }
289
290 let old_glyph_id = self.cells[idx].glyph_id();
291 let style_bits = old_glyph_id & style_mask;
292
293 let slot = self
294 .atlas
295 .get_symbol(old_glyph_id & glyph_mask)
296 .and_then(|symbol| atlas.resolve_glyph_slot(symbol.as_str(), style_bits));
297
298 match slot {
299 Some(GlyphSlot::Normal(id)) => {
300 self.cells[idx].set_glyph_id(id);
301 },
302 Some(GlyphSlot::Wide(id)) | Some(GlyphSlot::Emoji(id)) => {
303 self.cells[idx].set_glyph_id(id);
304 if let Some(next_cell) = self.cells.get_mut(idx + 1) {
306 next_cell.set_glyph_id(id + 1);
307 skip_next = true;
308 }
309 },
310 None => {
311 self.cells[idx].set_glyph_id(self.fallback_glyph);
312 },
313 }
314 }
315
316 self.selection.clear();
318
319 let old_atlas = std::mem::replace(&mut self.atlas, atlas);
321 old_atlas.delete(gl);
322 self.cells_pending_flush = true;
323
324 self.gpu
326 .buffers
327 .update_vertex_buffer(gl, self.effective_cell_size());
328
329 let _ = self.resize(gl, self.canvas_size_px, self.pixel_ratio);
330 }
331
332 pub fn atlas(&self) -> &FontAtlas {
334 &self.atlas
335 }
336
337 pub fn atlas_mut(&mut self) -> &mut FontAtlas {
339 &mut self.atlas
340 }
341
342 pub fn set_bg_alpha(&mut self, gl: &glow::Context, alpha: f32) {
351 self.bg_alpha = alpha.clamp(0.0, 1.0);
352 self.upload_ubo_data(gl);
353 }
354
355 pub fn canvas_size(&self) -> (i32, i32) {
357 self.canvas_size_px
358 }
359
360 pub fn cell_size(&self) -> (i32, i32) {
362 self.effective_cell_size()
363 }
364
365 pub fn terminal_size(&self) -> (u16, u16) {
367 self.terminal_size
368 }
369
370 pub fn render(&self, gl: &glow::Context, state: &mut GlState) -> Result<(), crate::Error> {
377 let mut ctx = RenderContext { gl, state };
378 self.prepare(&mut ctx)?;
379 self.draw(&mut ctx);
380 self.cleanup(&mut ctx);
381 Ok(())
382 }
383
384 pub fn cell_data_mut(&mut self, x: u16, y: u16) -> Option<&mut CellDynamic> {
386 let (cols, _) = self.terminal_size;
387 let idx = y as usize * cols as usize + x as usize;
388 self.cells_pending_flush = true;
389 self.cells.get_mut(idx)
390 }
391
392 pub fn selection_tracker(&self) -> SelectionTracker {
394 self.selection.clone()
395 }
396
397 pub(super) fn get_symbols(&self, selection: CellIterator) -> CompactString {
399 let mut text = CompactString::new("");
400
401 for (idx, require_newline_after) in selection {
402 let cell_symbol = self.get_cell_symbol(idx);
403 if cell_symbol.is_some() {
404 text.push_str(&cell_symbol.unwrap_or_default());
405 }
406
407 if require_newline_after {
408 text.push('\n'); }
410 }
411
412 text
413 }
414
415 pub(crate) fn get_ascii_char_at(&self, cursor: CursorPosition) -> Option<char> {
420 let idx = cursor.row as usize * self.terminal_size.0 as usize + cursor.col as usize;
421 if idx < self.cells.len() {
422 let glyph_id = self.cells[idx].glyph_id();
423 self.atlas.get_ascii_char(glyph_id)
424 } else {
425 None
426 }
427 }
428
429 pub fn hash_cells(&self, selection: CellQuery) -> u64 {
430 use std::hash::{Hash, Hasher};
431
432 use rustc_hash::FxHasher;
433
434 let mut hasher = FxHasher::default();
435 for (idx, _) in self.cell_iter(selection) {
436 self.cells[idx].hash(&mut hasher);
437 }
438
439 hasher.finish()
440 }
441
442 fn get_cell_symbol(&self, idx: usize) -> Option<CompactString> {
443 if idx < self.cells.len() {
444 let glyph_id = self.cells[idx].glyph_id();
445 let cell_symbol = self.atlas.get_symbol(glyph_id);
446 if cell_symbol.is_some() {
447 return cell_symbol;
448 }
449 }
450
451 self.fallback_symbol()
452 }
453
454 fn upload_ubo_data(&self, gl: &glow::Context) {
456 let vertex_ubo = CellVertexUbo::new(self.canvas_size_px, self.effective_cell_size());
457 self.gpu.ubo_vertex.upload_data(gl, &vertex_ubo);
458
459 let fragment_ubo = CellFragmentUbo::new(&self.atlas, self.bg_alpha);
460 self.gpu
461 .ubo_fragment
462 .upload_data(gl, &fragment_ubo);
463 }
464
465 pub fn cell_count(&self) -> usize {
467 self.cells.len()
468 }
469
470 pub fn update_cells<'a>(
472 &mut self,
473 cells: impl Iterator<Item = CellData<'a>>,
474 ) -> Result<(), Error> {
475 let atlas = &self.atlas;
477
478 let fallback_glyph = GlyphSlot::Normal(self.fallback_glyph);
479
480 let mut pending_cell: Option<CellDynamic> = None;
482 self.cells
483 .iter_mut()
484 .zip(cells)
485 .for_each(|(cell, data)| {
486 let glyph = atlas
487 .resolve_glyph_slot(data.symbol, data.style_bits)
488 .unwrap_or(fallback_glyph);
489
490 *cell = if let Some(second_cell) = pending_cell.take() {
491 second_cell
492 } else {
493 match glyph {
494 GlyphSlot::Normal(id) => CellDynamic::new(id, data.fg, data.bg),
495
496 GlyphSlot::Wide(id) | GlyphSlot::Emoji(id) => {
497 pending_cell = Some(CellDynamic::new(id + 1, data.fg, data.bg));
499 CellDynamic::new(id, data.fg, data.bg)
500 },
501 }
502 }
503 });
504
505 self.cells_pending_flush = true;
506 Ok(())
507 }
508
509 pub fn update_cells_by_position<'a>(
510 &mut self,
511 cells: impl Iterator<Item = (u16, u16, CellData<'a>)>,
512 ) -> Result<(), Error> {
513 let cols = self.terminal_size.0 as usize;
514 let cells_by_index = cells.map(|(x, y, data)| (y as usize * cols + x as usize, data));
515
516 self.update_cells_by_index(cells_by_index)
517 }
518
519 pub fn update_cells_by_index<'a>(
520 &mut self,
521 cells: impl Iterator<Item = (usize, CellData<'a>)>,
522 ) -> Result<(), Error> {
523 let atlas = &self.atlas;
525
526 let cell_count = self.cells.len();
527 let fallback_glyph = GlyphSlot::Normal(self.fallback_glyph);
528
529 let mut skip_idx = None;
534
535 cells
536 .filter(|(idx, _)| *idx < cell_count)
537 .for_each(|(idx, cell)| {
538 if skip_idx.take() == Some(idx) {
539 return;
541 }
542
543 let glyph = atlas
544 .resolve_glyph_slot(cell.symbol, cell.style_bits)
545 .unwrap_or(fallback_glyph);
546
547 match glyph {
548 GlyphSlot::Normal(id) => {
549 self.cells[idx] = CellDynamic::new(id, cell.fg, cell.bg);
550 },
551
552 GlyphSlot::Wide(id) | GlyphSlot::Emoji(id) => {
553 self.cells[idx] = CellDynamic::new(id, cell.fg, cell.bg);
555
556 if let Some(c) = self.cells.get_mut(idx + 1) {
558 *c = CellDynamic::new(id + 1, cell.fg, cell.bg);
559 skip_idx = Some(idx + 1);
560 }
561 },
562 }
563 });
564
565 self.cells_pending_flush = true;
566
567 Ok(())
568 }
569
570 pub fn update_cell(&mut self, x: u16, y: u16, cell_data: CellData) -> Result<(), Error> {
571 let (cols, _) = self.terminal_size;
572 let idx = y as usize * cols as usize + x as usize;
573 self.update_cell_by_index(idx, cell_data)
574 }
575
576 pub fn update_cell_by_index(&mut self, idx: usize, cell_data: CellData) -> Result<(), Error> {
577 self.update_cells_by_index(std::iter::once((idx, cell_data)))
578 }
579
580 pub fn flush_cells(&mut self, gl: &glow::Context) -> Result<(), Error> {
582 if !self.cells_pending_flush {
583 return Ok(()); }
585
586 self.clear_stale_selection();
589
590 self.flip_selected_cell_colors();
594
595 self.gpu
596 .buffers
597 .upload_instance_data(gl, &self.cells);
598
599 self.flip_selected_cell_colors();
602
603 self.cells_pending_flush = false;
604 Ok(())
605 }
606
607 fn flip_selected_cell_colors(&mut self) {
608 if let Some(iter) = self.selected_cells_iter() {
609 iter.for_each(|(idx, _)| self.cells[idx].flip_colors());
610 }
611 }
612
613 fn selected_cells_iter(&self) -> Option<CellIterator> {
614 self.selection
615 .get_query()
616 .map(|query| self.cell_iter(query))
617 }
618
619 pub fn resize(
621 &mut self,
622 gl: &glow::Context,
623 canvas_size: (i32, i32),
624 pixel_ratio: f32,
625 ) -> Result<(), Error> {
626 self.canvas_size_px = canvas_size;
627 self.pixel_ratio = pixel_ratio;
628
629 let cell_size = self.effective_cell_size();
630
631 self.gpu
633 .buffers
634 .update_vertex_buffer(gl, cell_size);
635
636 self.upload_ubo_data(gl);
638
639 let cols = (canvas_size.0 / cell_size.0).max(1);
640 let rows = (canvas_size.1 / cell_size.1).max(1);
641 if self.terminal_size == (cols as u16, rows as u16) {
642 return Ok(()); }
644
645 unsafe {
647 gl.bind_vertex_array(Some(self.gpu.buffers.vao));
648
649 gl.delete_buffer(self.gpu.buffers.instance_cell);
651 gl.delete_buffer(self.gpu.buffers.instance_pos);
652 }
653
654 let current_size = (self.terminal_size.0 as i32, self.terminal_size.1 as i32);
656 let cell_data = self.resize_cell_grid(current_size, (cols, rows));
657 self.cells = cell_data;
658
659 let cell_pos = CellStatic::create_grid(cols, rows);
660
661 self.gpu.buffers.instance_cell = create_dynamic_instance_buffer(gl, &self.cells)?;
663 self.gpu.buffers.instance_pos = create_static_instance_buffer(gl, &cell_pos)?;
664
665 unsafe { gl.bind_vertex_array(None) };
667
668 self.terminal_size = (cols as u16, rows as u16);
669
670 Ok(())
671 }
672
673 pub fn recreate_resources(
678 &mut self,
679 gl: &glow::Context,
680 glsl_version: &crate::GlslVersion,
681 ) -> Result<(), Error> {
682 let cell_size = self.effective_cell_size();
683 let (cols, rows) = (self.terminal_size.0 as i32, self.terminal_size.1 as i32);
684 let cell_pos = CellStatic::create_grid(cols, rows);
685
686 self.gpu = GpuResources::new(gl, &cell_pos, &self.cells, cell_size, glsl_version)?;
688
689 self.upload_ubo_data(gl);
691
692 self.cells_pending_flush = true;
694
695 Ok(())
696 }
697
698 pub fn recreate_atlas_texture(&mut self, gl: &glow::Context) -> Result<(), Error> {
700 self.atlas.recreate_texture(gl)
701 }
702
703 pub fn base_glyph_id(&self, symbol: &str) -> Option<u16> {
705 self.atlas.get_base_glyph_id(symbol)
706 }
707
708 fn fallback_symbol(&self) -> Option<CompactString> {
709 self.atlas.get_symbol(self.fallback_glyph)
710 }
711
712 fn clear_stale_selection(&self) {
713 if let Some(query) = self.selection_tracker().get_query()
714 && let Some(hash) = query.content_hash
715 && hash != self.hash_cells(query)
716 {
717 self.selection.clear();
718 }
719 }
720
721 fn resize_cell_grid(&self, old_size: (i32, i32), new_size: (i32, i32)) -> Vec<CellDynamic> {
722 let empty_cell = CellDynamic::new(self.atlas.space_glyph_id(), 0xFFFFFF, 0x000000);
723
724 let new_len = new_size.0 * new_size.1;
725 let mut new_cells = Vec::with_capacity(new_len as usize);
726 for _ in 0..new_len {
727 new_cells.push(empty_cell);
728 }
729
730 let cells = &self.cells;
731 for y in 0..min(old_size.1, new_size.1) {
732 for x in 0..min(old_size.0, new_size.0) {
733 let new_idx = (y * new_size.0 + x) as usize;
734 let old_idx = (y * old_size.0 + x) as usize;
735 new_cells[new_idx] = cells[old_idx];
736 }
737 }
738
739 new_cells
740 }
741}
742
743fn setup_buffers(
744 gl: &glow::Context,
745 vao: glow::VertexArray,
746 cell_pos: &[CellStatic],
747 cell_data: &[CellDynamic],
748 cell_size: (i32, i32),
749) -> Result<TerminalBuffers, Error> {
750 let (w, h) = (cell_size.0 as f32, cell_size.1 as f32);
751
752 #[rustfmt::skip]
753 let vertices = [
754 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 ];
760 let indices = [0, 1, 2, 0, 3, 1];
761
762 Ok(TerminalBuffers {
763 vao,
764 vertices: create_buffer_f32(gl, glow::ARRAY_BUFFER, &vertices, glow::STATIC_DRAW)?,
765 instance_pos: create_static_instance_buffer(gl, cell_pos)?,
766 instance_cell: create_dynamic_instance_buffer(gl, cell_data)?,
767 indices: create_buffer_u8(gl, glow::ELEMENT_ARRAY_BUFFER, &indices, glow::STATIC_DRAW)?,
768 })
769}
770
771fn create_buffer_u8(
772 gl: &glow::Context,
773 target: u32,
774 data: &[u8],
775 usage: u32,
776) -> Result<glow::Buffer, Error> {
777 let buffer =
778 unsafe { gl.create_buffer() }.map_err(|e| Error::buffer_creation_failed("vbo-u8", e))?;
779 unsafe {
780 gl.bind_buffer(target, Some(buffer));
781 gl.buffer_data_u8_slice(target, data, usage);
782 }
783 Ok(buffer)
784}
785
786fn create_buffer_f32(
787 gl: &glow::Context,
788 target: u32,
789 data: &[f32],
790 usage: u32,
791) -> Result<glow::Buffer, Error> {
792 let buffer =
793 unsafe { gl.create_buffer() }.map_err(|e| Error::buffer_creation_failed("vbo-f32", e))?;
794
795 unsafe {
796 gl.bind_buffer(target, Some(buffer));
797 let bytes =
798 std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data));
799 gl.buffer_data_u8_slice(target, bytes, usage);
800 }
801
802 const STRIDE: i32 = (2 + 2) * 4; enable_vertex_attrib(gl, attrib::POS, 2, glow::FLOAT, 0, STRIDE);
805 enable_vertex_attrib(gl, attrib::UV, 2, glow::FLOAT, 8, STRIDE);
806
807 Ok(buffer)
808}
809
810fn create_static_instance_buffer(
811 gl: &glow::Context,
812 instance_data: &[CellStatic],
813) -> Result<glow::Buffer, Error> {
814 let buffer = unsafe { gl.create_buffer() }
815 .map_err(|e| Error::buffer_creation_failed("static-instance-buffer", e))?;
816
817 unsafe {
818 gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer));
819 buffer_upload_array(gl, glow::ARRAY_BUFFER, instance_data, glow::STATIC_DRAW);
820 }
821
822 let stride = size_of::<CellStatic>() as i32;
823 enable_vertex_attrib_array(gl, attrib::GRID_XY, 2, glow::UNSIGNED_SHORT, 0, stride);
824
825 Ok(buffer)
826}
827
828fn create_dynamic_instance_buffer(
829 gl: &glow::Context,
830 instance_data: &[CellDynamic],
831) -> Result<glow::Buffer, Error> {
832 let buffer = unsafe { gl.create_buffer() }
833 .map_err(|e| Error::buffer_creation_failed("dynamic-instance-buffer", e))?;
834
835 unsafe {
836 gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer));
837 buffer_upload_array(gl, glow::ARRAY_BUFFER, instance_data, glow::DYNAMIC_DRAW);
838 }
839
840 let stride = size_of::<CellDynamic>() as i32;
841
842 enable_vertex_attrib_array(
844 gl,
845 attrib::PACKED_DEPTH_FG_BG,
846 2,
847 glow::UNSIGNED_INT,
848 0,
849 stride,
850 );
851
852 Ok(buffer)
853}
854
855fn enable_vertex_attrib_array(
856 gl: &glow::Context,
857 index: u32,
858 size: i32,
859 type_: u32,
860 offset: i32,
861 stride: i32,
862) {
863 enable_vertex_attrib(gl, index, size, type_, offset, stride);
864 unsafe { gl.vertex_attrib_divisor(index, 1) };
865}
866
867fn enable_vertex_attrib(
868 gl: &glow::Context,
869 index: u32,
870 size: i32,
871 type_: u32,
872 offset: i32,
873 stride: i32,
874) {
875 unsafe {
876 gl.enable_vertex_attrib_array(index);
877 if type_ == glow::FLOAT {
878 gl.vertex_attrib_pointer_f32(index, size, type_, false, stride, offset);
879 } else {
880 gl.vertex_attrib_pointer_i32(index, size, type_, stride, offset);
881 }
882 }
883}
884
885impl Drawable for TerminalGrid {
886 fn prepare(&self, context: &mut RenderContext) -> Result<(), crate::Error> {
887 let gl = context.gl;
888
889 self.gpu.shader.use_program(gl);
890
891 unsafe { gl.bind_vertex_array(Some(self.gpu.buffers.vao)) };
892
893 context.state.active_texture(gl, glow::TEXTURE0);
894 self.atlas.bind(gl);
895 self.atlas.flush(gl)?;
896 self.gpu.ubo_vertex.bind(context.gl);
897 self.gpu.ubo_fragment.bind(context.gl);
898 unsafe { gl.uniform_1_i32(Some(&self.gpu.sampler_loc), 0) };
899
900 Ok(())
901 }
902
903 fn draw(&self, context: &mut RenderContext) {
904 let gl = context.gl;
905 let cell_count = self.cells.len() as i32;
906
907 unsafe {
908 gl.draw_elements_instanced(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0, cell_count);
909 }
910 }
911
912 fn cleanup(&self, context: &mut RenderContext) {
913 let gl = context.gl;
914 unsafe {
915 gl.bind_vertex_array(None);
916 gl.bind_texture(glow::TEXTURE_2D_ARRAY, None);
917 gl.use_program(None);
918 }
919
920 self.gpu.ubo_vertex.unbind(gl);
921 self.gpu.ubo_fragment.unbind(gl);
922 }
923}
924
925#[derive(Debug, Copy, Clone)]
937pub struct CellData<'a> {
938 symbol: &'a str,
939 style_bits: u16,
940 fg: u32,
941 bg: u32,
942}
943
944impl<'a> CellData<'a> {
945 pub fn new(symbol: &'a str, style: FontStyle, effect: GlyphEffect, fg: u32, bg: u32) -> Self {
947 Self::new_with_style_bits(symbol, style.style_mask() | effect as u16, fg, bg)
948 }
949
950 pub fn new_with_style_bits(symbol: &'a str, style_bits: u16, fg: u32, bg: u32) -> Self {
952 debug_assert!(
954 0x81FF & style_bits == 0,
955 "Invalid style bits: {style_bits:#04x}"
956 );
957 Self { symbol, style_bits, fg, bg }
958 }
959}
960
961#[derive(Clone, Copy)]
963#[repr(C, align(4))]
964struct CellStatic {
965 pub grid_xy: [u16; 2],
967}
968
969#[derive(Debug, Clone, Copy, Hash)]
980#[repr(C, align(4))]
981pub struct CellDynamic {
982 data: [u8; 8], }
995
996impl CellStatic {
997 fn create_grid(cols: i32, rows: i32) -> Vec<Self> {
998 debug_assert!(cols > 0 && cols < u16::MAX as i32, "cols: {cols}");
999 debug_assert!(rows > 0 && rows < u16::MAX as i32, "rows: {rows}");
1000
1001 (0..rows)
1002 .flat_map(|row| (0..cols).map(move |col| (col, row)))
1003 .map(|(col, row)| Self { grid_xy: [col as u16, row as u16] })
1004 .collect()
1005 }
1006}
1007
1008impl CellDynamic {
1009 const GLYPH_STYLE_MASK: u16 =
1010 Glyph::BOLD_FLAG | Glyph::ITALIC_FLAG | Glyph::UNDERLINE_FLAG | Glyph::STRIKETHROUGH_FLAG;
1011
1012 #[inline]
1013 pub fn new(glyph_id: u16, fg: u32, bg: u32) -> Self {
1014 let mut data = [0; 8];
1015
1016 let glyph_id = glyph_id.to_le_bytes();
1018 data[0] = glyph_id[0];
1019 data[1] = glyph_id[1];
1020
1021 let fg = fg.to_le_bytes();
1022 data[2] = fg[2]; data[3] = fg[1]; data[4] = fg[0]; let bg = bg.to_le_bytes();
1027 data[5] = bg[2]; data[6] = bg[1]; data[7] = bg[0]; Self { data }
1032 }
1033
1034 pub fn style(&mut self, style_bits: u16) {
1036 let glyph_id = (self.glyph_id() & !Self::GLYPH_STYLE_MASK) | style_bits;
1037 self.data[..2].copy_from_slice(&glyph_id.to_le_bytes());
1038 }
1039
1040 pub fn flip_colors(&mut self) {
1042 let fg = [self.data[2], self.data[3], self.data[4]];
1044 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]; }
1051
1052 pub fn fg_color(&mut self, fg: u32) {
1054 let fg = fg.to_le_bytes();
1055 self.data[2] = fg[2]; self.data[3] = fg[1]; self.data[4] = fg[0]; }
1059
1060 pub fn bg_color(&mut self, bg: u32) {
1062 let bg = bg.to_le_bytes();
1063 self.data[5] = bg[2]; self.data[6] = bg[1]; self.data[7] = bg[0]; }
1067
1068 pub fn get_fg_color(&self) -> u32 {
1070 ((self.data[2] as u32) << 16) | ((self.data[3] as u32) << 8) | (self.data[4] as u32)
1072 }
1073
1074 pub fn get_bg_color(&self) -> u32 {
1076 ((self.data[5] as u32) << 16) | ((self.data[6] as u32) << 8) | (self.data[7] as u32)
1078 }
1079
1080 pub fn get_style(&self) -> u16 {
1082 self.glyph_id() & Self::GLYPH_STYLE_MASK
1083 }
1084
1085 pub fn is_emoji(&self) -> bool {
1087 self.glyph_id() & Glyph::EMOJI_FLAG != 0
1088 }
1089
1090 #[inline]
1091 fn glyph_id(&self) -> u16 {
1092 u16::from_le_bytes([self.data[0], self.data[1]])
1093 }
1094
1095 fn set_glyph_id(&mut self, glyph_id: u16) {
1096 let bytes = glyph_id.to_le_bytes();
1097 self.data[0] = bytes[0];
1098 self.data[1] = bytes[1];
1099 }
1100}
1101
1102#[derive(Clone, Copy)]
1103#[repr(C, align(16))] struct CellVertexUbo {
1105 pub projection: [f32; 16], pub cell_size: [f32; 2], pub _padding: [f32; 2],
1108}
1109
1110#[derive(Clone, Copy)]
1111#[repr(C, align(16))] struct CellFragmentUbo {
1113 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, }
1121
1122impl CellVertexUbo {
1123 pub const BINDING_POINT: u32 = 0;
1124
1125 fn new(canvas_size: (i32, i32), cell_size: (i32, i32)) -> Self {
1126 let projection =
1127 Mat4::orthographic_from_size(canvas_size.0 as f32, canvas_size.1 as f32).data;
1128 Self {
1129 projection,
1130 cell_size: [cell_size.0 as f32, cell_size.1 as f32],
1131 _padding: [0.0; 2], }
1133 }
1134}
1135
1136impl CellFragmentUbo {
1137 pub const BINDING_POINT: u32 = 1;
1138
1139 fn new(atlas: &FontAtlas, bg_alpha: f32) -> Self {
1140 let texture_cell_size = atlas.texture_cell_size();
1142 let underline = atlas.underline();
1143 let strikethrough = atlas.strikethrough();
1144
1145 Self {
1146 padding_frac: [
1147 FontAtlasData::PADDING as f32 / texture_cell_size.0 as f32,
1148 FontAtlasData::PADDING as f32 / texture_cell_size.1 as f32,
1149 ],
1150 underline_pos: underline.position,
1151 underline_thickness: underline.thickness,
1152 strikethrough_pos: strikethrough.position,
1153 strikethrough_thickness: strikethrough.thickness,
1154 texture_lookup_mask: atlas.base_lookup_mask(),
1155 bg_alpha,
1156 }
1157 }
1158}
1159
1160fn create_terminal_cell_data(cols: i32, rows: i32, fill_glyph: u16) -> Vec<CellDynamic> {
1161 (0..cols * rows)
1162 .map(|_i| CellDynamic::new(fill_glyph, 0x00ff_ffff, 0x0000_0000))
1163 .collect()
1164}
1165
1166mod attrib {
1167 pub const POS: u32 = 0;
1168 pub const UV: u32 = 1;
1169
1170 pub const GRID_XY: u32 = 2;
1171 pub const PACKED_DEPTH_FG_BG: u32 = 3;
1172}