dear_imgui_rs/
texture.rs

1//! Texture management for Dear ImGui
2//!
3//! This module provides access to Dear ImGui's modern texture management system
4//! introduced in version 1.92+. It includes support for ImTextureData, texture
5//! status management, and automatic texture updates.
6
7use crate::sys;
8use std::ffi::c_void;
9
10/// Simple texture ID for backward compatibility
11///
12/// This is a simple wrapper around u64 that can be used to identify textures.
13/// For modern texture management, use TextureData instead.
14///
15/// Note: Changed from usize to u64 in Dear ImGui 1.91.4+ to support 64-bit handles
16/// like Vulkan and DX12 on 32-bit targets.
17#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
18#[repr(transparent)]
19pub struct TextureId(u64);
20
21impl TextureId {
22    /// Creates a new texture id with the given identifier
23    #[inline]
24    pub const fn new(id: u64) -> Self {
25        Self(id)
26    }
27
28    /// Returns the id of the TextureId
29    #[inline]
30    pub const fn id(self) -> u64 {
31        self.0
32    }
33
34    /// Creates a null texture ID
35    #[inline]
36    pub const fn null() -> Self {
37        Self(0)
38    }
39
40    /// Checks if this texture ID is null
41    #[inline]
42    pub const fn is_null(self) -> bool {
43        self.0 == 0
44    }
45}
46
47impl From<u64> for TextureId {
48    #[inline]
49    fn from(id: u64) -> Self {
50        TextureId(id)
51    }
52}
53
54impl<T> From<*const T> for TextureId {
55    #[inline]
56    fn from(ptr: *const T) -> Self {
57        TextureId(ptr as usize as u64)
58    }
59}
60
61impl<T> From<*mut T> for TextureId {
62    #[inline]
63    fn from(ptr: *mut T) -> Self {
64        TextureId(ptr as usize as u64)
65    }
66}
67
68impl From<TextureId> for *const c_void {
69    #[inline]
70    fn from(id: TextureId) -> Self {
71        id.0 as usize as *const c_void
72    }
73}
74
75impl From<TextureId> for *mut c_void {
76    #[inline]
77    fn from(id: TextureId) -> Self {
78        id.0 as usize as *mut c_void
79    }
80}
81
82// Backward compatibility: allow conversion from usize for legacy code
83impl From<usize> for TextureId {
84    #[inline]
85    fn from(id: usize) -> Self {
86        TextureId(id as u64)
87    }
88}
89
90// Allow conversion to usize for legacy code
91impl From<TextureId> for usize {
92    #[inline]
93    fn from(id: TextureId) -> Self {
94        id.0 as usize
95    }
96}
97
98impl Default for TextureId {
99    #[inline]
100    fn default() -> Self {
101        Self::null()
102    }
103}
104
105/// Raw texture ID type for compatibility with Dear ImGui
106pub type RawTextureId = *const c_void;
107
108/// A convenient, typed wrapper around ImGui's ImTextureRef (v1.92+)
109///
110/// Can reference either a plain `TextureId` (legacy path) or a managed `TextureData`.
111///
112/// Examples
113/// - With a plain GPU handle (legacy path):
114/// ```no_run
115/// # use dear_imgui_rs::{Ui, TextureId};
116/// # fn demo(ui: &Ui) {
117/// let tex_id = TextureId::new(12345);
118/// ui.image(tex_id, [64.0, 64.0]);
119/// # }
120/// ```
121/// - With a managed texture (ImGui 1.92 texture system):
122/// ```no_run
123/// # use dear_imgui_rs::{Ui, texture::{TextureData, TextureFormat}};
124/// # fn demo(ui: &Ui) {
125/// let mut tex = TextureData::new();
126/// tex.create(TextureFormat::RGBA32, 256, 256);
127/// // Fill pixels or schedule updates...
128/// ui.image(&mut *tex, [256.0, 256.0]);
129/// // The renderer backend will honor WantCreate/WantUpdates/WantDestroy
130/// // via DrawData::textures() when rendering this frame.
131/// # }
132/// ```
133#[derive(Copy, Clone, Debug)]
134#[repr(transparent)]
135pub struct TextureRef(sys::ImTextureRef);
136
137impl TextureRef {
138    /// Create a texture reference from a raw ImGui texture ref
139    #[inline]
140    pub fn from_raw(raw: sys::ImTextureRef) -> Self {
141        Self(raw)
142    }
143
144    /// Get the underlying ImGui texture ref (by value)
145    #[inline]
146    pub fn raw(self) -> sys::ImTextureRef {
147        self.0
148    }
149}
150
151impl From<TextureId> for TextureRef {
152    #[inline]
153    fn from(id: TextureId) -> Self {
154        TextureRef(sys::ImTextureRef {
155            _TexData: std::ptr::null_mut(),
156            _TexID: id.id() as sys::ImTextureID,
157        })
158    }
159}
160
161impl From<u64> for TextureRef {
162    #[inline]
163    fn from(id: u64) -> Self {
164        TextureRef::from(TextureId::from(id))
165    }
166}
167
168impl From<&TextureData> for TextureRef {
169    #[inline]
170    fn from(td: &TextureData) -> Self {
171        // Safe for immediate use during the frame; the caller must uphold lifetime.
172        TextureRef(sys::ImTextureRef {
173            _TexData: td.as_raw() as *mut sys::ImTextureData,
174            _TexID: 0,
175        })
176    }
177}
178
179impl From<&mut TextureData> for TextureRef {
180    #[inline]
181    fn from(td: &mut TextureData) -> Self {
182        TextureRef(sys::ImTextureRef {
183            _TexData: td.as_raw_mut(),
184            _TexID: 0,
185        })
186    }
187}
188
189/// Texture format supported by Dear ImGui
190#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
191#[repr(i32)]
192pub enum TextureFormat {
193    /// 4 components per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight * 4
194    RGBA32 = sys::ImTextureFormat_RGBA32 as i32,
195    /// 1 component per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight
196    Alpha8 = sys::ImTextureFormat_Alpha8 as i32,
197}
198
199impl From<sys::ImTextureFormat> for TextureFormat {
200    fn from(format: sys::ImTextureFormat) -> Self {
201        match format {
202            sys::ImTextureFormat_RGBA32 => TextureFormat::RGBA32,
203            sys::ImTextureFormat_Alpha8 => TextureFormat::Alpha8,
204            _ => TextureFormat::RGBA32, // Default fallback
205        }
206    }
207}
208
209impl From<TextureFormat> for sys::ImTextureFormat {
210    fn from(format: TextureFormat) -> Self {
211        format as sys::ImTextureFormat
212    }
213}
214
215/// Status of a texture to communicate with Renderer Backend
216#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
217#[repr(i32)]
218pub enum TextureStatus {
219    /// Texture is ready and can be used
220    OK = sys::ImTextureStatus_OK as i32,
221    /// Backend destroyed the texture
222    Destroyed = sys::ImTextureStatus_Destroyed as i32,
223    /// Requesting backend to create the texture. Set status OK when done.
224    WantCreate = sys::ImTextureStatus_WantCreate as i32,
225    /// Requesting backend to update specific blocks of pixels. Set status OK when done.
226    WantUpdates = sys::ImTextureStatus_WantUpdates as i32,
227    /// Requesting backend to destroy the texture. Set status to Destroyed when done.
228    WantDestroy = sys::ImTextureStatus_WantDestroy as i32,
229}
230
231impl From<sys::ImTextureStatus> for TextureStatus {
232    fn from(status: sys::ImTextureStatus) -> Self {
233        match status {
234            sys::ImTextureStatus_OK => TextureStatus::OK,
235            sys::ImTextureStatus_Destroyed => TextureStatus::Destroyed,
236            sys::ImTextureStatus_WantCreate => TextureStatus::WantCreate,
237            sys::ImTextureStatus_WantUpdates => TextureStatus::WantUpdates,
238            sys::ImTextureStatus_WantDestroy => TextureStatus::WantDestroy,
239            _ => TextureStatus::Destroyed, // Default fallback
240        }
241    }
242}
243
244impl From<TextureStatus> for sys::ImTextureStatus {
245    fn from(status: TextureStatus) -> Self {
246        status as sys::ImTextureStatus
247    }
248}
249
250/// Coordinates of a rectangle within a texture
251#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
252pub struct TextureRect {
253    /// Upper-left X coordinate of rectangle to update
254    pub x: u16,
255    /// Upper-left Y coordinate of rectangle to update
256    pub y: u16,
257    /// Width of rectangle to update
258    pub w: u16,
259    /// Height of rectangle to update
260    pub h: u16,
261}
262
263impl From<sys::ImTextureRect> for TextureRect {
264    fn from(rect: sys::ImTextureRect) -> Self {
265        Self {
266            x: rect.x,
267            y: rect.y,
268            w: rect.w,
269            h: rect.h,
270        }
271    }
272}
273
274impl From<TextureRect> for sys::ImTextureRect {
275    fn from(rect: TextureRect) -> Self {
276        Self {
277            x: rect.x,
278            y: rect.y,
279            w: rect.w,
280            h: rect.h,
281        }
282    }
283}
284
285/// Texture data managed by Dear ImGui
286///
287/// This is a wrapper around ImTextureData that provides safe access to
288/// texture information and pixel data. It's used by renderer backends
289/// to create, update, and destroy textures.
290///
291/// Lifecycle & Backend Flow (ImGui 1.92+)
292/// - Create an instance (e.g. via `TextureData::new()` + `create()`)
293/// - Mutate pixels, set flags/rects (e.g. call `set_data()` or directly write `Pixels` then
294///   set `UpdateRect`), and set status to `WantCreate`/`WantUpdates`.
295/// - When you pass `&mut TextureData` to any widget/draw call, Dear ImGui will place this
296///   texture into `DrawData::textures()` for the current frame.
297/// - Your renderer backend iterates `DrawData::textures()` and performs the requested
298///   create/update/destroy operations, then updates status to `OK`/`Destroyed`.
299/// - You can also set/get a `TexID` (e.g., GPU handle) via `set_tex_id()/tex_id()` after creation.
300///
301/// Lifetime Note: If using the managed path, you must keep `TextureData` alive at least until the
302/// end of the frame where it is referenced by UI calls. Prefer owning containers like `Box`/`Arc`.
303#[repr(transparent)]
304pub struct TextureData {
305    raw: sys::ImTextureData,
306}
307
308impl TextureData {
309    /// Create a new empty texture data
310    ///
311    /// This creates a new TextureData instance with default values.
312    /// The texture will be in Destroyed status and needs to be created with `create()`.
313    pub fn new() -> Box<Self> {
314        // Initialize via ImGui constructor to inherit default changes safely
315        let raw = unsafe {
316            let p = sys::ImTextureData_ImTextureData();
317            let v = *p;
318            sys::ImTextureData_destroy(p);
319            v
320        };
321        let raw_data = Box::new(raw);
322        unsafe { Box::from_raw(Box::into_raw(raw_data) as *mut Self) }
323    }
324
325    /// Create a new texture data from raw pointer (crate-internal)
326    ///
327    /// Safety: caller must ensure the pointer is valid for the returned lifetime.
328    pub(crate) unsafe fn from_raw<'a>(raw: *mut sys::ImTextureData) -> &'a mut Self {
329        unsafe { &mut *(raw as *mut Self) }
330    }
331
332    /// Get the raw pointer to the underlying ImTextureData
333    pub fn as_raw(&self) -> *const sys::ImTextureData {
334        &self.raw as *const _
335    }
336
337    /// Get the raw mutable pointer to the underlying ImTextureData
338    pub fn as_raw_mut(&mut self) -> *mut sys::ImTextureData {
339        &mut self.raw as *mut _
340    }
341
342    /// Get the unique ID of this texture (for debugging)
343    pub fn unique_id(&self) -> i32 {
344        self.raw.UniqueID
345    }
346
347    /// Get the current status of this texture
348    pub fn status(&self) -> TextureStatus {
349        TextureStatus::from(self.raw.Status)
350    }
351
352    /// Set the status of this texture
353    ///
354    /// This should only be called by renderer backends after handling a request.
355    pub fn set_status(&mut self, status: TextureStatus) {
356        self.raw.Status = status.into();
357    }
358
359    /// Get the backend user data
360    pub fn backend_user_data(&self) -> *mut c_void {
361        self.raw.BackendUserData
362    }
363
364    /// Set the backend user data
365    pub fn set_backend_user_data(&mut self, data: *mut c_void) {
366        self.raw.BackendUserData = data;
367    }
368
369    /// Get the texture ID
370    pub fn tex_id(&self) -> TextureId {
371        TextureId::from(self.raw.TexID)
372    }
373
374    /// Set the texture ID
375    ///
376    /// This should only be called by renderer backends after creating or destroying the texture.
377    pub fn set_tex_id(&mut self, tex_id: TextureId) {
378        self.raw.TexID = tex_id.id() as sys::ImTextureID;
379    }
380
381    /// Get the texture format
382    pub fn format(&self) -> TextureFormat {
383        TextureFormat::from(self.raw.Format)
384    }
385
386    /// Get the texture width
387    pub fn width(&self) -> i32 {
388        self.raw.Width
389    }
390
391    /// Get the texture height
392    pub fn height(&self) -> i32 {
393        self.raw.Height
394    }
395
396    /// Get the bytes per pixel
397    pub fn bytes_per_pixel(&self) -> i32 {
398        self.raw.BytesPerPixel
399    }
400
401    /// Get the number of unused frames
402    pub fn unused_frames(&self) -> i32 {
403        self.raw.UnusedFrames
404    }
405
406    /// Get the reference count
407    pub fn ref_count(&self) -> u16 {
408        self.raw.RefCount
409    }
410
411    /// Check if the texture uses colors (rather than just white + alpha)
412    pub fn use_colors(&self) -> bool {
413        self.raw.UseColors
414    }
415
416    /// Check if the texture is queued for destruction next frame
417    pub fn want_destroy_next_frame(&self) -> bool {
418        self.raw.WantDestroyNextFrame
419    }
420
421    /// Get the pixel data
422    ///
423    /// Returns None if no pixel data is available.
424    pub fn pixels(&self) -> Option<&[u8]> {
425        if self.raw.Pixels.is_null() {
426            None
427        } else {
428            let size = (self.width() * self.height() * self.bytes_per_pixel()) as usize;
429            unsafe {
430                Some(std::slice::from_raw_parts(
431                    self.raw.Pixels as *const u8,
432                    size,
433                ))
434            }
435        }
436    }
437
438    /// Get the bounding box of all used pixels in the texture
439    pub fn used_rect(&self) -> TextureRect {
440        unsafe { TextureRect::from((*self.as_raw()).UsedRect) }
441    }
442
443    /// Get the bounding box of all queued updates
444    pub fn update_rect(&self) -> TextureRect {
445        unsafe { TextureRect::from((*self.as_raw()).UpdateRect) }
446    }
447
448    /// Iterate over queued update rectangles (copying to safe TextureRect)
449    pub fn updates(&self) -> impl Iterator<Item = TextureRect> + '_ {
450        unsafe {
451            let vec = &(*self.as_raw()).Updates;
452            let count = vec.Size as usize;
453            let data = vec.Data as *const sys::ImTextureRect;
454            (0..count).map(move |i| TextureRect::from(*data.add(i)))
455        }
456    }
457
458    /// Get the pixel data at a specific position
459    ///
460    /// Returns None if no pixel data is available or coordinates are out of bounds.
461    pub fn pixels_at(&self, x: i32, y: i32) -> Option<&[u8]> {
462        if self.raw.Pixels.is_null() || x < 0 || y < 0 || x >= self.width() || y >= self.height() {
463            None
464        } else {
465            let offset = (x + y * self.width()) * self.bytes_per_pixel();
466            let remaining_size = ((self.width() - x) + (self.height() - y - 1) * self.width())
467                * self.bytes_per_pixel();
468            unsafe {
469                let ptr = (self.raw.Pixels as *const u8).add(offset as usize);
470                Some(std::slice::from_raw_parts(ptr, remaining_size as usize))
471            }
472        }
473    }
474
475    /// Get the pitch (bytes per row)
476    pub fn pitch(&self) -> i32 {
477        self.width() * self.bytes_per_pixel()
478    }
479
480    /// Create a new texture with the specified format and dimensions
481    ///
482    /// This allocates pixel data and sets the status to WantCreate.
483    pub fn create(&mut self, format: TextureFormat, width: i32, height: i32) {
484        unsafe {
485            sys::ImTextureData_Create(self.as_raw_mut(), format.into(), width, height);
486        }
487    }
488
489    /// Destroy the pixel data
490    ///
491    /// This frees the CPU-side pixel data but doesn't affect the GPU texture.
492    pub fn destroy_pixels(&mut self) {
493        unsafe {
494            sys::ImTextureData_DestroyPixels(self.as_raw_mut());
495        }
496    }
497
498    /// Set the pixel data for the texture
499    ///
500    /// This copies the provided data into the texture's pixel buffer.
501    pub fn set_data(&mut self, data: &[u8]) {
502        unsafe {
503            let raw = self.as_raw_mut();
504            let needed = (*raw)
505                .Width
506                .saturating_mul((*raw).Height)
507                .saturating_mul((*raw).BytesPerPixel);
508            if needed <= 0 {
509                // Nothing to do without valid dimensions/format.
510                return;
511            }
512
513            // Ensure pixel buffer exists and has correct size
514            if (*raw).Pixels.is_null() {
515                sys::ImTextureData_Create(
516                    self.as_raw_mut(),
517                    (*raw).Format,
518                    (*raw).Width,
519                    (*raw).Height,
520                );
521            }
522
523            let copy_bytes = std::cmp::min(needed as usize, data.len());
524            if copy_bytes == 0 {
525                return;
526            }
527
528            std::ptr::copy_nonoverlapping(data.as_ptr(), (*raw).Pixels as *mut u8, copy_bytes);
529
530            // Mark entire texture as updated
531            (*raw).UpdateRect = sys::ImTextureRect {
532                x: 0u16,
533                y: 0u16,
534                w: (*raw).Width.clamp(0, u16::MAX as i32) as u16,
535                h: (*raw).Height.clamp(0, u16::MAX as i32) as u16,
536            };
537            (*raw).Status = sys::ImTextureStatus_WantUpdates;
538        }
539    }
540
541    /// Set the width of the texture
542    pub fn set_width(&mut self, width: u32) {
543        unsafe {
544            (*self.as_raw_mut()).Width = width as i32;
545        }
546    }
547
548    /// Set the height of the texture
549    pub fn set_height(&mut self, height: u32) {
550        unsafe {
551            (*self.as_raw_mut()).Height = height as i32;
552        }
553    }
554
555    /// Set the format of the texture
556    pub fn set_format(&mut self, format: TextureFormat) {
557        unsafe {
558            (*self.as_raw_mut()).Format = format.into();
559        }
560    }
561}
562
563/// Get the number of bytes per pixel for a texture format
564pub fn get_format_bytes_per_pixel(format: TextureFormat) -> i32 {
565    unsafe { sys::igImTextureDataGetFormatBytesPerPixel(format.into()) }
566}
567
568/// Create an ImTextureRef from a texture ID
569///
570/// This is the safe way to create an ImTextureRef for use with Dear ImGui.
571/// Use this instead of directly constructing the sys::ImTextureRef structure.
572pub fn create_texture_ref(texture_id: u64) -> sys::ImTextureRef {
573    sys::ImTextureRef {
574        _TexData: std::ptr::null_mut(),
575        _TexID: texture_id,
576    }
577}
578
579/// Get the name of a texture status (for debugging)
580pub fn get_status_name(status: TextureStatus) -> &'static str {
581    unsafe {
582        let ptr = sys::igImTextureDataGetStatusName(status.into());
583        if ptr.is_null() {
584            "Unknown"
585        } else {
586            std::ffi::CStr::from_ptr(ptr).to_str().unwrap_or("Invalid")
587        }
588    }
589}
590
591/// Get the name of a texture format (for debugging)
592pub fn get_format_name(format: TextureFormat) -> &'static str {
593    unsafe {
594        let ptr = sys::igImTextureDataGetFormatName(format.into());
595        if ptr.is_null() {
596            "Unknown"
597        } else {
598            std::ffi::CStr::from_ptr(ptr).to_str().unwrap_or("Invalid")
599        }
600    }
601}