use crate::sys;
use std::cell::UnsafeCell;
use std::ffi::c_void;
use std::ptr::NonNull;
fn texture_format_bytes_per_pixel(format: TextureFormat) -> i32 {
match format {
TextureFormat::RGBA32 => 4,
TextureFormat::Alpha8 => 1,
}
}
fn checked_texture_byte_len(caller: &str, width: i32, height: i32, bytes_per_pixel: i32) -> usize {
assert!(width > 0, "{caller} width must be positive");
assert!(height > 0, "{caller} height must be positive");
assert!(
bytes_per_pixel > 0,
"{caller} bytes_per_pixel must be positive"
);
let width = usize::try_from(width).expect("positive width must fit usize");
let height = usize::try_from(height).expect("positive height must fit usize");
let bytes_per_pixel =
usize::try_from(bytes_per_pixel).expect("positive bytes_per_pixel must fit usize");
let size = width
.checked_mul(height)
.and_then(|size| size.checked_mul(bytes_per_pixel))
.expect("texture byte size overflowed usize");
assert!(
size <= i32::MAX as usize,
"{caller} texture byte size must fit Dear ImGui's signed int allocation path"
);
size
}
fn checked_texture_byte_len_if_valid(
caller: &str,
width: i32,
height: i32,
bytes_per_pixel: i32,
) -> Option<usize> {
if width <= 0 || height <= 0 || bytes_per_pixel <= 0 {
return None;
}
Some(checked_texture_byte_len(
caller,
width,
height,
bytes_per_pixel,
))
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct TextureId(u64);
impl TextureId {
#[inline]
pub const fn new(id: u64) -> Self {
Self(id)
}
#[inline]
pub const fn id(self) -> u64 {
self.0
}
#[inline]
pub const fn null() -> Self {
Self(0)
}
#[inline]
pub const fn is_null(self) -> bool {
self.0 == 0
}
pub fn try_as_usize(self) -> Option<usize> {
usize::try_from(self.0).ok()
}
pub fn try_as_ptr<T>(self) -> Option<*const T> {
self.try_as_usize().map(|value| value as *const T)
}
pub fn try_as_mut_ptr<T>(self) -> Option<*mut T> {
self.try_as_usize().map(|value| value as *mut T)
}
}
impl From<u64> for TextureId {
#[inline]
fn from(id: u64) -> Self {
TextureId(id)
}
}
impl<T> From<*const T> for TextureId {
#[inline]
fn from(ptr: *const T) -> Self {
TextureId(ptr as usize as u64)
}
}
impl<T> From<*mut T> for TextureId {
#[inline]
fn from(ptr: *mut T) -> Self {
TextureId(ptr as usize as u64)
}
}
impl From<usize> for TextureId {
#[inline]
fn from(id: usize) -> Self {
TextureId(id as u64)
}
}
impl Default for TextureId {
#[inline]
fn default() -> Self {
Self::null()
}
}
pub type RawTextureId = sys::ImTextureID;
impl From<TextureId> for RawTextureId {
#[inline]
fn from(id: TextureId) -> Self {
id.id() as sys::ImTextureID
}
}
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct TextureRef(sys::ImTextureRef);
const _: [(); std::mem::size_of::<sys::ImTextureRef>()] = [(); std::mem::size_of::<TextureRef>()];
const _: [(); std::mem::align_of::<sys::ImTextureRef>()] = [(); std::mem::align_of::<TextureRef>()];
impl TextureRef {
#[inline]
pub fn from_raw(raw: sys::ImTextureRef) -> Self {
Self(raw)
}
#[inline]
pub fn raw(self) -> sys::ImTextureRef {
self.0
}
}
impl From<TextureId> for TextureRef {
#[inline]
fn from(id: TextureId) -> Self {
TextureRef(sys::ImTextureRef {
_TexData: std::ptr::null_mut(),
_TexID: id.id() as sys::ImTextureID,
})
}
}
impl From<u64> for TextureRef {
#[inline]
fn from(id: u64) -> Self {
TextureRef::from(TextureId::from(id))
}
}
impl From<&TextureData> for TextureRef {
#[inline]
fn from(td: &TextureData) -> Self {
TextureRef(sys::ImTextureRef {
_TexData: std::ptr::null_mut(),
_TexID: td.tex_id().id() as sys::ImTextureID,
})
}
}
impl From<&mut TextureData> for TextureRef {
#[inline]
fn from(td: &mut TextureData) -> Self {
TextureRef(sys::ImTextureRef {
_TexData: td.as_raw_mut(),
_TexID: 0,
})
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum TextureFormat {
RGBA32 = sys::ImTextureFormat_RGBA32 as i32,
Alpha8 = sys::ImTextureFormat_Alpha8 as i32,
}
impl From<sys::ImTextureFormat> for TextureFormat {
fn from(format: sys::ImTextureFormat) -> Self {
match format {
sys::ImTextureFormat_RGBA32 => TextureFormat::RGBA32,
sys::ImTextureFormat_Alpha8 => TextureFormat::Alpha8,
_ => TextureFormat::RGBA32, }
}
}
impl From<TextureFormat> for sys::ImTextureFormat {
fn from(format: TextureFormat) -> Self {
format as sys::ImTextureFormat
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum TextureStatus {
OK = sys::ImTextureStatus_OK as i32,
Destroyed = sys::ImTextureStatus_Destroyed as i32,
WantCreate = sys::ImTextureStatus_WantCreate as i32,
WantUpdates = sys::ImTextureStatus_WantUpdates as i32,
WantDestroy = sys::ImTextureStatus_WantDestroy as i32,
}
impl From<sys::ImTextureStatus> for TextureStatus {
fn from(status: sys::ImTextureStatus) -> Self {
match status {
sys::ImTextureStatus_OK => TextureStatus::OK,
sys::ImTextureStatus_Destroyed => TextureStatus::Destroyed,
sys::ImTextureStatus_WantCreate => TextureStatus::WantCreate,
sys::ImTextureStatus_WantUpdates => TextureStatus::WantUpdates,
sys::ImTextureStatus_WantDestroy => TextureStatus::WantDestroy,
_ => TextureStatus::Destroyed, }
}
}
impl From<TextureStatus> for sys::ImTextureStatus {
fn from(status: TextureStatus) -> Self {
status as sys::ImTextureStatus
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct TextureRect {
pub x: u16,
pub y: u16,
pub w: u16,
pub h: u16,
}
impl From<sys::ImTextureRect> for TextureRect {
fn from(rect: sys::ImTextureRect) -> Self {
Self {
x: rect.x,
y: rect.y,
w: rect.w,
h: rect.h,
}
}
}
impl From<TextureRect> for sys::ImTextureRect {
fn from(rect: TextureRect) -> Self {
Self {
x: rect.x,
y: rect.y,
w: rect.w,
h: rect.h,
}
}
}
pub struct OwnedTextureData {
raw: NonNull<sys::ImTextureData>,
}
impl OwnedTextureData {
pub fn new() -> Self {
let raw = unsafe { sys::ImTextureData_ImTextureData() };
let raw = NonNull::new(raw).expect("ImTextureData_ImTextureData() returned null");
Self { raw }
}
pub fn into_raw(self) -> *mut sys::ImTextureData {
let raw = self.raw.as_ptr();
std::mem::forget(self);
raw
}
pub unsafe fn from_raw_owned(raw: *mut sys::ImTextureData) -> Self {
let raw = NonNull::new(raw).expect("raw ImTextureData pointer was null");
Self { raw }
}
}
impl Drop for OwnedTextureData {
fn drop(&mut self) {
crate::context::unregister_user_texture_from_all_contexts(self.raw.as_ptr());
unsafe { sys::ImTextureData_destroy(self.raw.as_ptr()) }
}
}
impl std::ops::Deref for OwnedTextureData {
type Target = TextureData;
fn deref(&self) -> &Self::Target {
unsafe { &*(self.raw.as_ptr() as *const TextureData) }
}
}
impl std::ops::DerefMut for OwnedTextureData {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *(self.raw.as_ptr() as *mut TextureData) }
}
}
impl AsRef<TextureData> for OwnedTextureData {
fn as_ref(&self) -> &TextureData {
self
}
}
impl AsMut<TextureData> for OwnedTextureData {
fn as_mut(&mut self) -> &mut TextureData {
self
}
}
#[repr(transparent)]
pub struct TextureData {
raw: UnsafeCell<sys::ImTextureData>,
}
const _: [(); std::mem::size_of::<sys::ImTextureData>()] = [(); std::mem::size_of::<TextureData>()];
const _: [(); std::mem::align_of::<sys::ImTextureData>()] =
[(); std::mem::align_of::<TextureData>()];
impl TextureData {
#[inline]
fn inner(&self) -> &sys::ImTextureData {
unsafe { &*self.raw.get() }
}
#[inline]
fn inner_mut(&mut self) -> &mut sys::ImTextureData {
unsafe { &mut *self.raw.get() }
}
fn assert_metadata_mutation_allowed(&self, caller: &str) {
let raw = self.inner();
assert!(
raw.Pixels.is_null(),
"{caller} cannot change texture metadata while pixel storage is allocated"
);
assert!(
raw.Status == sys::ImTextureStatus_Destroyed,
"{caller} requires Destroyed texture status"
);
}
pub fn new() -> OwnedTextureData {
OwnedTextureData::new()
}
pub(crate) unsafe fn from_raw<'a>(raw: *mut sys::ImTextureData) -> &'a mut Self {
unsafe { &mut *(raw as *mut Self) }
}
pub(crate) unsafe fn from_raw_ref<'a>(raw: *const sys::ImTextureData) -> &'a Self {
unsafe { &*(raw as *const Self) }
}
pub fn as_raw(&self) -> *const sys::ImTextureData {
self.raw.get() as *const _
}
pub fn as_raw_mut(&mut self) -> *mut sys::ImTextureData {
self.raw.get()
}
pub fn unique_id(&self) -> i32 {
self.inner().UniqueID
}
pub fn status(&self) -> TextureStatus {
TextureStatus::from(self.inner().Status)
}
pub fn set_status(&mut self, status: TextureStatus) {
unsafe {
if status == TextureStatus::Destroyed {
sys::ImTextureData_SetTexID(self.as_raw_mut(), 0 as sys::ImTextureID);
(*self.as_raw_mut()).BackendUserData = std::ptr::null_mut();
}
sys::ImTextureData_SetStatus(self.as_raw_mut(), status.into());
}
}
pub fn backend_user_data(&self) -> *mut c_void {
self.inner().BackendUserData
}
pub fn set_backend_user_data(&mut self, data: *mut c_void) {
self.inner_mut().BackendUserData = data;
}
pub fn tex_id(&self) -> TextureId {
TextureId::from(self.inner().TexID)
}
pub fn set_tex_id(&mut self, tex_id: TextureId) {
unsafe {
sys::ImTextureData_SetTexID(self.as_raw_mut(), tex_id.id() as sys::ImTextureID);
}
}
#[inline]
pub fn texture_ref(&mut self) -> TextureRef {
unsafe { TextureRef::from_raw(sys::ImTextureData_GetTexRef(self.as_raw_mut())) }
}
pub fn format(&self) -> TextureFormat {
TextureFormat::from(self.inner().Format)
}
pub fn width(&self) -> i32 {
self.inner().Width
}
pub fn height(&self) -> i32 {
self.inner().Height
}
pub fn bytes_per_pixel(&self) -> i32 {
self.inner().BytesPerPixel
}
pub fn unused_frames(&self) -> i32 {
self.inner().UnusedFrames
}
pub fn ref_count(&self) -> u16 {
self.inner().RefCount
}
pub fn use_colors(&self) -> bool {
self.inner().UseColors
}
pub fn want_destroy_next_frame(&self) -> bool {
self.inner().WantDestroyNextFrame
}
pub fn pixels(&self) -> Option<&[u8]> {
let raw = self.inner();
if raw.Pixels.is_null() {
None
} else {
let width = raw.Width;
let height = raw.Height;
let bytes_per_pixel = raw.BytesPerPixel;
if width <= 0 || height <= 0 || bytes_per_pixel <= 0 {
return None;
}
let size = (width as usize)
.checked_mul(height as usize)?
.checked_mul(bytes_per_pixel as usize)?;
unsafe { Some(std::slice::from_raw_parts(raw.Pixels as *const u8, size)) }
}
}
pub fn used_rect(&self) -> TextureRect {
TextureRect::from(self.inner().UsedRect)
}
pub fn update_rect(&self) -> TextureRect {
TextureRect::from(self.inner().UpdateRect)
}
pub fn updates(&self) -> impl Iterator<Item = TextureRect> + '_ {
let vec = &self.inner().Updates;
let count = if vec.Data.is_null() {
0
} else {
usize::try_from(vec.Size).unwrap_or(0)
};
let data = vec.Data as *const sys::ImTextureRect;
(0..count).map(move |i| unsafe { TextureRect::from(*data.add(i)) })
}
pub fn pixels_at(&self, x: i32, y: i32) -> Option<&[u8]> {
let raw = self.inner();
let width = raw.Width;
let height = raw.Height;
let bytes_per_pixel = raw.BytesPerPixel;
if raw.Pixels.is_null()
|| width <= 0
|| height <= 0
|| bytes_per_pixel <= 0
|| x < 0
|| y < 0
|| x >= width
|| y >= height
{
None
} else {
let width_usize = width as usize;
let x_usize = x as usize;
let y_usize = y as usize;
let bpp_usize = bytes_per_pixel as usize;
let total_size = width_usize
.checked_mul(height as usize)?
.checked_mul(bpp_usize)?;
let offset_px = y_usize.checked_mul(width_usize)?.checked_add(x_usize)?;
let offset_bytes = offset_px.checked_mul(bpp_usize)?;
let remaining_size = total_size.checked_sub(offset_bytes)?;
unsafe {
let ptr = (raw.Pixels as *const u8).add(offset_bytes);
Some(std::slice::from_raw_parts(ptr, remaining_size))
}
}
}
pub fn pitch(&self) -> i32 {
let width = self.width();
let bytes_per_pixel = self.bytes_per_pixel();
if width <= 0 || bytes_per_pixel <= 0 {
return 0;
}
width
.checked_mul(bytes_per_pixel)
.expect("TextureData::pitch() byte pitch overflowed i32")
}
pub fn create(&mut self, format: TextureFormat, width: i32, height: i32) {
assert!(
self.status() == TextureStatus::Destroyed,
"TextureData::create() requires Destroyed texture status"
);
let bytes_per_pixel = texture_format_bytes_per_pixel(format);
let _ = checked_texture_byte_len("TextureData::create()", width, height, bytes_per_pixel);
unsafe {
sys::ImTextureData_Create(self.as_raw_mut(), format.into(), width, height);
}
}
pub fn destroy_pixels(&mut self) {
unsafe {
sys::ImTextureData_DestroyPixels(self.as_raw_mut());
}
}
pub fn set_data(&mut self, data: &[u8]) {
unsafe {
let raw = self.as_raw_mut();
let Some(needed) = checked_texture_byte_len_if_valid(
"TextureData::set_data()",
(*raw).Width,
(*raw).Height,
(*raw).BytesPerPixel,
) else {
return;
};
if (*raw).Pixels.is_null() {
assert!(
(*raw).Status == sys::ImTextureStatus_Destroyed,
"TextureData::set_data() requires Destroyed texture status when allocating missing pixel storage"
);
sys::ImTextureData_Create(
self.as_raw_mut(),
(*raw).Format,
(*raw).Width,
(*raw).Height,
);
}
let copy_bytes = std::cmp::min(needed, data.len());
if copy_bytes == 0 {
return;
}
std::ptr::copy_nonoverlapping(data.as_ptr(), (*raw).Pixels as *mut u8, copy_bytes);
(*raw).UpdateRect = sys::ImTextureRect {
x: 0u16,
y: 0u16,
w: (*raw).Width.clamp(0, u16::MAX as i32) as u16,
h: (*raw).Height.clamp(0, u16::MAX as i32) as u16,
};
sys::ImTextureData_SetStatus(raw, sys::ImTextureStatus_WantUpdates);
}
}
pub fn set_width(&mut self, width: u32) {
self.assert_metadata_mutation_allowed("TextureData::set_width()");
assert!(width > 0, "TextureData::set_width() width must be positive");
let width =
i32::try_from(width).expect("TextureData::set_width() width exceeded i32 range");
self.inner_mut().Width = width;
}
pub fn set_height(&mut self, height: u32) {
self.assert_metadata_mutation_allowed("TextureData::set_height()");
assert!(
height > 0,
"TextureData::set_height() height must be positive"
);
let height =
i32::try_from(height).expect("TextureData::set_height() height exceeded i32 range");
self.inner_mut().Height = height;
}
pub fn set_format(&mut self, format: TextureFormat) {
self.assert_metadata_mutation_allowed("TextureData::set_format()");
let raw = self.inner_mut();
raw.Format = format.into();
raw.BytesPerPixel = texture_format_bytes_per_pixel(format);
}
}
pub fn get_format_bytes_per_pixel(format: TextureFormat) -> i32 {
unsafe { sys::igImTextureDataGetFormatBytesPerPixel(format.into()) }
}
pub fn create_texture_ref(texture_id: u64) -> sys::ImTextureRef {
sys::ImTextureRef {
_TexData: std::ptr::null_mut(),
_TexID: texture_id,
}
}
pub fn get_status_name(status: TextureStatus) -> &'static str {
unsafe {
let ptr = sys::igImTextureDataGetStatusName(status.into());
if ptr.is_null() {
"Unknown"
} else {
std::ffi::CStr::from_ptr(ptr).to_str().unwrap_or("Invalid")
}
}
}
pub fn get_format_name(format: TextureFormat) -> &'static str {
unsafe {
let ptr = sys::igImTextureDataGetFormatName(format.into());
if ptr.is_null() {
"Unknown"
} else {
std::ffi::CStr::from_ptr(ptr).to_str().unwrap_or("Invalid")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn texture_id_try_as_usize_reports_overflow() {
assert_eq!(TextureId::new(42).try_as_usize(), Some(42));
if std::mem::size_of::<usize>() < std::mem::size_of::<u64>() {
assert_eq!(TextureId::new(u64::MAX).try_as_usize(), None);
}
}
#[test]
fn texture_create_rejects_invalid_sizes_and_status_before_ffi() {
let mut texture = TextureData::new();
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
texture.create(TextureFormat::RGBA32, 0, 1);
}))
.is_err()
);
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
texture.create(TextureFormat::RGBA32, -1, 1);
}))
.is_err()
);
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
texture.create(TextureFormat::RGBA32, i32::MAX, 2);
}))
.is_err()
);
texture.create(TextureFormat::RGBA32, 1, 1);
assert_eq!(texture.status(), TextureStatus::WantCreate);
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
texture.create(TextureFormat::RGBA32, 1, 1);
}))
.is_err()
);
}
#[test]
fn texture_metadata_setters_are_destroyed_only_and_keep_bpp_in_sync() {
let mut texture = TextureData::new();
texture.set_width(4);
texture.set_height(3);
texture.set_format(TextureFormat::Alpha8);
assert_eq!(texture.width(), 4);
assert_eq!(texture.height(), 3);
assert_eq!(texture.format(), TextureFormat::Alpha8);
assert_eq!(texture.bytes_per_pixel(), 1);
assert_eq!(texture.pitch(), 4);
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
texture.set_width(0);
}))
.is_err()
);
texture.create(TextureFormat::RGBA32, 1, 1);
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
texture.set_width(2);
}))
.is_err()
);
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
texture.set_height(2);
}))
.is_err()
);
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
texture.set_format(TextureFormat::Alpha8);
}))
.is_err()
);
}
#[test]
fn set_data_checks_byte_count_before_allocating_or_copying() {
let mut texture = TextureData::new();
unsafe {
let raw = texture.as_raw_mut();
(*raw).Format = sys::ImTextureFormat_RGBA32;
(*raw).Width = i32::MAX;
(*raw).Height = 2;
(*raw).BytesPerPixel = 4;
}
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
texture.set_data(&[0; 4]);
}))
.is_err()
);
let mut texture = TextureData::new();
unsafe {
let raw = texture.as_raw_mut();
(*raw).Format = sys::ImTextureFormat_RGBA32;
(*raw).Width = 1;
(*raw).Height = 1;
(*raw).BytesPerPixel = 4;
(*raw).Status = sys::ImTextureStatus_WantCreate;
}
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
texture.set_data(&[1, 2, 3, 4]);
}))
.is_err()
);
assert!(unsafe { (*texture.as_raw()).Pixels.is_null() });
}
}