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 pixel_ratio: f32,
37 atlas: FontAtlas,
39 fallback_glyph: u16,
41 selection: SelectionTracker,
43 cells_pending_flush: bool,
45}
46
47#[derive(Debug)]
54struct GpuResources {
55 shader: ShaderProgram,
57 buffers: TerminalBuffers,
59 ubo_vertex: UniformBufferObject,
61 ubo_fragment: UniformBufferObject,
63 sampler_loc: web_sys::WebGlUniformLocation,
65}
66
67impl GpuResources {
68 const FRAGMENT_GLSL: &'static str = include_str!("../shaders/cell.frag");
69 const VERTEX_GLSL: &'static str = include_str!("../shaders/cell.vert");
70
71 fn new(
80 gl: &WebGl2RenderingContext,
81 cell_pos: &[CellStatic],
82 cell_data: &[CellDynamic],
83 cell_size: (i32, i32),
84 ) -> Result<Self, Error> {
85 let vao = create_vao(gl)?;
87 gl.bind_vertex_array(Some(&vao));
88
89 let buffers = setup_buffers(gl, vao, cell_pos, cell_data, cell_size)?;
91
92 gl.bind_vertex_array(None);
94
95 let shader = ShaderProgram::create(gl, Self::VERTEX_GLSL, Self::FRAGMENT_GLSL)?;
97 shader.use_program(gl);
98
99 let ubo_vertex = UniformBufferObject::new(gl, CellVertexUbo::BINDING_POINT)?;
100 ubo_vertex.bind_to_shader(gl, &shader, "VertUbo")?;
101 let ubo_fragment = UniformBufferObject::new(gl, CellFragmentUbo::BINDING_POINT)?;
102 ubo_fragment.bind_to_shader(gl, &shader, "FragUbo")?;
103
104 let sampler_loc = gl
105 .get_uniform_location(&shader.program, "u_sampler")
106 .ok_or(Error::uniform_location_failed("u_sampler"))?;
107
108 Ok(Self {
109 shader,
110 buffers,
111 ubo_vertex,
112 ubo_fragment,
113 sampler_loc,
114 })
115 }
116}
117
118#[derive(Debug)]
119struct TerminalBuffers {
120 vao: web_sys::WebGlVertexArrayObject,
121 vertices: web_sys::WebGlBuffer,
122 instance_pos: web_sys::WebGlBuffer,
123 instance_cell: web_sys::WebGlBuffer,
124 indices: web_sys::WebGlBuffer,
125}
126
127impl TerminalBuffers {
128 fn upload_instance_data<T>(&self, gl: &WebGl2RenderingContext, cell_data: &[T]) {
129 gl.bind_vertex_array(Some(&self.vao));
130 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&self.instance_cell));
131
132 buffer_upload_array(gl, GL::ARRAY_BUFFER, cell_data, GL::DYNAMIC_DRAW);
133
134 gl.bind_vertex_array(None);
135 }
136
137 fn update_vertex_buffer(&self, gl: &WebGl2RenderingContext, cell_size: (i32, i32)) {
139 let (w, h) = (cell_size.0 as f32, cell_size.1 as f32);
140
141 #[rustfmt::skip]
142 let vertices: [f32; 16] = [
143 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 ];
149
150 gl.bind_vertex_array(Some(&self.vao));
151 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&self.vertices));
152
153 unsafe {
154 let view = js_sys::Float32Array::view(&vertices);
155 gl.buffer_sub_data_with_i32_and_array_buffer_view(GL::ARRAY_BUFFER, 0, &view);
156 }
157
158 gl.bind_vertex_array(None);
159 }
160}
161
162impl TerminalGrid {
163 pub(crate) fn new(
164 gl: &WebGl2RenderingContext,
165 atlas: FontAtlas,
166 screen_size: (i32, i32),
167 pixel_ratio: f32,
168 ) -> Result<Self, Error> {
169 let cell_scale = atlas.cell_scale_for_dpr(pixel_ratio);
170 let base_cell_size = atlas.cell_size();
171 let cell_size = (
172 (base_cell_size.0 as f32 * cell_scale).round() as i32,
173 (base_cell_size.1 as f32 * cell_scale).round() as i32,
174 );
175 let (cols, rows) = (screen_size.0 / cell_size.0, screen_size.1 / cell_size.1);
176
177 let space_glyph = atlas.space_glyph_id();
178 let cell_data = create_terminal_cell_data(cols, rows, space_glyph);
179 let cell_pos = CellStatic::create_grid(cols, rows);
180
181 let grid = Self {
182 gpu: GpuResources::new(gl, &cell_pos, &cell_data, cell_size)?,
183 terminal_size: (cols as u16, rows as u16),
184 canvas_size_px: screen_size,
185 pixel_ratio,
186 cells: cell_data,
187 atlas,
188 fallback_glyph: space_glyph,
189 selection: SelectionTracker::new(),
190 cells_pending_flush: false,
191 };
192
193 grid.upload_ubo_data(gl);
194
195 Ok(grid)
196 }
197
198 fn effective_cell_size(&self) -> (i32, i32) {
200 let cell_scale = self.atlas.cell_scale_for_dpr(self.pixel_ratio);
201 let base = self.atlas.cell_size();
202 (
203 (base.0 as f32 * cell_scale).round() as i32,
204 (base.1 as f32 * cell_scale).round() as i32,
205 )
206 }
207
208 pub fn set_fallback_glyph(&mut self, fallback: &str) {
210 self.fallback_glyph = self
211 .atlas
212 .get_glyph_id(fallback, FontStyle::Normal as u16)
213 .unwrap_or(' ' as u16);
214 }
215
216 pub(crate) fn replace_atlas(&mut self, gl: &WebGl2RenderingContext, atlas: FontAtlas) {
229 let glyph_mask = self.atlas.base_lookup_mask() as u16;
230 let style_mask = !glyph_mask;
231
232 self.fallback_glyph = self
234 .atlas
235 .get_symbol(self.fallback_glyph & glyph_mask)
236 .and_then(|symbol| {
237 let style_bits = self.fallback_glyph & style_mask;
238 atlas.resolve_glyph_slot(symbol.as_str(), style_bits)
239 })
240 .map(|slot| slot.slot_id())
241 .unwrap_or(atlas.space_glyph_id());
242
243 let mut skip_next = false;
245 for idx in 0..self.cells.len() {
246 if skip_next {
247 skip_next = false;
248 continue;
249 }
250
251 let old_glyph_id = self.cells[idx].glyph_id();
252 let style_bits = old_glyph_id & style_mask;
253
254 let slot = self
255 .atlas
256 .get_symbol(old_glyph_id & glyph_mask)
257 .and_then(|symbol| atlas.resolve_glyph_slot(symbol.as_str(), style_bits));
258
259 match slot {
260 Some(GlyphSlot::Normal(id)) => {
261 self.cells[idx].set_glyph_id(id);
262 },
263 Some(GlyphSlot::Wide(id)) | Some(GlyphSlot::Emoji(id)) => {
264 self.cells[idx].set_glyph_id(id);
265 if let Some(next_cell) = self.cells.get_mut(idx + 1) {
267 next_cell.set_glyph_id(id + 1);
268 skip_next = true;
269 }
270 },
271 None => {
272 self.cells[idx].set_glyph_id(self.fallback_glyph);
273 },
274 }
275 }
276
277 self.selection.clear();
279
280 let old_atlas = std::mem::replace(&mut self.atlas, atlas);
282 old_atlas.delete(gl);
283 self.cells_pending_flush = true;
284
285 self.gpu
287 .buffers
288 .update_vertex_buffer(gl, self.effective_cell_size());
289
290 let _ = self.resize(gl, self.canvas_size_px, self.pixel_ratio);
291 }
292
293 pub(crate) fn atlas(&self) -> &FontAtlas {
295 &self.atlas
296 }
297
298 pub(crate) fn atlas_mut(&mut self) -> &mut FontAtlas {
300 &mut self.atlas
301 }
302
303 pub(crate) fn canvas_size(&self) -> (i32, i32) {
305 self.canvas_size_px
306 }
307
308 pub fn cell_size(&self) -> (i32, i32) {
310 self.effective_cell_size()
311 }
312
313 pub fn terminal_size(&self) -> (u16, u16) {
315 self.terminal_size
316 }
317
318 pub fn cell_data_mut(&mut self, x: u16, y: u16) -> Option<&mut CellDynamic> {
320 let (cols, _) = self.terminal_size;
321 let idx = y as usize * cols as usize + x as usize;
322 self.cells.get_mut(idx)
323 }
324
325 pub(crate) fn selection_tracker(&self) -> SelectionTracker {
327 self.selection.clone()
328 }
329
330 pub(super) fn get_symbols(&self, selection: CellIterator) -> CompactString {
332 let (cols, rows) = self.terminal_size;
333 let mut text = CompactString::new("");
334
335 for (idx, require_newline_after) in selection {
336 let cell_symbol = self.get_cell_symbol(idx);
337 if cell_symbol.is_some() {
338 text.push_str(&cell_symbol.unwrap_or_default());
339 }
340
341 if require_newline_after {
342 text.push('\n'); }
344 }
345
346 text
347 }
348
349 pub(crate) fn hash_cells(&self, selection: CellQuery) -> u64 {
350 use std::hash::{Hash, Hasher};
351
352 use rustc_hash::FxHasher;
353
354 let mut hasher = FxHasher::default();
355 for (idx, _) in self.cell_iter(selection) {
356 self.cells[idx].hash(&mut hasher);
357 }
358
359 hasher.finish()
360 }
361
362 fn get_cell_symbol(&self, idx: usize) -> Option<CompactString> {
363 if idx < self.cells.len() {
364 let glyph_id = self.cells[idx].glyph_id();
365 let cell_symbol = self.atlas.get_symbol(glyph_id);
366 if cell_symbol.is_some() {
367 return cell_symbol;
368 }
369 }
370
371 self.fallback_symbol()
372 }
373
374 fn upload_ubo_data(&self, gl: &WebGl2RenderingContext) {
383 let vertex_ubo = CellVertexUbo::new(self.canvas_size_px, self.effective_cell_size());
384 self.gpu.ubo_vertex.upload_data(gl, &vertex_ubo);
385
386 let fragment_ubo = CellFragmentUbo::new(&self.atlas);
387 self.gpu
388 .ubo_fragment
389 .upload_data(gl, &fragment_ubo);
390 }
391
392 pub fn cell_count(&self) -> usize {
394 self.cells.len()
395 }
396
397 pub fn update_cells<'a>(
411 &mut self,
412 gl: &WebGl2RenderingContext,
413 cells: impl Iterator<Item = CellData<'a>>,
414 ) -> Result<(), Error> {
415 let atlas = &self.atlas;
417
418 let fallback_glyph = GlyphSlot::Normal(self.fallback_glyph);
419
420 let mut pending_cell: Option<CellDynamic> = None;
422 self.cells
423 .iter_mut()
424 .zip(cells)
425 .for_each(|(cell, data)| {
426 let glyph = atlas
427 .resolve_glyph_slot(data.symbol, data.style_bits)
428 .unwrap_or(fallback_glyph);
429
430 *cell = if let Some(second_cell) = pending_cell.take() {
431 second_cell
432 } else {
433 match glyph {
434 GlyphSlot::Normal(id) => CellDynamic::new(id, data.fg, data.bg),
435
436 GlyphSlot::Wide(id) | GlyphSlot::Emoji(id) => {
437 pending_cell = Some(CellDynamic::new(id + 1, data.fg, data.bg));
439 CellDynamic::new(id, data.fg, data.bg)
440 },
441 }
442 }
443 });
444
445 self.cells_pending_flush = true;
446 Ok(())
447 }
448
449 pub(crate) fn update_cells_by_position<'a>(
450 &mut self,
451 cells: impl Iterator<Item = (u16, u16, CellData<'a>)>,
452 ) -> Result<(), Error> {
453 let cols = self.terminal_size.0 as usize;
454 let cells_by_index = cells.map(|(x, y, data)| (y as usize * cols + x as usize, data));
455
456 self.update_cells_by_index(cells_by_index)
457 }
458
459 pub(crate) fn update_cells_by_index<'a>(
460 &mut self,
461 cells: impl Iterator<Item = (usize, CellData<'a>)>,
462 ) -> Result<(), Error> {
463 let atlas = &self.atlas;
465
466 let cell_count = self.cells.len();
467 let fallback_glyph = GlyphSlot::Normal(self.fallback_glyph);
468
469 let mut skip_idx = None;
474
475 cells
476 .filter(|(idx, _)| *idx < cell_count)
477 .for_each(|(idx, cell)| {
478 if skip_idx.take() == Some(idx) {
479 return;
481 }
482
483 let glyph = atlas
484 .resolve_glyph_slot(cell.symbol, cell.style_bits)
485 .unwrap_or(fallback_glyph);
486
487 match glyph {
488 GlyphSlot::Normal(id) => {
489 self.cells[idx] = CellDynamic::new(id, cell.fg, cell.bg);
490 },
491
492 GlyphSlot::Wide(id) | GlyphSlot::Emoji(id) => {
493 self.cells[idx] = CellDynamic::new(id, cell.fg, cell.bg);
495
496 if let Some(c) = self.cells.get_mut(idx + 1) {
498 *c = CellDynamic::new(id + 1, cell.fg, cell.bg);
499 skip_idx = Some(idx + 1);
500 }
501 },
502 }
503 });
504
505 self.cells_pending_flush = true;
506
507 Ok(())
508 }
509
510 pub(crate) fn update_cell(&mut self, x: u16, y: u16, cell_data: CellData) -> Result<(), Error> {
511 let (cols, _) = self.terminal_size;
512 let idx = y as usize * cols as usize + x as usize;
513 self.update_cell_by_index(idx, cell_data)
514 }
515
516 pub(crate) fn update_cell_by_index(
517 &mut self,
518 idx: usize,
519 cell_data: CellData,
520 ) -> Result<(), Error> {
521 self.update_cells_by_index(std::iter::once((idx, cell_data)))
522 }
523
524 pub(crate) fn flush_cells(&mut self, gl: &WebGl2RenderingContext) -> Result<(), Error> {
526 if !self.cells_pending_flush {
527 return Ok(()); }
529
530 self.clear_stale_selection();
533
534 self.flip_selected_cell_colors();
538
539 self.gpu
540 .buffers
541 .upload_instance_data(gl, &self.cells);
542
543 self.flip_selected_cell_colors();
546
547 self.cells_pending_flush = false;
548 Ok(())
549 }
550
551 fn flip_selected_cell_colors(&mut self) {
552 if let Some(iter) = self.selected_cells_iter() {
553 iter.for_each(|(idx, _)| self.cells[idx].flip_colors());
554 }
555 }
556
557 fn selected_cells_iter(&self) -> Option<CellIterator> {
558 self.selection
559 .get_query()
560 .map(|query| self.cell_iter(query))
561 }
562
563 fn flip_cell_colors(&mut self, x: u16, y: u16) {
564 let (cols, _) = self.terminal_size;
565 let idx = y as usize * cols as usize + x as usize;
566 if idx < self.cells.len() {
567 self.cells[idx].flip_colors();
568 }
569 }
570
571 pub fn resize(
587 &mut self,
588 gl: &WebGl2RenderingContext,
589 canvas_size: (i32, i32),
590 pixel_ratio: f32,
591 ) -> Result<(), Error> {
592 self.canvas_size_px = canvas_size;
593 self.pixel_ratio = pixel_ratio;
594
595 let cell_size = self.effective_cell_size();
596
597 self.gpu
599 .buffers
600 .update_vertex_buffer(gl, cell_size);
601
602 self.upload_ubo_data(gl);
604
605 let cols = canvas_size.0 / cell_size.0;
606 let rows = canvas_size.1 / cell_size.1;
607 if self.terminal_size == (cols as u16, rows as u16) {
608 return Ok(()); }
610
611 gl.bind_vertex_array(Some(&self.gpu.buffers.vao));
613
614 gl.delete_buffer(Some(&self.gpu.buffers.instance_cell));
616 gl.delete_buffer(Some(&self.gpu.buffers.instance_pos));
617
618 let current_size = (self.terminal_size.0 as i32, self.terminal_size.1 as i32);
620 let cell_data = self.resize_cell_grid(current_size, (cols, rows));
621 self.cells = cell_data;
622
623 let cell_pos = CellStatic::create_grid(cols, rows);
624
625 self.gpu.buffers.instance_cell = create_dynamic_instance_buffer(gl, &self.cells)?;
627 self.gpu.buffers.instance_pos = create_static_instance_buffer(gl, &cell_pos)?;
628
629 gl.bind_vertex_array(None);
631
632 self.terminal_size = (cols as u16, rows as u16);
633
634 Ok(())
635 }
636
637 pub fn recreate_resources(&mut self, gl: &WebGl2RenderingContext) -> Result<(), Error> {
654 let cell_size = self.effective_cell_size();
655 let (cols, rows) = (self.terminal_size.0 as i32, self.terminal_size.1 as i32);
656 let cell_pos = CellStatic::create_grid(cols, rows);
657
658 self.gpu = GpuResources::new(gl, &cell_pos, &self.cells, cell_size)?;
660
661 self.upload_ubo_data(gl);
663
664 self.cells_pending_flush = true;
666
667 Ok(())
668 }
669
670 pub fn recreate_atlas_texture(&mut self, gl: &WebGl2RenderingContext) -> Result<(), Error> {
675 self.atlas.recreate_texture(gl)
676 }
677
678 pub fn base_glyph_id(&self, symbol: &str) -> Option<u16> {
680 self.atlas.get_base_glyph_id(symbol)
681 }
682
683 fn fallback_symbol(&self) -> Option<CompactString> {
684 self.atlas.get_symbol(self.fallback_glyph)
685 }
686
687 fn clear_stale_selection(&self) {
688 if let Some(query) = self.selection_tracker().get_query()
689 && let Some(hash) = query.content_hash
690 && hash != self.hash_cells(query)
691 {
692 self.selection.clear();
693 }
694 }
695
696 fn resize_cell_grid(&self, old_size: (i32, i32), new_size: (i32, i32)) -> Vec<CellDynamic> {
697 let empty_cell = CellDynamic::new(self.atlas.space_glyph_id(), 0xFFFFFF, 0x000000);
698
699 let new_len = new_size.0 * new_size.1;
700 let mut new_cells = Vec::with_capacity(new_len as usize);
701 for _ in 0..new_len {
702 new_cells.push(empty_cell);
703 }
704
705 let cells = &self.cells;
706 for y in 0..min(old_size.1, new_size.1) {
707 for x in 0..min(old_size.0, new_size.0) {
708 let new_idx = (y * new_size.0 + x) as usize;
709 let old_idx = (y * old_size.0 + x) as usize;
710 new_cells[new_idx] = cells[old_idx];
711 }
712 }
713
714 new_cells
715 }
716}
717
718fn create_vao(gl: &WebGl2RenderingContext) -> Result<web_sys::WebGlVertexArrayObject, Error> {
719 gl.create_vertex_array()
720 .ok_or(Error::vertex_array_creation_failed())
721}
722
723fn setup_buffers(
724 gl: &WebGl2RenderingContext,
725 vao: web_sys::WebGlVertexArrayObject,
726 cell_pos: &[CellStatic],
727 cell_data: &[CellDynamic],
728 cell_size: (i32, i32),
729) -> Result<TerminalBuffers, Error> {
730 let (w, h) = (cell_size.0 as f32, cell_size.1 as f32);
731
732 #[rustfmt::skip]
733 let vertices = [
734 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 ];
740 let indices = [0, 1, 2, 0, 3, 1];
741
742 Ok(TerminalBuffers {
743 vao,
744 vertices: create_buffer_f32(gl, GL::ARRAY_BUFFER, &vertices, GL::STATIC_DRAW)?,
745 instance_pos: create_static_instance_buffer(gl, cell_pos)?,
746 instance_cell: create_dynamic_instance_buffer(gl, cell_data)?,
747 indices: create_buffer_u8(gl, GL::ELEMENT_ARRAY_BUFFER, &indices, GL::STATIC_DRAW)?,
748 })
749}
750
751fn create_buffer_u8(
752 gl: &WebGl2RenderingContext,
753 target: u32,
754 data: &[u8],
755 usage: u32,
756) -> Result<web_sys::WebGlBuffer, Error> {
757 let index_buf = gl
758 .create_buffer()
759 .ok_or(Error::buffer_creation_failed("vbo-u8"))?;
760 gl.bind_buffer(target, Some(&index_buf));
761
762 gl.buffer_data_with_u8_array(target, data, usage);
763
764 Ok(index_buf)
765}
766
767fn create_buffer_f32(
768 gl: &WebGl2RenderingContext,
769 target: u32,
770 data: &[f32],
771 usage: u32,
772) -> Result<web_sys::WebGlBuffer, Error> {
773 let buffer = gl
774 .create_buffer()
775 .ok_or(Error::buffer_creation_failed("vbo-f32"))?;
776
777 gl.bind_buffer(target, Some(&buffer));
778
779 unsafe {
780 let view = js_sys::Float32Array::view(data);
781 gl.buffer_data_with_array_buffer_view(target, &view, usage);
782 }
783
784 const STRIDE: i32 = (2 + 2) * 4; enable_vertex_attrib(gl, attrib::POS, 2, GL::FLOAT, 0, STRIDE);
787 enable_vertex_attrib(gl, attrib::UV, 2, GL::FLOAT, 8, STRIDE);
788
789 Ok(buffer)
790}
791
792fn create_static_instance_buffer(
793 gl: &WebGl2RenderingContext,
794 instance_data: &[CellStatic],
795) -> Result<web_sys::WebGlBuffer, Error> {
796 let instance_buf = gl
797 .create_buffer()
798 .ok_or(Error::buffer_creation_failed("static-instance-buffer"))?;
799
800 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&instance_buf));
801 buffer_upload_array(gl, GL::ARRAY_BUFFER, instance_data, GL::STATIC_DRAW);
802
803 let stride = size_of::<CellStatic>() as i32;
804 enable_vertex_attrib_array(gl, attrib::GRID_XY, 2, GL::UNSIGNED_SHORT, 0, stride);
805
806 Ok(instance_buf)
807}
808
809fn create_dynamic_instance_buffer(
810 gl: &WebGl2RenderingContext,
811 instance_data: &[CellDynamic],
812) -> Result<web_sys::WebGlBuffer, Error> {
813 let instance_buf = gl
814 .create_buffer()
815 .ok_or(Error::buffer_creation_failed("dynamic-instance-buffer"))?;
816
817 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&instance_buf));
818 buffer_upload_array(gl, GL::ARRAY_BUFFER, instance_data, GL::DYNAMIC_DRAW);
819
820 let stride = size_of::<CellDynamic>() as i32;
821
822 enable_vertex_attrib_array(
824 gl,
825 attrib::PACKED_DEPTH_FG_BG,
826 2,
827 GL::UNSIGNED_INT,
828 0,
829 stride,
830 );
831
832 Ok(instance_buf)
833}
834
835fn enable_vertex_attrib_array(
836 gl: &WebGl2RenderingContext,
837 index: u32,
838 size: i32,
839 type_: u32,
840 offset: i32,
841 stride: i32,
842) {
843 enable_vertex_attrib(gl, index, size, type_, offset, stride);
844 gl.vertex_attrib_divisor(index, 1);
845}
846
847fn enable_vertex_attrib(
848 gl: &WebGl2RenderingContext,
849 index: u32,
850 size: i32,
851 type_: u32,
852 offset: i32,
853 stride: i32,
854) {
855 gl.enable_vertex_attrib_array(index);
856 if type_ == GL::FLOAT {
857 gl.vertex_attrib_pointer_with_i32(index, size, type_, false, stride, offset);
858 } else {
859 gl.vertex_attrib_i_pointer_with_i32(index, size, type_, stride, offset);
860 }
861}
862
863impl Drawable for TerminalGrid {
864 fn prepare(&self, context: &mut RenderContext) {
865 let gl = context.gl;
866
867 self.gpu.shader.use_program(gl);
868
869 gl.bind_vertex_array(Some(&self.gpu.buffers.vao));
870
871 self.atlas.bind(gl, 0);
872 self.atlas.flush(gl).unwrap(); self.gpu.ubo_vertex.bind(context.gl);
874 self.gpu.ubo_fragment.bind(context.gl);
875 gl.uniform1i(Some(&self.gpu.sampler_loc), 0);
876 }
877
878 fn draw(&self, context: &mut RenderContext) {
879 let gl = context.gl;
880 let cell_count = self.cells.len() as i32;
881
882 gl.draw_elements_instanced_with_i32(GL::TRIANGLES, 6, GL::UNSIGNED_BYTE, 0, cell_count);
883 }
884
885 fn cleanup(&self, context: &mut RenderContext) {
886 let gl = context.gl;
887 gl.bind_vertex_array(None);
888 gl.bind_texture(GL::TEXTURE_2D_ARRAY, None);
889
890 self.gpu.ubo_vertex.unbind(gl);
891 self.gpu.ubo_fragment.unbind(gl);
892 }
893}
894
895#[derive(Debug, Copy, Clone)]
907pub struct CellData<'a> {
908 symbol: &'a str,
909 style_bits: u16,
910 fg: u32,
911 bg: u32,
912}
913
914impl<'a> CellData<'a> {
915 pub fn new(symbol: &'a str, style: FontStyle, effect: GlyphEffect, fg: u32, bg: u32) -> Self {
927 Self::new_with_style_bits(symbol, style.style_mask() | effect as u16, fg, bg)
928 }
929
930 pub fn new_with_style_bits(symbol: &'a str, style_bits: u16, fg: u32, bg: u32) -> Self {
954 debug_assert!(
956 0x81FF & style_bits == 0,
957 "Invalid style bits: {style_bits:#04x}"
958 );
959 Self { symbol, style_bits, fg, bg }
960 }
961}
962
963#[repr(C, align(4))]
984struct CellStatic {
985 pub grid_xy: [u16; 2],
987}
988
989#[derive(Debug, Clone, Copy, Hash)]
1017#[repr(C, align(4))]
1018pub struct CellDynamic {
1019 data: [u8; 8], }
1032
1033impl CellStatic {
1034 fn create_grid(cols: i32, rows: i32) -> Vec<Self> {
1035 debug_assert!(cols > 0 && cols < u16::MAX as i32, "cols: {cols}");
1036 debug_assert!(rows > 0 && rows < u16::MAX as i32, "rows: {rows}");
1037
1038 (0..rows)
1039 .flat_map(|row| (0..cols).map(move |col| (col, row)))
1040 .map(|(col, row)| Self { grid_xy: [col as u16, row as u16] })
1041 .collect()
1042 }
1043}
1044
1045impl CellDynamic {
1046 const GLYPH_STYLE_MASK: u16 =
1047 Glyph::BOLD_FLAG | Glyph::ITALIC_FLAG | Glyph::UNDERLINE_FLAG | Glyph::STRIKETHROUGH_FLAG;
1048
1049 #[inline]
1050 pub fn new(glyph_id: u16, fg: u32, bg: u32) -> Self {
1051 let mut data = [0; 8];
1052
1053 let glyph_id = glyph_id.to_le_bytes();
1055 data[0] = glyph_id[0];
1056 data[1] = glyph_id[1];
1057
1058 let fg = fg.to_le_bytes();
1059 data[2] = fg[2]; data[3] = fg[1]; data[4] = fg[0]; let bg = bg.to_le_bytes();
1064 data[5] = bg[2]; data[6] = bg[1]; data[7] = bg[0]; Self { data }
1069 }
1070
1071 pub fn style(&mut self, style_bits: u16) {
1073 let glyph_id = (self.glyph_id() & !Self::GLYPH_STYLE_MASK) | style_bits;
1074 self.data[..2].copy_from_slice(&glyph_id.to_le_bytes());
1075 }
1076
1077 pub fn flip_colors(&mut self) {
1079 let fg = [self.data[2], self.data[3], self.data[4]];
1081 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]; }
1088
1089 pub fn fg_color(&mut self, fg: u32) {
1091 let fg = fg.to_le_bytes();
1092 self.data[2] = fg[2]; self.data[3] = fg[1]; self.data[4] = fg[0]; }
1096
1097 pub fn bg_color(&mut self, bg: u32) {
1099 let bg = bg.to_le_bytes();
1100 self.data[5] = bg[2]; self.data[6] = bg[1]; self.data[7] = bg[0]; }
1104
1105 pub fn get_fg_color(&self) -> u32 {
1107 ((self.data[2] as u32) << 16) | ((self.data[3] as u32) << 8) | (self.data[4] as u32)
1109 }
1110
1111 pub fn get_bg_color(&self) -> u32 {
1113 ((self.data[5] as u32) << 16) | ((self.data[6] as u32) << 8) | (self.data[7] as u32)
1115 }
1116
1117 pub fn get_style(&self) -> u16 {
1119 self.glyph_id() & Self::GLYPH_STYLE_MASK
1120 }
1121
1122 pub fn is_emoji(&self) -> bool {
1124 self.glyph_id() & Glyph::EMOJI_FLAG != 0
1125 }
1126
1127 #[inline]
1128 fn glyph_id(&self) -> u16 {
1129 u16::from_le_bytes([self.data[0], self.data[1]])
1130 }
1131
1132 fn set_glyph_id(&mut self, glyph_id: u16) {
1133 let bytes = glyph_id.to_le_bytes();
1134 self.data[0] = bytes[0];
1135 self.data[1] = bytes[1];
1136 }
1137}
1138
1139#[repr(C, align(16))] struct CellVertexUbo {
1141 pub projection: [f32; 16], pub cell_size: [f32; 2], pub _padding: [f32; 2],
1144}
1145
1146#[repr(C, align(16))] struct CellFragmentUbo {
1148 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,
1155}
1156
1157impl CellVertexUbo {
1158 pub const BINDING_POINT: u32 = 0;
1159
1160 fn new(canvas_size: (i32, i32), cell_size: (i32, i32)) -> Self {
1161 let projection =
1162 Mat4::orthographic_from_size(canvas_size.0 as f32, canvas_size.1 as f32).data;
1163 Self {
1164 projection,
1165 cell_size: [cell_size.0 as f32, cell_size.1 as f32],
1166 _padding: [0.0; 2], }
1168 }
1169}
1170
1171impl CellFragmentUbo {
1172 pub const BINDING_POINT: u32 = 1;
1173
1174 fn new(atlas: &FontAtlas) -> Self {
1175 let texture_cell_size = atlas.texture_cell_size();
1177 let underline = atlas.underline();
1178 let strikethrough = atlas.strikethrough();
1179
1180 Self {
1181 padding_frac: [
1182 FontAtlasData::PADDING as f32 / texture_cell_size.0 as f32,
1183 FontAtlasData::PADDING as f32 / texture_cell_size.1 as f32,
1184 ],
1185 underline_pos: underline.position,
1186 underline_thickness: underline.thickness,
1187 strikethrough_pos: strikethrough.position,
1188 strikethrough_thickness: strikethrough.thickness,
1189 texture_lookup_mask: atlas.base_lookup_mask(),
1190 _padding: 0.0, }
1192 }
1193}
1194
1195fn create_terminal_cell_data(cols: i32, rows: i32, fill_glyph: u16) -> Vec<CellDynamic> {
1196 (0..cols * rows)
1197 .map(|i| CellDynamic::new(fill_glyph, 0x00ff_ffff, 0x0000_0000))
1198 .collect()
1199}
1200
1201mod attrib {
1202 pub const POS: u32 = 0;
1203 pub const UV: u32 = 1;
1204
1205 pub const GRID_XY: u32 = 2;
1206 pub const PACKED_DEPTH_FG_BG: u32 = 3;
1207}