1use std::{borrow::Cow, cmp::min, fmt::Debug, ops::Index};
2
3use beamterm_data::{FontAtlasData, FontStyle, GlyphEffect};
4use compact_str::{CompactString, CompactStringExt};
5use web_sys::{console, WebGl2RenderingContext};
6
7use crate::{
8 error::Error,
9 gl::{
10 buffer_upload_array, selection::SelectionTracker, ubo::UniformBufferObject, CellIterator,
11 Drawable, FontAtlas, RenderContext, ShaderProgram, GL,
12 },
13 mat4::Mat4,
14};
15
16#[derive(Debug)]
23pub struct TerminalGrid {
24 shader: ShaderProgram,
26 cells: Vec<CellDynamic>,
28 terminal_size: (u16, u16),
30 canvas_size_px: (i32, i32),
32 buffers: TerminalBuffers,
34 ubo_vertex: UniformBufferObject,
36 ubo_fragment: UniformBufferObject,
38 atlas: FontAtlas,
40 sampler_loc: web_sys::WebGlUniformLocation,
42 fallback_glyph: u16,
44 selection: SelectionTracker,
46}
47
48#[derive(Debug)]
49struct TerminalBuffers {
50 vao: web_sys::WebGlVertexArrayObject,
51 vertices: web_sys::WebGlBuffer,
52 instance_pos: web_sys::WebGlBuffer,
53 instance_cell: web_sys::WebGlBuffer,
54 indices: web_sys::WebGlBuffer,
55}
56
57impl TerminalBuffers {
58 fn upload_instance_data<T>(&self, gl: &WebGl2RenderingContext, cell_data: &[T]) {
59 gl.bind_vertex_array(Some(&self.vao));
60 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&self.instance_cell));
61
62 buffer_upload_array(gl, GL::ARRAY_BUFFER, cell_data, GL::DYNAMIC_DRAW);
63
64 gl.bind_vertex_array(None);
65 }
66}
67
68impl TerminalGrid {
69 const FRAGMENT_GLSL: &'static str = include_str!("../shaders/cell.frag");
70 const VERTEX_GLSL: &'static str = include_str!("../shaders/cell.vert");
71
72 pub fn new(
73 gl: &WebGl2RenderingContext,
74 atlas: FontAtlas,
75 screen_size: (i32, i32),
76 ) -> Result<Self, Error> {
77 let vao = create_vao(gl)?;
79 gl.bind_vertex_array(Some(&vao));
80
81 let cell_size = atlas.cell_size();
83 let (cols, rows) = (screen_size.0 / cell_size.0, screen_size.1 / cell_size.1);
84
85 let cell_data = create_terminal_cell_data(cols, rows, &[' ' as u16]);
88 let cell_pos = CellStatic::create_grid(cols, rows);
89 let buffers = setup_buffers(gl, vao, &cell_pos, &cell_data, cell_size)?;
90
91 gl.bind_vertex_array(None);
93
94 let shader = ShaderProgram::create(gl, Self::VERTEX_GLSL, Self::FRAGMENT_GLSL)?;
96 shader.use_program(gl);
97
98 let ubo_vertex = UniformBufferObject::new(gl, CellVertexUbo::BINDING_POINT)?;
99 ubo_vertex.bind_to_shader(gl, &shader, "VertUbo")?;
100 let ubo_fragment = UniformBufferObject::new(gl, CellFragmentUbo::BINDING_POINT)?;
101 ubo_fragment.bind_to_shader(gl, &shader, "FragUbo")?;
102
103 let sampler_loc = gl
104 .get_uniform_location(&shader.program, "u_sampler")
105 .ok_or(Error::uniform_location_failed("u_sampler"))?;
106
107 console::log_2(&"terminal cells".into(), &cell_data.len().into());
108
109 let (cols, rows) = (screen_size.0 / cell_size.0, screen_size.1 / cell_size.1);
110 console::log_1(&format!("terminal size {cols}x{rows}").into());
111 let grid = Self {
112 shader,
113 terminal_size: (cols as u16, rows as u16),
114 canvas_size_px: screen_size,
115 cells: cell_data,
116 buffers,
117 ubo_vertex,
118 ubo_fragment,
119 atlas,
120 sampler_loc,
121 fallback_glyph: ' ' as u16,
122 selection: SelectionTracker::new(),
123 };
124
125 grid.upload_ubo_data(gl);
126
127 Ok(grid)
128 }
129
130 pub fn set_fallback_glyph(&mut self, fallback: &str) {
132 self.fallback_glyph = self.atlas.get_base_glyph_id(fallback).unwrap_or(' ' as u16);
133 }
134
135 pub fn atlas(&self) -> &FontAtlas {
137 &self.atlas
138 }
139
140 pub fn cell_size(&self) -> (i32, i32) {
142 self.atlas.cell_size()
143 }
144
145 pub fn terminal_size(&self) -> (u16, u16) {
147 self.terminal_size
148 }
149
150 pub(crate) fn selection_tracker(&self) -> SelectionTracker {
152 self.selection.clone()
153 }
154
155 pub(super) fn get_symbols(&self, selection: CellIterator) -> CompactString {
157 let (cols, rows) = self.terminal_size;
158 let mut text = CompactString::new("");
159
160 for (idx, require_newline_after) in selection {
161 text.push_str(&self.get_cell_symbol(idx));
162 if require_newline_after {
163 text.push('\n'); }
165 }
166
167 text
168 }
169
170 fn get_cell_symbol(&self, idx: usize) -> Cow<str> {
171 if idx < self.cells.len() {
172 let glyph_id = self.cells[idx].glyph_id();
173 self.atlas.get_symbol(glyph_id).unwrap_or_else(|| self.fallback_symbol())
174 } else {
175 self.fallback_symbol()
176 }
177 }
178
179 fn upload_ubo_data(&self, gl: &WebGl2RenderingContext) {
188 let vertex_ubo = CellVertexUbo::new(self.canvas_size_px, self.cell_size());
189 self.ubo_vertex.upload_data(gl, &vertex_ubo);
190
191 let fragment_ubo = CellFragmentUbo::new(&self.atlas);
192 self.ubo_fragment.upload_data(gl, &fragment_ubo);
193 }
194
195 pub fn cell_count(&self) -> usize {
197 self.cells.len()
198 }
199
200 pub fn update_cells<'a>(
214 &mut self,
215 gl: &WebGl2RenderingContext,
216 cells: impl Iterator<Item = CellData<'a>>,
217 ) -> Result<(), Error> {
218 let atlas = &self.atlas;
220
221 let fallback_glyph = self.fallback_glyph;
222 self.cells.iter_mut().zip(cells).for_each(|(cell, data)| {
223 let glyph_id = atlas.get_base_glyph_id(data.symbol).unwrap_or(fallback_glyph);
224
225 *cell = CellDynamic::new(glyph_id | data.style_bits, data.fg, data.bg);
226 });
227
228 self.flush_cells(gl)?;
229
230 Ok(())
231 }
232
233 pub(crate) fn update_cells_by_position<'a>(
234 &mut self,
235 gl: &WebGl2RenderingContext,
236 cells: impl Iterator<Item = (u16, u16, CellData<'a>)>,
237 ) -> Result<(), Error> {
238 let atlas = &self.atlas;
240
241 let cell_count = self.cells.len();
242 let fallback_glyph = self.fallback_glyph;
243 let w = self.terminal_size.0 as usize;
244 cells
245 .map(|(x, y, cell)| (w * y as usize + x as usize, cell))
246 .filter(|(idx, _)| *idx < cell_count)
247 .for_each(|(idx, cell)| {
248 let glyph_id = atlas.get_base_glyph_id(cell.symbol).unwrap_or(fallback_glyph);
249 self.cells[idx] = CellDynamic::new(glyph_id | cell.style_bits, cell.fg, cell.bg);
250 });
251
252 self.flush_cells(gl)?;
253
254 Ok(())
255 }
256
257 pub(crate) fn update_cell(&mut self, x: u16, y: u16, cell_data: CellData) {
258 let (cols, _) = self.terminal_size;
259 let idx = y as usize * cols as usize + x as usize;
260 self.update_cell_by_index(idx, cell_data);
261 }
262
263 pub(crate) fn update_cell_by_index(&mut self, idx: usize, cell_data: CellData) {
264 if idx >= self.cells.len() {
265 return;
266 }
267
268 let atlas = &self.atlas;
269 let fallback_glyph = self.fallback_glyph;
270 let glyph_id = atlas.get_base_glyph_id(cell_data.symbol).unwrap_or(fallback_glyph);
271
272 self.cells[idx] =
273 CellDynamic::new(glyph_id | cell_data.style_bits, cell_data.fg, cell_data.bg);
274 }
275
276 pub(crate) fn flush_cells(&mut self, gl: &WebGl2RenderingContext) -> Result<(), Error> {
278 self.flip_selected_cell_colors();
282
283 self.buffers.upload_instance_data(gl, &self.cells);
284
285 self.flip_selected_cell_colors();
288
289 Ok(())
290 }
291
292 fn flip_selected_cell_colors(&mut self) {
293 if let Some(iter) = self.selected_cells_iter() {
294 iter.for_each(|(idx, _)| self.cells[idx].flip_colors());
295 }
296 }
297
298 fn selected_cells_iter(&self) -> Option<CellIterator> {
299 self.selection
300 .get_query()
301 .and_then(|query| query.range())
302 .map(|(start, end)| self.cell_iter(start, end, self.selection.mode()))
303 }
304
305 fn flip_cell_colors(&mut self, x: u16, y: u16) {
306 let (cols, _) = self.terminal_size;
307 let idx = y as usize * cols as usize + x as usize;
308 if idx < self.cells.len() {
309 self.cells[idx].flip_colors();
310 }
311 }
312
313 pub fn resize(
329 &mut self,
330 gl: &WebGl2RenderingContext,
331 canvas_size: (i32, i32),
332 ) -> Result<(), Error> {
333 self.canvas_size_px = canvas_size;
334
335 self.upload_ubo_data(gl);
337
338 let cell_size = self.atlas.cell_size();
339 let cols = canvas_size.0 / cell_size.0;
340 let rows = canvas_size.1 / cell_size.1;
341 if self.terminal_size == (cols as u16, rows as u16) {
342 return Ok(()); }
344
345 gl.bind_vertex_array(Some(&self.buffers.vao));
347
348 gl.delete_buffer(Some(&self.buffers.instance_cell));
350 gl.delete_buffer(Some(&self.buffers.instance_pos));
351
352 let current_size = (self.terminal_size.0 as i32, self.terminal_size.1 as i32);
354 let cell_data = resize_cell_grid(&self.cells, current_size, (cols, rows));
355 self.cells = cell_data;
356
357 let cell_pos = CellStatic::create_grid(cols, rows);
358
359 self.buffers.instance_cell = create_dynamic_instance_buffer(gl, &self.cells)?;
361 self.buffers.instance_pos = create_static_instance_buffer(gl, &cell_pos)?;
362
363 gl.bind_vertex_array(None);
365
366 self.terminal_size = (cols as u16, rows as u16);
367
368 Ok(())
369 }
370
371 pub fn base_glyph_id(&self, symbol: &str) -> Option<u16> {
373 self.atlas.get_base_glyph_id(symbol)
374 }
375
376 fn fallback_symbol(&self) -> Cow<str> {
377 self.atlas.get_symbol(self.fallback_glyph).unwrap_or(Cow::Borrowed(" "))
378 }
379
380 fn fill_glyphs(atlas: &FontAtlas) -> Vec<u16> {
381 [
382 ("🤫", FontStyle::Normal),
383 ("🙌", FontStyle::Normal),
384 ("n", FontStyle::Normal),
385 ("o", FontStyle::Normal),
386 ("r", FontStyle::Normal),
387 ("m", FontStyle::Normal),
388 ("a", FontStyle::Normal),
389 ("l", FontStyle::Normal),
390 ("b", FontStyle::Bold),
391 ("o", FontStyle::Bold),
392 ("l", FontStyle::Bold),
393 ("d", FontStyle::Bold),
394 ("i", FontStyle::Italic),
395 ("t", FontStyle::Italic),
396 ("a", FontStyle::Italic),
397 ("l", FontStyle::Italic),
398 ("i", FontStyle::Italic),
399 ("c", FontStyle::Italic),
400 ("b", FontStyle::BoldItalic),
401 ("-", FontStyle::BoldItalic),
402 ("i", FontStyle::BoldItalic),
403 ("t", FontStyle::BoldItalic),
404 ("a", FontStyle::BoldItalic),
405 ("l", FontStyle::BoldItalic),
406 ("i", FontStyle::BoldItalic),
407 ("c", FontStyle::BoldItalic),
408 ("🤪", FontStyle::Normal),
409 ("🤩", FontStyle::Normal),
410 ]
411 .into_iter()
412 .map(|(symbol, style)| atlas.get_base_glyph_id(symbol).map(|g| g | style as u16))
413 .map(|g| g.unwrap_or(' ' as u16))
414 .collect()
415 }
416}
417
418fn resize_cell_grid(
419 cells: &[CellDynamic],
420 old_size: (i32, i32),
421 new_size: (i32, i32),
422) -> Vec<CellDynamic> {
423 let new_len = new_size.0 * new_size.1;
424
425 let mut new_cells = Vec::with_capacity(new_len as usize);
426 for _ in 0..new_len {
427 new_cells.push(CellDynamic::new(' ' as u16, 0xFFFFFF, 0x000000));
428 }
429
430 for y in 0..min(old_size.1, new_size.1) {
431 for x in 0..min(old_size.0, new_size.0) {
432 let new_idx = (y * new_size.0 + x) as usize;
433 let old_idx = (y * old_size.0 + x) as usize;
434 new_cells[new_idx] = cells[old_idx];
435 }
436 }
437
438 new_cells
439}
440
441fn create_vao(gl: &WebGl2RenderingContext) -> Result<web_sys::WebGlVertexArrayObject, Error> {
442 gl.create_vertex_array().ok_or(Error::vertex_array_creation_failed())
443}
444
445fn setup_buffers(
446 gl: &WebGl2RenderingContext,
447 vao: web_sys::WebGlVertexArrayObject,
448 cell_pos: &[CellStatic],
449 cell_data: &[CellDynamic],
450 cell_size: (i32, i32),
451) -> Result<TerminalBuffers, Error> {
452 let (w, h) = (cell_size.0 as f32, cell_size.1 as f32);
453
454 let overlap = 0.0; #[rustfmt::skip]
457 let vertices = [
458 w + overlap, -overlap, 1.0, 0.0, -overlap, h + overlap, 0.0, 1.0, w + overlap, h + overlap, 1.0, 1.0, -overlap, -overlap, 0.0, 0.0 ];
464 let indices = [0, 1, 2, 0, 3, 1];
465
466 Ok(TerminalBuffers {
467 vao,
468 vertices: create_buffer_f32(gl, GL::ARRAY_BUFFER, &vertices, GL::STATIC_DRAW)?,
469 instance_pos: create_static_instance_buffer(gl, cell_pos)?,
470 instance_cell: create_dynamic_instance_buffer(gl, cell_data)?,
471 indices: create_buffer_u8(gl, GL::ELEMENT_ARRAY_BUFFER, &indices, GL::STATIC_DRAW)?,
472 })
473}
474
475fn create_buffer_u8(
476 gl: &WebGl2RenderingContext,
477 target: u32,
478 data: &[u8],
479 usage: u32,
480) -> Result<web_sys::WebGlBuffer, Error> {
481 let index_buf = gl.create_buffer().ok_or(Error::buffer_creation_failed("vbo-u8"))?;
482 gl.bind_buffer(target, Some(&index_buf));
483
484 gl.buffer_data_with_u8_array(target, data, usage);
485
486 Ok(index_buf)
487}
488
489fn create_buffer_f32(
490 gl: &WebGl2RenderingContext,
491 target: u32,
492 data: &[f32],
493 usage: u32,
494) -> Result<web_sys::WebGlBuffer, Error> {
495 let buffer = gl.create_buffer().ok_or(Error::buffer_creation_failed("vbo-f32"))?;
496
497 gl.bind_buffer(target, Some(&buffer));
498
499 unsafe {
500 let view = js_sys::Float32Array::view(data);
501 gl.buffer_data_with_array_buffer_view(target, &view, usage);
502 }
503
504 const STRIDE: i32 = (2 + 2) * 4; enable_vertex_attrib(gl, attrib::POS, 2, GL::FLOAT, 0, STRIDE);
507 enable_vertex_attrib(gl, attrib::UV, 2, GL::FLOAT, 8, STRIDE);
508
509 Ok(buffer)
510}
511
512fn create_static_instance_buffer(
513 gl: &WebGl2RenderingContext,
514 instance_data: &[CellStatic],
515) -> Result<web_sys::WebGlBuffer, Error> {
516 let instance_buf = gl
517 .create_buffer()
518 .ok_or(Error::buffer_creation_failed("static-instance-buffer"))?;
519
520 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&instance_buf));
521 buffer_upload_array(gl, GL::ARRAY_BUFFER, instance_data, GL::STATIC_DRAW);
522
523 let stride = size_of::<CellStatic>() as i32;
524 enable_vertex_attrib_array(gl, attrib::GRID_XY, 2, GL::UNSIGNED_SHORT, 0, stride);
525
526 Ok(instance_buf)
527}
528
529fn create_dynamic_instance_buffer(
530 gl: &WebGl2RenderingContext,
531 instance_data: &[CellDynamic],
532) -> Result<web_sys::WebGlBuffer, Error> {
533 let instance_buf = gl
534 .create_buffer()
535 .ok_or(Error::buffer_creation_failed("dynamic-instance-buffer"))?;
536
537 gl.bind_buffer(GL::ARRAY_BUFFER, Some(&instance_buf));
538 buffer_upload_array(gl, GL::ARRAY_BUFFER, instance_data, GL::DYNAMIC_DRAW);
539
540 let stride = size_of::<CellDynamic>() as i32;
541
542 enable_vertex_attrib_array(gl, attrib::PACKED_DEPTH_FG_BG, 2, GL::UNSIGNED_INT, 0, stride);
544
545 Ok(instance_buf)
546}
547
548fn enable_vertex_attrib_array(
549 gl: &WebGl2RenderingContext,
550 index: u32,
551 size: i32,
552 type_: u32,
553 offset: i32,
554 stride: i32,
555) {
556 enable_vertex_attrib(gl, index, size, type_, offset, stride);
557 gl.vertex_attrib_divisor(index, 1);
558}
559
560fn enable_vertex_attrib(
561 gl: &WebGl2RenderingContext,
562 index: u32,
563 size: i32,
564 type_: u32,
565 offset: i32,
566 stride: i32,
567) {
568 gl.enable_vertex_attrib_array(index);
569 if type_ == GL::FLOAT {
570 gl.vertex_attrib_pointer_with_i32(index, size, type_, false, stride, offset);
571 } else {
572 gl.vertex_attrib_i_pointer_with_i32(index, size, type_, stride, offset);
573 }
574}
575
576impl Drawable for TerminalGrid {
577 fn prepare(&self, context: &mut RenderContext) {
578 let gl = context.gl;
579
580 self.shader.use_program(gl);
581
582 gl.bind_vertex_array(Some(&self.buffers.vao));
583
584 self.atlas.bind(gl, 0);
585 self.ubo_vertex.bind(context.gl);
586 self.ubo_fragment.bind(context.gl);
587 gl.uniform1i(Some(&self.sampler_loc), 0);
588 }
589
590 fn draw(&self, context: &mut RenderContext) {
591 let gl = context.gl;
592 let cell_count = self.cells.len() as i32;
593 gl.draw_elements_instanced_with_i32(GL::TRIANGLES, 6, GL::UNSIGNED_BYTE, 0, cell_count);
594 }
595
596 fn cleanup(&self, context: &mut RenderContext) {
597 let gl = context.gl;
598 gl.bind_vertex_array(None);
599 gl.bind_texture(GL::TEXTURE_2D_ARRAY, None);
600
601 self.ubo_vertex.unbind(gl);
602 self.ubo_fragment.unbind(gl);
603 }
604}
605
606#[derive(Debug, Copy, Clone)]
618pub struct CellData<'a> {
619 symbol: &'a str,
621 style_bits: u16,
622 fg: u32,
623 bg: u32,
624}
625
626impl<'a> CellData<'a> {
627 pub fn new(symbol: &'a str, style: FontStyle, effect: GlyphEffect, fg: u32, bg: u32) -> Self {
639 Self::new_with_style_bits(symbol, style.style_mask() | effect as u16, fg, bg)
640 }
641
642 pub fn new_with_style_bits(symbol: &'a str, style_bits: u16, fg: u32, bg: u32) -> Self {
666 debug_assert!(0x81FF & style_bits == 0, "Invalid style bits: {style_bits:#04x}");
668 Self { symbol, style_bits, fg, bg }
669 }
670}
671
672#[repr(C, align(4))]
693struct CellStatic {
694 pub grid_xy: [u16; 2],
696}
697
698#[derive(Debug, Clone, Copy)]
726#[repr(C, align(4))]
727struct CellDynamic {
728 pub data: [u8; 8], }
741
742impl CellStatic {
743 fn create_grid(cols: i32, rows: i32) -> Vec<Self> {
744 debug_assert!(cols > 0 && cols < u16::MAX as i32, "cols: {cols}");
745 debug_assert!(rows > 0 && rows < u16::MAX as i32, "rows: {rows}");
746
747 (0..rows)
748 .flat_map(|row| (0..cols).map(move |col| (col, row)))
749 .map(|(col, row)| Self { grid_xy: [col as u16, row as u16] })
750 .collect()
751 }
752}
753
754impl CellDynamic {
755 fn new(glyph_id: u16, fg: u32, bg: u32) -> Self {
756 let mut data = [0; 8];
757
758 let glyph_id = glyph_id.to_le_bytes();
760 data[0] = glyph_id[0];
761 data[1] = glyph_id[1];
762
763 let fg = fg.to_le_bytes();
764 data[2] = fg[2]; data[3] = fg[1]; data[4] = fg[0]; let bg = bg.to_le_bytes();
769 data[5] = bg[2]; data[6] = bg[1]; data[7] = bg[0]; Self { data }
774 }
775
776 fn flip_colors(&mut self) {
777 let fg = [self.data[2], self.data[3], self.data[4]];
779 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]; }
786
787 fn glyph_id(&self) -> u16 {
788 u16::from_le_bytes([self.data[0], self.data[1]])
789 }
790}
791
792#[repr(C, align(16))] struct CellVertexUbo {
794 pub projection: [f32; 16], pub cell_size: [f32; 2], pub _padding: [f32; 2],
797}
798
799#[repr(C, align(16))] struct CellFragmentUbo {
801 pub padding_frac: [f32; 2], pub underline_pos: f32, pub underline_thickness: f32, pub strikethrough_pos: f32, pub strikethrough_thickness: f32, pub _padding: [f32; 2],
807}
808
809impl CellVertexUbo {
810 pub const BINDING_POINT: u32 = 0;
811
812 fn new(canvas_size: (i32, i32), cell_size: (i32, i32)) -> Self {
813 let projection =
814 Mat4::orthographic_from_size(canvas_size.0 as f32, canvas_size.1 as f32).data;
815 Self {
816 projection,
817 cell_size: [cell_size.0 as f32, cell_size.1 as f32],
818 _padding: [0.0; 2], }
820 }
821}
822
823impl CellFragmentUbo {
824 pub const BINDING_POINT: u32 = 1;
825
826 fn new(atlas: &FontAtlas) -> Self {
827 let cell_size = atlas.cell_size();
828 let underline = atlas.underline();
829 let strikethrough = atlas.strikethrough();
830 Self {
831 padding_frac: [
832 FontAtlasData::PADDING as f32 / cell_size.0 as f32,
833 FontAtlasData::PADDING as f32 / cell_size.1 as f32,
834 ],
835 underline_pos: underline.position,
836 underline_thickness: underline.thickness,
837 strikethrough_pos: strikethrough.position,
838 strikethrough_thickness: strikethrough.thickness,
839 _padding: [0.0; 2], }
841 }
842}
843
844fn create_terminal_cell_data(cols: i32, rows: i32, fill_glyph: &[u16]) -> Vec<CellDynamic> {
845 let glyph_len = fill_glyph.len();
846 (0..cols * rows)
847 .map(|i| CellDynamic::new(fill_glyph[i as usize % glyph_len], 0x00ff_ffff, 0x0000_0000))
848 .collect()
849}
850
851mod attrib {
852 pub const POS: u32 = 0;
853 pub const UV: u32 = 1;
854
855 pub const GRID_XY: u32 = 2;
856 pub const PACKED_DEPTH_FG_BG: u32 = 3;
857}