Skip to main content

dear_imgui_rs/texture/
data.rs

1use super::format::{texture_format_bytes_per_pixel, texture_format_bytes_per_pixel_i32};
2use super::validation::{
3    checked_texture_byte_len, checked_texture_byte_len_if_valid, checked_texture_dimension_to_i32,
4    non_negative_texture_count_from_i32,
5};
6use super::{
7    ManagedTextureId, OwnedTextureData, TextureFormat, TextureId, TextureRect, TextureRef,
8    TextureStatus,
9};
10use crate::sys;
11use std::cell::UnsafeCell;
12use std::ffi::c_void;
13
14/// Texture data managed by Dear ImGui
15///
16/// This is a wrapper around ImTextureData that provides safe access to
17/// texture information and pixel data. It's used by renderer backends
18/// to create, update, and destroy textures.
19///
20/// Lifecycle & Backend Flow (ImGui 1.92+)
21/// - Create an instance (e.g. via `OwnedTextureData::new()` + `create()`)
22/// - Mutate pixels, set flags/rects (e.g. call `set_data()` or directly write `Pixels` then
23///   set `UpdateRect`), and set status to `WantCreate`/`WantUpdates`.
24/// - Register user-created owned textures once via `Context::register_user_texture(&mut tex)`. Dear
25///   ImGui builds `DrawData::textures()` from its internal `PlatformIO.Textures[]` list (font atlas
26///   textures are registered by ImGui itself).
27/// - Your renderer backend iterates `DrawData::textures_mut()` and performs the requested
28///   create/update/destroy operations, then updates status to `OK`/`Destroyed`.
29/// - You can also set/get a `TexID` (e.g., GPU handle) via `set_tex_id()/tex_id()` after creation.
30///
31/// Lifetime Note: If using the managed path, you must keep the underlying `ImTextureData` alive at
32/// least until the end of the frame where it is referenced by UI calls. If you create textures
33/// yourself, use [`OwnedTextureData`] to ensure the object is correctly constructed and destroyed
34/// by the C++ side.
35#[repr(transparent)]
36pub struct TextureData {
37    raw: UnsafeCell<sys::ImTextureData>,
38}
39
40// Ensure the wrapper stays layout-compatible with the sys bindings.
41const _: [(); std::mem::size_of::<sys::ImTextureData>()] = [(); std::mem::size_of::<TextureData>()];
42const _: [(); std::mem::align_of::<sys::ImTextureData>()] =
43    [(); std::mem::align_of::<TextureData>()];
44
45impl TextureData {
46    #[inline]
47    pub(super) fn inner(&self) -> &sys::ImTextureData {
48        // Safety: `TextureData` is a view into an ImGui-owned `ImTextureData`. Dear ImGui and
49        // renderer backends can mutate fields (e.g. Status/TexID/BackendUserData) while Rust holds
50        // `&TextureData`, so we store it behind `UnsafeCell` to make that interior mutability
51        // explicit.
52        unsafe { &*self.raw.get() }
53    }
54
55    #[inline]
56    pub(super) fn inner_mut(&mut self) -> &mut sys::ImTextureData {
57        // Safety: caller has `&mut TextureData`, so this is a unique Rust borrow for this wrapper.
58        unsafe { &mut *self.raw.get() }
59    }
60
61    pub(super) fn assert_metadata_mutation_allowed(&self, caller: &str) {
62        let raw = self.inner();
63        assert!(
64            raw.Pixels.is_null(),
65            "{caller} cannot change texture metadata while pixel storage is allocated"
66        );
67        assert!(
68            raw.Status == sys::ImTextureStatus_Destroyed,
69            "{caller} requires Destroyed texture status"
70        );
71    }
72
73    /// Create a new owned texture data object.
74    ///
75    /// This is kept for convenience. Prefer [`OwnedTextureData::new()`] for clarity.
76    pub fn new() -> OwnedTextureData {
77        OwnedTextureData::new()
78    }
79
80    /// Create a new texture data from raw pointer (crate-internal)
81    ///
82    /// Safety: caller must ensure the pointer is valid for the returned lifetime.
83    pub(crate) unsafe fn from_raw<'a>(raw: *mut sys::ImTextureData) -> &'a mut Self {
84        unsafe { &mut *(raw as *mut Self) }
85    }
86
87    /// Create a shared texture data view from a raw pointer (crate-internal).
88    ///
89    /// Safety: caller must ensure the pointer is valid for the returned lifetime.
90    pub(crate) unsafe fn from_raw_ref<'a>(raw: *const sys::ImTextureData) -> &'a Self {
91        unsafe { &*(raw as *const Self) }
92    }
93
94    /// Get the raw pointer to the underlying ImTextureData
95    pub fn as_raw(&self) -> *const sys::ImTextureData {
96        self.raw.get() as *const _
97    }
98
99    /// Get the raw mutable pointer to the underlying ImTextureData
100    pub fn as_raw_mut(&mut self) -> *mut sys::ImTextureData {
101        self.raw.get()
102    }
103
104    /// Get this managed texture's stable ImGui identity.
105    pub fn unique_id(&self) -> ManagedTextureId {
106        ManagedTextureId::from_raw(self.inner().UniqueID)
107    }
108
109    /// Get the current status of this texture
110    pub fn status(&self) -> TextureStatus {
111        TextureStatus::from(self.inner().Status)
112    }
113
114    /// Set the status of this texture
115    ///
116    /// This should only be called by renderer backends after handling a request.
117    pub fn set_status(&mut self, status: TextureStatus) {
118        unsafe {
119            // When marking a texture as destroyed, Dear ImGui expects the backend to clear any
120            // backend bindings (TexID/BackendUserData). Otherwise ImGui will assert when
121            // processing the texture list.
122            if status == TextureStatus::Destroyed {
123                sys::ImTextureData_SetTexID(self.as_raw_mut(), 0 as sys::ImTextureID);
124                (*self.as_raw_mut()).BackendUserData = std::ptr::null_mut();
125            }
126            sys::ImTextureData_SetStatus(self.as_raw_mut(), status.into());
127        }
128    }
129
130    /// Get the backend user data
131    pub fn backend_user_data(&self) -> *mut c_void {
132        self.inner().BackendUserData
133    }
134
135    /// Set the backend user data
136    pub fn set_backend_user_data(&mut self, data: *mut c_void) {
137        self.inner_mut().BackendUserData = data;
138    }
139
140    /// Get the texture ID
141    pub fn tex_id(&self) -> TextureId {
142        TextureId::from(self.inner().TexID)
143    }
144
145    /// Set the texture ID
146    ///
147    /// This should only be called by renderer backends after creating or destroying the texture.
148    pub fn set_tex_id(&mut self, tex_id: TextureId) {
149        unsafe {
150            sys::ImTextureData_SetTexID(self.as_raw_mut(), tex_id.id() as sys::ImTextureID);
151        }
152    }
153
154    /// Get the current texture reference for this managed texture.
155    #[inline]
156    pub fn texture_ref(&mut self) -> TextureRef<'_> {
157        unsafe { TextureRef::from_raw(sys::ImTextureData_GetTexRef(self.as_raw_mut())) }
158    }
159
160    /// Get the texture format
161    pub fn format(&self) -> TextureFormat {
162        TextureFormat::from(self.inner().Format)
163    }
164
165    /// Get the texture width
166    pub fn width(&self) -> u32 {
167        u32::try_from(self.raw_width_i32()).unwrap_or(0)
168    }
169
170    /// Get the texture height
171    pub fn height(&self) -> u32 {
172        u32::try_from(self.raw_height_i32()).unwrap_or(0)
173    }
174
175    /// Get the bytes per pixel
176    pub fn bytes_per_pixel(&self) -> usize {
177        usize::try_from(self.raw_bytes_per_pixel_i32()).unwrap_or(0)
178    }
179
180    /// Get the number of unused frames
181    pub fn unused_frames(&self) -> usize {
182        non_negative_texture_count_from_i32(
183            "TextureData::unused_frames()",
184            self.inner().UnusedFrames,
185        )
186    }
187
188    /// Get the reference count
189    pub fn ref_count(&self) -> u16 {
190        self.inner().RefCount
191    }
192
193    /// Check if the texture uses colors (rather than just white + alpha)
194    pub fn use_colors(&self) -> bool {
195        self.inner().UseColors
196    }
197
198    /// Check if the texture is queued for destruction next frame
199    pub fn want_destroy_next_frame(&self) -> bool {
200        self.inner().WantDestroyNextFrame
201    }
202
203    /// Get the pixel data
204    ///
205    /// Returns None if no pixel data is available.
206    pub fn pixels(&self) -> Option<&[u8]> {
207        let raw = self.inner();
208        if raw.Pixels.is_null() {
209            None
210        } else {
211            let width = raw.Width;
212            let height = raw.Height;
213            let bytes_per_pixel = raw.BytesPerPixel;
214            if width <= 0 || height <= 0 || bytes_per_pixel <= 0 {
215                return None;
216            }
217
218            let size = (width as usize)
219                .checked_mul(height as usize)?
220                .checked_mul(bytes_per_pixel as usize)?;
221            unsafe { Some(std::slice::from_raw_parts(raw.Pixels as *const u8, size)) }
222        }
223    }
224
225    /// Get the bounding box of all used pixels in the texture
226    pub fn used_rect(&self) -> TextureRect {
227        TextureRect::from(self.inner().UsedRect)
228    }
229
230    /// Get the bounding box of all queued updates
231    pub fn update_rect(&self) -> TextureRect {
232        TextureRect::from(self.inner().UpdateRect)
233    }
234
235    /// Iterate over queued update rectangles (copying to safe TextureRect)
236    pub fn updates(&self) -> impl Iterator<Item = TextureRect> + '_ {
237        let vec = &self.inner().Updates;
238        let count = if vec.Data.is_null() {
239            0
240        } else {
241            usize::try_from(vec.Size).unwrap_or(0)
242        };
243        let data = vec.Data as *const sys::ImTextureRect;
244        (0..count).map(move |i| unsafe { TextureRect::from(*data.add(i)) })
245    }
246
247    /// Get the pixel data at a specific position
248    ///
249    /// Returns None if no pixel data is available or coordinates are out of bounds.
250    pub fn pixels_at(&self, x: u32, y: u32) -> Option<&[u8]> {
251        let raw = self.inner();
252        let width = u32::try_from(raw.Width).ok()?;
253        let height = u32::try_from(raw.Height).ok()?;
254        let bytes_per_pixel = usize::try_from(raw.BytesPerPixel).ok()?;
255        if raw.Pixels.is_null() || width == 0 || height == 0 || bytes_per_pixel == 0 {
256            return None;
257        }
258        if x >= width || y >= height {
259            None
260        } else {
261            let width_usize = usize::try_from(width).ok()?;
262            let height_usize = usize::try_from(height).ok()?;
263            let x_usize = usize::try_from(x).ok()?;
264            let y_usize = usize::try_from(y).ok()?;
265
266            let total_size = width_usize
267                .checked_mul(height_usize)?
268                .checked_mul(bytes_per_pixel)?;
269
270            let offset_px = y_usize.checked_mul(width_usize)?.checked_add(x_usize)?;
271            let offset_bytes = offset_px.checked_mul(bytes_per_pixel)?;
272            let remaining_size = total_size.checked_sub(offset_bytes)?;
273
274            unsafe {
275                let ptr = (raw.Pixels as *const u8).add(offset_bytes);
276                Some(std::slice::from_raw_parts(ptr, remaining_size))
277            }
278        }
279    }
280
281    /// Get the pitch (bytes per row)
282    pub fn pitch(&self) -> usize {
283        let width = self.width();
284        let bytes_per_pixel = self.bytes_per_pixel();
285        if width == 0 || bytes_per_pixel == 0 {
286            return 0;
287        }
288        usize::try_from(width)
289            .expect("TextureData::pitch() width must fit usize")
290            .checked_mul(bytes_per_pixel)
291            .expect("TextureData::pitch() byte pitch overflowed usize")
292    }
293
294    /// Create a new texture with the specified format and dimensions
295    ///
296    /// This allocates pixel data and sets the status to WantCreate.
297    pub fn create(&mut self, format: TextureFormat, width: u32, height: u32) {
298        assert!(
299            self.status() == TextureStatus::Destroyed,
300            "TextureData::create() requires Destroyed texture status"
301        );
302        let bytes_per_pixel = texture_format_bytes_per_pixel(format);
303        let _ = checked_texture_byte_len("TextureData::create()", width, height, bytes_per_pixel);
304        let width = checked_texture_dimension_to_i32("TextureData::create()", "width", width);
305        let height = checked_texture_dimension_to_i32("TextureData::create()", "height", height);
306
307        unsafe {
308            sys::ImTextureData_Create(self.as_raw_mut(), format.into(), width, height);
309        }
310    }
311
312    /// Destroy the pixel data
313    ///
314    /// This frees the CPU-side pixel data but doesn't affect the GPU texture.
315    pub fn destroy_pixels(&mut self) {
316        unsafe {
317            sys::ImTextureData_DestroyPixels(self.as_raw_mut());
318        }
319    }
320
321    /// Set the pixel data for the texture
322    ///
323    /// This copies the provided data into the texture's pixel buffer.
324    pub fn set_data(&mut self, data: &[u8]) {
325        unsafe {
326            let raw = self.as_raw_mut();
327            let Some(needed) = checked_texture_byte_len_if_valid(
328                "TextureData::set_data()",
329                (*raw).Width,
330                (*raw).Height,
331                (*raw).BytesPerPixel,
332            ) else {
333                // Nothing to do without valid dimensions/format.
334                return;
335            };
336
337            // Ensure pixel buffer exists and has correct size
338            if (*raw).Pixels.is_null() {
339                assert!(
340                    (*raw).Status == sys::ImTextureStatus_Destroyed,
341                    "TextureData::set_data() requires Destroyed texture status when allocating missing pixel storage"
342                );
343                sys::ImTextureData_Create(
344                    self.as_raw_mut(),
345                    (*raw).Format,
346                    (*raw).Width,
347                    (*raw).Height,
348                );
349            }
350
351            let copy_bytes = std::cmp::min(needed, data.len());
352            if copy_bytes == 0 {
353                return;
354            }
355
356            std::ptr::copy_nonoverlapping(data.as_ptr(), (*raw).Pixels as *mut u8, copy_bytes);
357
358            // Mark entire texture as updated
359            (*raw).UpdateRect = sys::ImTextureRect {
360                x: 0u16,
361                y: 0u16,
362                w: (*raw).Width.clamp(0, u16::MAX as i32) as u16,
363                h: (*raw).Height.clamp(0, u16::MAX as i32) as u16,
364            };
365            sys::ImTextureData_SetStatus(raw, sys::ImTextureStatus_WantUpdates);
366        }
367    }
368
369    /// Set the width of the texture
370    pub fn set_width(&mut self, width: u32) {
371        self.assert_metadata_mutation_allowed("TextureData::set_width()");
372        assert!(width > 0, "TextureData::set_width() width must be positive");
373        let width =
374            i32::try_from(width).expect("TextureData::set_width() width exceeded i32 range");
375        self.inner_mut().Width = width;
376    }
377
378    /// Set the height of the texture
379    pub fn set_height(&mut self, height: u32) {
380        self.assert_metadata_mutation_allowed("TextureData::set_height()");
381        assert!(
382            height > 0,
383            "TextureData::set_height() height must be positive"
384        );
385        let height =
386            i32::try_from(height).expect("TextureData::set_height() height exceeded i32 range");
387        self.inner_mut().Height = height;
388    }
389
390    /// Set the format of the texture
391    pub fn set_format(&mut self, format: TextureFormat) {
392        self.assert_metadata_mutation_allowed("TextureData::set_format()");
393        let raw = self.inner_mut();
394        raw.Format = format.into();
395        raw.BytesPerPixel = texture_format_bytes_per_pixel_i32(format);
396    }
397
398    pub(crate) fn raw_width_i32(&self) -> i32 {
399        self.inner().Width
400    }
401
402    pub(crate) fn raw_height_i32(&self) -> i32 {
403        self.inner().Height
404    }
405
406    pub(crate) fn raw_bytes_per_pixel_i32(&self) -> i32 {
407        self.inner().BytesPerPixel
408    }
409}