Skip to main content

dear_imgui_glow/
texture.rs

1//! Texture management for Dear ImGui
2
3use 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
19/// Trait for managing texture mappings with modern Dear ImGui texture system
20pub trait TextureMap {
21    /// Get the OpenGL texture for a Dear ImGui texture ID
22    fn get(&self, texture_id: TextureId) -> Option<GlTexture>;
23
24    /// Set the OpenGL texture for a Dear ImGui texture ID
25    fn set(&mut self, texture_id: TextureId, gl_texture: GlTexture);
26
27    /// Remove a texture mapping
28    fn remove(&mut self, texture_id: TextureId) -> Option<GlTexture>;
29
30    /// Clear all texture mappings
31    fn clear(&mut self);
32
33    /// Register a texture with Dear ImGui's texture management system
34    fn register_texture(
35        &mut self,
36        gl_texture: GlTexture,
37        width: u32,
38        height: u32,
39        format: TextureFormat,
40    ) -> TextureId;
41
42    /// Update a texture in Dear ImGui's texture management system
43    fn update_texture(
44        &mut self,
45        texture_id: TextureId,
46        gl_texture: GlTexture,
47        width: u32,
48        height: u32,
49    );
50
51    /// Get texture data from Dear ImGui's texture management system
52    fn get_texture_data(&self, texture_id: TextureId) -> Option<&TextureData>;
53
54    /// Get mutable texture data from Dear ImGui's texture management system
55    fn get_texture_data_mut(&mut self, texture_id: TextureId) -> Option<&mut TextureData>;
56}
57
58/// Simple texture map implementation using a HashMap with modern texture management
59#[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    /// Create a new empty texture map
133    pub fn new() -> Self {
134        Self {
135            textures: HashMap::new(),
136            texture_data: HashMap::new(),
137            next_id: 0,
138        }
139    }
140
141    /// Get the number of textures in the map
142    pub fn len(&self) -> usize {
143        self.textures.len()
144    }
145
146    /// Check if the texture map is empty
147    pub fn is_empty(&self) -> bool {
148        self.textures.is_empty()
149    }
150
151    /// Iterate over all texture mappings
152    pub fn iter(&self) -> impl Iterator<Item = (&TextureId, &GlTexture)> {
153        self.textures.iter()
154    }
155
156    /// Iterate over all texture data
157    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
164/// Create a texture from raw RGBA data
165pub 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        // Set texture parameters
189        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
216/// Create a texture from raw alpha data (single channel)
217pub 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        // Set pixel store parameters
232        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        // Set texture parameters
250        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
277/// Update a texture with new data
278pub 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
398/// Update texture from ImGui texture data (similar to ImGui_ImplOpenGL3_UpdateTexture)
399pub 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        // Backup current texture binding
409        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        // Set pixel store parameters
415        gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
416
417        let gl_texture = if texture_id.id() == 0 {
418            // Create new texture
419            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            // Update existing texture
457            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        // Restore previous texture binding
479        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, //
497                255, 255, 255, 64, //
498                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}