1use beamterm_data::FontAtlasData;
2use glow::HasContext;
3
4use crate::error::Error;
5
6const GLYPHS_PER_LAYER: i32 = 32;
8
9#[derive(Debug, Clone)]
11pub struct RasterizedGlyph {
12 pub pixels: Vec<u8>,
13 pub width: u32,
14 pub height: u32,
15}
16
17impl RasterizedGlyph {
18 pub fn new(pixels: Vec<u8>, width: u32, height: u32) -> Self {
19 Self { pixels, width, height }
20 }
21
22 pub fn is_empty(&self) -> bool {
24 self.pixels
25 .iter()
26 .skip(3)
27 .step_by(4)
28 .all(|&a| a == 0)
29 }
30}
31
32#[derive(Debug)]
33pub struct Texture {
34 gl_texture: glow::Texture,
35 pub format: u32,
36 dimensions: (i32, i32, i32),
38}
39
40impl Texture {
41 pub fn from_font_atlas_data(
42 gl: &glow::Context,
43 format: u32,
44 atlas: &FontAtlasData,
45 ) -> Result<Self, Error> {
46 let (width, height, layers) = atlas.texture_dimensions;
47
48 let gl_texture = unsafe { gl.create_texture() }.map_err(Error::texture_creation_failed)?;
50 unsafe {
51 gl.bind_texture(glow::TEXTURE_2D_ARRAY, Some(gl_texture));
52 gl.tex_storage_3d(
53 glow::TEXTURE_2D_ARRAY,
54 1,
55 glow::RGBA8,
56 width,
57 height,
58 layers,
59 );
60
61 gl.tex_sub_image_3d(
63 glow::TEXTURE_2D_ARRAY,
64 0, 0,
66 0,
67 0, width,
69 height,
70 layers, glow::RGBA,
72 glow::UNSIGNED_BYTE,
73 glow::PixelUnpackData::Slice(Some(&atlas.texture_data)),
74 );
75 }
76
77 Self::setup_sampling(gl);
78
79 let (width, height, layers) = atlas.texture_dimensions;
80 Ok(Self {
81 gl_texture,
82 format,
83 dimensions: (width, height, layers),
84 })
85 }
86
87 pub fn for_dynamic_font_atlas(
102 gl: &glow::Context,
103 format: u32,
104 cell_size: (i32, i32),
105 initial_layers: i32,
106 ) -> Result<Self, Error> {
107 let (cell_w, cell_h) = cell_size;
108
109 let width = cell_w;
113 let height = cell_h * GLYPHS_PER_LAYER;
114
115 let gl_texture = unsafe { gl.create_texture() }.map_err(Error::texture_creation_failed)?;
116
117 unsafe {
118 gl.bind_texture(glow::TEXTURE_2D_ARRAY, Some(gl_texture));
119 gl.tex_storage_3d(
120 glow::TEXTURE_2D_ARRAY,
121 1, glow::RGBA8,
123 width,
124 height,
125 initial_layers,
126 );
127
128 let empty_data = vec![0u8; (width * height * initial_layers * 4) as usize];
132 gl.tex_sub_image_3d(
133 glow::TEXTURE_2D_ARRAY,
134 0, 0, 0, 0, width,
139 height,
140 initial_layers, glow::RGBA,
142 glow::UNSIGNED_BYTE,
143 glow::PixelUnpackData::Slice(Some(&empty_data)),
144 );
145 }
146
147 Self::setup_sampling(gl);
148
149 Ok(Self {
150 gl_texture,
151 format,
152 dimensions: (width, height, initial_layers),
153 })
154 }
155
156 pub fn upload_glyph(
160 &self,
161 gl: &glow::Context,
162 glyph_id: u16,
163 padded_cell_size: (i32, i32),
164 rasterized: &RasterizedGlyph,
165 ) -> Result<(), Error> {
166 let (_cell_w, cell_h) = padded_cell_size;
167
168 let layer = (glyph_id as i32) / GLYPHS_PER_LAYER;
170 let glyph_index = (glyph_id as i32) % GLYPHS_PER_LAYER;
171 let y_offset = glyph_index * cell_h;
172
173 if layer >= self.dimensions.2 {
174 return Err(Error::texture_creation_failed(format_args!(
175 "glyph id {glyph_id} exceeds texture layer count {}",
176 self.dimensions.2
177 )));
178 }
179
180 unsafe {
181 gl.bind_texture(glow::TEXTURE_2D_ARRAY, Some(self.gl_texture));
182
183 gl.tex_sub_image_3d(
184 glow::TEXTURE_2D_ARRAY,
185 0, 0,
187 y_offset,
188 layer, rasterized.width as i32,
190 rasterized.height as i32,
191 1, glow::RGBA,
193 glow::UNSIGNED_BYTE,
194 glow::PixelUnpackData::Slice(Some(&rasterized.pixels)),
195 );
196 }
197
198 Ok(())
199 }
200
201 pub fn dimensions(&self) -> (i32, i32, i32) {
203 self.dimensions
204 }
205
206 pub fn bind(&self, gl: &glow::Context) {
207 unsafe {
208 gl.bind_texture(glow::TEXTURE_2D_ARRAY, Some(self.gl_texture));
209 }
210 }
211
212 pub fn delete(&self, gl: &glow::Context) {
213 unsafe {
214 gl.delete_texture(self.gl_texture);
215 }
216 }
217
218 fn setup_sampling(gl: &glow::Context) {
219 unsafe {
220 gl.tex_parameter_i32(
221 glow::TEXTURE_2D_ARRAY,
222 glow::TEXTURE_MIN_FILTER,
223 glow::NEAREST as i32,
224 );
225 gl.tex_parameter_i32(
226 glow::TEXTURE_2D_ARRAY,
227 glow::TEXTURE_MAG_FILTER,
228 glow::NEAREST as i32,
229 );
230 gl.tex_parameter_i32(
231 glow::TEXTURE_2D_ARRAY,
232 glow::TEXTURE_WRAP_S,
233 glow::CLAMP_TO_EDGE as i32,
234 );
235 gl.tex_parameter_i32(
236 glow::TEXTURE_2D_ARRAY,
237 glow::TEXTURE_WRAP_T,
238 glow::CLAMP_TO_EDGE as i32,
239 );
240 }
241 }
242}