1use crate::{GlTexture, InitError, InitResult};
4use dear_imgui_rs::{OwnedTextureData, TextureData, TextureFormat, TextureId, TextureStatus};
5use glow::{Context, HasContext};
6use std::collections::HashMap;
7
8pub(crate) fn gl_texture_size_i32(dimension: &'static str, value: u32) -> InitResult<i32> {
9 i32::try_from(value).map_err(|_| InitError::TextureDimensionOutOfRange { dimension, value })
10}
11
12pub(crate) fn checked_gl_texture_size(width: u32, height: u32) -> InitResult<(i32, i32)> {
13 Ok((
14 gl_texture_size_i32("width", width)?,
15 gl_texture_size_i32("height", height)?,
16 ))
17}
18
19pub trait TextureMap {
21 fn get(&self, texture_id: TextureId) -> Option<GlTexture>;
23
24 fn set(&mut self, texture_id: TextureId, gl_texture: GlTexture);
26
27 fn remove(&mut self, texture_id: TextureId) -> Option<GlTexture>;
29
30 fn clear(&mut self);
32
33 fn register_texture(
35 &mut self,
36 gl_texture: GlTexture,
37 width: u32,
38 height: u32,
39 format: TextureFormat,
40 ) -> TextureId;
41
42 fn update_texture(
44 &mut self,
45 texture_id: TextureId,
46 gl_texture: GlTexture,
47 width: u32,
48 height: u32,
49 );
50
51 fn get_texture_data(&self, texture_id: TextureId) -> Option<&TextureData>;
53
54 fn get_texture_data_mut(&mut self, texture_id: TextureId) -> Option<&mut TextureData>;
56}
57
58#[derive(Default)]
60pub struct SimpleTextureMap {
61 textures: HashMap<TextureId, GlTexture>,
62 texture_data: HashMap<TextureId, OwnedTextureData>,
63 next_id: usize,
64}
65
66impl TextureMap for SimpleTextureMap {
67 fn get(&self, texture_id: TextureId) -> Option<GlTexture> {
68 self.textures.get(&texture_id).copied()
69 }
70
71 fn set(&mut self, texture_id: TextureId, gl_texture: GlTexture) {
72 self.textures.insert(texture_id, gl_texture);
73 }
74
75 fn remove(&mut self, texture_id: TextureId) -> Option<GlTexture> {
76 let gl_texture = self.textures.remove(&texture_id);
77 self.texture_data.remove(&texture_id);
78 gl_texture
79 }
80
81 fn clear(&mut self) {
82 self.textures.clear();
83 self.texture_data.clear();
84 }
85
86 fn register_texture(
87 &mut self,
88 gl_texture: GlTexture,
89 width: u32,
90 height: u32,
91 format: TextureFormat,
92 ) -> TextureId {
93 self.next_id += 1;
94 let texture_id = TextureId::new(self.next_id as u64);
95
96 let mut texture_data = TextureData::new();
97 texture_data.create(format, width, height);
98 texture_data.set_tex_id(texture_id);
99 texture_data.set_status(TextureStatus::OK);
100
101 self.textures.insert(texture_id, gl_texture);
102 self.texture_data.insert(texture_id, texture_data);
103
104 texture_id
105 }
106
107 fn update_texture(
108 &mut self,
109 texture_id: TextureId,
110 gl_texture: GlTexture,
111 _width: u32,
112 _height: u32,
113 ) {
114 self.textures.insert(texture_id, gl_texture);
115
116 if let Some(texture_data) = self.texture_data.get_mut(&texture_id) {
117 texture_data.set_tex_id(texture_id);
118 texture_data.set_status(TextureStatus::OK);
119 }
120 }
121
122 fn get_texture_data(&self, texture_id: TextureId) -> Option<&TextureData> {
123 self.texture_data.get(&texture_id).map(AsRef::as_ref)
124 }
125
126 fn get_texture_data_mut(&mut self, texture_id: TextureId) -> Option<&mut TextureData> {
127 self.texture_data.get_mut(&texture_id).map(AsMut::as_mut)
128 }
129}
130
131impl SimpleTextureMap {
132 pub fn new() -> Self {
134 Self {
135 textures: HashMap::new(),
136 texture_data: HashMap::new(),
137 next_id: 0,
138 }
139 }
140
141 pub fn len(&self) -> usize {
143 self.textures.len()
144 }
145
146 pub fn is_empty(&self) -> bool {
148 self.textures.is_empty()
149 }
150
151 pub fn iter(&self) -> impl Iterator<Item = (&TextureId, &GlTexture)> {
153 self.textures.iter()
154 }
155
156 pub fn texture_data_iter(&self) -> impl Iterator<Item = (&TextureId, &TextureData)> {
158 self.texture_data
159 .iter()
160 .map(|(id, texture_data)| (id, texture_data.as_ref()))
161 }
162}
163
164pub fn create_texture_from_rgba(
166 gl: &Context,
167 width: u32,
168 height: u32,
169 data: &[u8],
170) -> InitResult<GlTexture> {
171 let (width_i32, height_i32) = checked_gl_texture_size(width, height)?;
172 unsafe {
173 let texture = gl.create_texture().map_err(InitError::CreateTexture)?;
174
175 gl.bind_texture(glow::TEXTURE_2D, Some(texture));
176 gl.tex_image_2d(
177 glow::TEXTURE_2D,
178 0,
179 glow::RGBA as i32,
180 width_i32,
181 height_i32,
182 0,
183 glow::RGBA,
184 glow::UNSIGNED_BYTE,
185 glow::PixelUnpackData::Slice(Some(data)),
186 );
187
188 gl.tex_parameter_i32(
190 glow::TEXTURE_2D,
191 glow::TEXTURE_MIN_FILTER,
192 glow::LINEAR as i32,
193 );
194 gl.tex_parameter_i32(
195 glow::TEXTURE_2D,
196 glow::TEXTURE_MAG_FILTER,
197 glow::LINEAR as i32,
198 );
199 gl.tex_parameter_i32(
200 glow::TEXTURE_2D,
201 glow::TEXTURE_WRAP_S,
202 glow::CLAMP_TO_EDGE as i32,
203 );
204 gl.tex_parameter_i32(
205 glow::TEXTURE_2D,
206 glow::TEXTURE_WRAP_T,
207 glow::CLAMP_TO_EDGE as i32,
208 );
209
210 gl.bind_texture(glow::TEXTURE_2D, None);
211
212 Ok(texture)
213 }
214}
215
216pub fn create_texture_from_alpha(
218 gl: &Context,
219 width: u32,
220 height: u32,
221 data: &[u8],
222) -> InitResult<GlTexture> {
223 let (width_i32, height_i32) = checked_gl_texture_size(width, height)?;
224 let rgba_data = alpha8_to_rgba(data, width, height)?;
225
226 unsafe {
227 let texture = gl.create_texture().map_err(InitError::CreateTexture)?;
228
229 gl.bind_texture(glow::TEXTURE_2D, Some(texture));
230
231 gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, 0);
233 gl.pixel_store_i32(glow::UNPACK_SKIP_PIXELS, 0);
234 gl.pixel_store_i32(glow::UNPACK_SKIP_ROWS, 0);
235 gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
236
237 gl.tex_image_2d(
238 glow::TEXTURE_2D,
239 0,
240 glow::RGBA as i32,
241 width_i32,
242 height_i32,
243 0,
244 glow::RGBA,
245 glow::UNSIGNED_BYTE,
246 glow::PixelUnpackData::Slice(Some(&rgba_data)),
247 );
248
249 gl.tex_parameter_i32(
251 glow::TEXTURE_2D,
252 glow::TEXTURE_MIN_FILTER,
253 glow::LINEAR as i32,
254 );
255 gl.tex_parameter_i32(
256 glow::TEXTURE_2D,
257 glow::TEXTURE_MAG_FILTER,
258 glow::LINEAR as i32,
259 );
260 gl.tex_parameter_i32(
261 glow::TEXTURE_2D,
262 glow::TEXTURE_WRAP_S,
263 glow::CLAMP_TO_EDGE as i32,
264 );
265 gl.tex_parameter_i32(
266 glow::TEXTURE_2D,
267 glow::TEXTURE_WRAP_T,
268 glow::CLAMP_TO_EDGE as i32,
269 );
270
271 gl.bind_texture(glow::TEXTURE_2D, None);
272
273 Ok(texture)
274 }
275}
276
277pub fn update_texture(
279 gl: &Context,
280 texture: GlTexture,
281 x: u32,
282 y: u32,
283 width: u32,
284 height: u32,
285 data: &[u8],
286 format: u32,
287) -> InitResult<()> {
288 let x = gl_texture_size_i32("x", x)?;
289 let y = gl_texture_size_i32("y", y)?;
290 let (width, height) = checked_gl_texture_size(width, height)?;
291 unsafe {
292 gl.bind_texture(glow::TEXTURE_2D, Some(texture));
293 gl.tex_sub_image_2d(
294 glow::TEXTURE_2D,
295 0,
296 x,
297 y,
298 width,
299 height,
300 format,
301 glow::UNSIGNED_BYTE,
302 glow::PixelUnpackData::Slice(Some(data)),
303 );
304 gl.bind_texture(glow::TEXTURE_2D, None);
305 }
306 Ok(())
307}
308
309fn alpha8_to_rgba(data: &[u8], width: u32, height: u32) -> InitResult<Vec<u8>> {
310 let expected_len =
311 (width as usize)
312 .checked_mul(height as usize)
313 .ok_or(InitError::TextureSizeOverflow {
314 format: TextureFormat::Alpha8,
315 })?;
316 if data.len() != expected_len {
317 return Err(InitError::TextureDataSizeMismatch {
318 format: TextureFormat::Alpha8,
319 expected: expected_len,
320 actual: data.len(),
321 });
322 }
323
324 let mut rgba = Vec::with_capacity(expected_len * 4);
325 for &alpha in data {
326 rgba.extend_from_slice(&[255, 255, 255, alpha]);
327 }
328
329 Ok(rgba)
330}
331
332pub(crate) fn upload_texture_data(
333 gl: &Context,
334 texture: GlTexture,
335 width: u32,
336 height: u32,
337 format: TextureFormat,
338 data: &[u8],
339) -> InitResult<()> {
340 let (width_i32, height_i32) = checked_gl_texture_size(width, height)?;
341 let rgba_data;
342 let data = match format {
343 TextureFormat::RGBA32 => {
344 let expected_len = (width as usize)
345 .checked_mul(height as usize)
346 .and_then(|len| len.checked_mul(4))
347 .ok_or(InitError::TextureSizeOverflow {
348 format: TextureFormat::RGBA32,
349 })?;
350 if data.len() != expected_len {
351 return Err(InitError::TextureDataSizeMismatch {
352 format: TextureFormat::RGBA32,
353 expected: expected_len,
354 actual: data.len(),
355 });
356 }
357 data
358 }
359 TextureFormat::Alpha8 => {
360 rgba_data = alpha8_to_rgba(data, width, height)?;
361 &rgba_data
362 }
363 };
364
365 unsafe {
366 let last_active = u32::try_from(gl.get_parameter_i32(glow::ACTIVE_TEXTURE))
367 .ok()
368 .unwrap_or(glow::TEXTURE0);
369 let last_texture = u32::try_from(gl.get_parameter_i32(glow::TEXTURE_BINDING_2D))
370 .ok()
371 .and_then(std::num::NonZeroU32::new)
372 .map(glow::NativeTexture);
373 let last_unpack = gl.get_parameter_i32(glow::UNPACK_ALIGNMENT);
374
375 gl.active_texture(glow::TEXTURE0);
376 gl.bind_texture(glow::TEXTURE_2D, Some(texture));
377 gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
378 gl.tex_image_2d(
379 glow::TEXTURE_2D,
380 0,
381 glow::RGBA as i32,
382 width_i32,
383 height_i32,
384 0,
385 glow::RGBA,
386 glow::UNSIGNED_BYTE,
387 glow::PixelUnpackData::Slice(Some(data)),
388 );
389
390 gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, last_unpack);
391 gl.bind_texture(glow::TEXTURE_2D, last_texture);
392 gl.active_texture(last_active);
393 }
394
395 Ok(())
396}
397
398pub fn update_imgui_texture(
400 gl: &Context,
401 texture_id: TextureId,
402 width: u32,
403 height: u32,
404 data: &[u8],
405) -> InitResult<GlTexture> {
406 let (width_i32, height_i32) = checked_gl_texture_size(width, height)?;
407 unsafe {
408 let last_texture = u32::try_from(gl.get_parameter_i32(glow::TEXTURE_BINDING_2D))
410 .ok()
411 .and_then(std::num::NonZeroU32::new)
412 .map(glow::NativeTexture);
413
414 gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
416
417 let gl_texture = if texture_id.id() == 0 {
418 let texture = gl.create_texture().map_err(InitError::CreateTexture)?;
420
421 gl.bind_texture(glow::TEXTURE_2D, Some(texture));
422 gl.tex_parameter_i32(
423 glow::TEXTURE_2D,
424 glow::TEXTURE_MIN_FILTER,
425 glow::LINEAR as i32,
426 );
427 gl.tex_parameter_i32(
428 glow::TEXTURE_2D,
429 glow::TEXTURE_MAG_FILTER,
430 glow::LINEAR as i32,
431 );
432 gl.tex_parameter_i32(
433 glow::TEXTURE_2D,
434 glow::TEXTURE_WRAP_S,
435 glow::CLAMP_TO_EDGE as i32,
436 );
437 gl.tex_parameter_i32(
438 glow::TEXTURE_2D,
439 glow::TEXTURE_WRAP_T,
440 glow::CLAMP_TO_EDGE as i32,
441 );
442 gl.tex_image_2d(
443 glow::TEXTURE_2D,
444 0,
445 glow::RGBA as i32,
446 width_i32,
447 height_i32,
448 0,
449 glow::RGBA,
450 glow::UNSIGNED_BYTE,
451 glow::PixelUnpackData::Slice(Some(data)),
452 );
453
454 texture
455 } else {
456 let texture_u32 = u32::try_from(texture_id.id())
458 .map_err(|_| InitError::TextureIdOutOfRange(texture_id.id()))?;
459 let texture_nz =
460 std::num::NonZeroU32::new(texture_u32).ok_or(InitError::NullTextureId)?;
461 let texture = glow::NativeTexture(texture_nz);
462 gl.bind_texture(glow::TEXTURE_2D, Some(texture));
463 gl.tex_image_2d(
464 glow::TEXTURE_2D,
465 0,
466 glow::RGBA as i32,
467 width_i32,
468 height_i32,
469 0,
470 glow::RGBA,
471 glow::UNSIGNED_BYTE,
472 glow::PixelUnpackData::Slice(Some(data)),
473 );
474
475 texture
476 };
477
478 gl.bind_texture(glow::TEXTURE_2D, last_texture);
480
481 Ok(gl_texture)
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488
489 #[test]
490 fn alpha8_to_rgba_expands_white_rgb_and_alpha() {
491 let rgba = alpha8_to_rgba(&[0, 64, 255], 3, 1).expect("valid alpha data");
492
493 assert_eq!(
494 rgba,
495 vec![
496 255, 255, 255, 0, 255, 255, 255, 64, 255, 255, 255, 255,
499 ]
500 );
501 }
502
503 #[test]
504 fn alpha8_to_rgba_rejects_size_mismatch() {
505 assert!(alpha8_to_rgba(&[0, 1], 3, 1).is_err());
506 }
507}