use super::validation::{
TextureLayout, non_negative_texture_count_from_i32, require_exact_payload,
validate_native_texture_layout,
};
use super::{
TextureDataError, TextureFormat, TextureId, TextureRect, TextureRegion, TextureStatus,
TextureSubresource,
};
use crate::sys;
use std::cell::UnsafeCell;
use std::ffi::c_void;
#[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]
pub(super) fn inner(&self) -> &sys::ImTextureData {
unsafe { &*self.raw.get() }
}
#[inline]
pub(super) fn inner_mut(&mut self) -> &mut sys::ImTextureData {
unsafe { &mut *self.raw.get() }
}
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(crate) fn native_unique_id(&self) -> i32 {
self.inner().UniqueID
}
pub fn status(&self) -> TextureStatus {
TextureStatus::from(self.inner().Status)
}
pub(crate) fn claim_managed_queue(&mut self) {
let raw = self.as_raw_mut();
let marker = raw.cast::<c_void>();
unsafe {
assert!(
(*raw).QueueUserData.is_null() || (*raw).QueueUserData == marker,
"managed texture is already retained by a different native queue"
);
(*raw).QueueUserData = marker;
}
}
pub unsafe 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();
(*self.as_raw_mut()).QueueUserData = 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 unsafe 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 unsafe fn set_tex_id(&mut self, tex_id: TextureId) {
unsafe {
sys::ImTextureData_SetTexID(self.as_raw_mut(), sys::ImTextureID::from(tex_id));
}
}
pub fn format(&self) -> TextureFormat {
TextureFormat::from(self.inner().Format)
}
pub fn width(&self) -> u32 {
u32::try_from(self.raw_width_i32()).unwrap_or(0)
}
pub fn height(&self) -> u32 {
u32::try_from(self.raw_height_i32()).unwrap_or(0)
}
pub fn bytes_per_pixel(&self) -> usize {
usize::try_from(self.raw_bytes_per_pixel_i32()).unwrap_or(0)
}
pub fn unused_frames(&self) -> usize {
non_negative_texture_count_from_i32(
"TextureData::unused_frames()",
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: u32, y: u32) -> Option<&[u8]> {
let raw = self.inner();
let width = u32::try_from(raw.Width).ok()?;
let height = u32::try_from(raw.Height).ok()?;
let bytes_per_pixel = usize::try_from(raw.BytesPerPixel).ok()?;
if raw.Pixels.is_null() || width == 0 || height == 0 || bytes_per_pixel == 0 {
return None;
}
if x >= width || y >= height {
None
} else {
let width_usize = usize::try_from(width).ok()?;
let height_usize = usize::try_from(height).ok()?;
let x_usize = usize::try_from(x).ok()?;
let y_usize = usize::try_from(y).ok()?;
let total_size = width_usize
.checked_mul(height_usize)?
.checked_mul(bytes_per_pixel)?;
let offset_px = y_usize.checked_mul(width_usize)?.checked_add(x_usize)?;
let offset_bytes = offset_px.checked_mul(bytes_per_pixel)?;
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) -> usize {
let width = self.width();
let bytes_per_pixel = self.bytes_per_pixel();
if width == 0 || bytes_per_pixel == 0 {
return 0;
}
usize::try_from(width)
.expect("TextureData::pitch() width must fit usize")
.checked_mul(bytes_per_pixel)
.expect("TextureData::pitch() byte pitch overflowed usize")
}
pub fn replace_pixels(&mut self, pixels: &[u8]) -> Result<(), TextureDataError> {
let raw = self.inner();
let layout = validate_native_texture_layout(raw.Width, raw.Height, raw.BytesPerPixel)?;
require_exact_payload(layout.byte_len, pixels.len())?;
let status = validate_mutable_texture(raw)?;
let queued_rect = if status == TextureStatus::WantCreate {
None
} else {
let region =
TextureRegion::from_validated_dimensions(0, 0, layout.width, layout.height);
Some(
validate_live_update_region(raw, layout, region).map_err(|error| match error {
TextureDataError::UpdateRegionNotRepresentable(_) => {
TextureDataError::FullUpdateRectOutOfRange {
width: layout.width,
height: layout.height,
}
}
other => other,
})?,
)
};
unsafe {
std::ptr::copy_nonoverlapping(
pixels.as_ptr(),
self.inner().Pixels.cast::<u8>(),
layout.byte_len,
);
}
if let Some(rect) = queued_rect {
queue_texture_upload(self.as_raw_mut(), rect);
}
Ok(())
}
pub fn update_subresource(
&mut self,
update: TextureSubresource<'_>,
) -> Result<(), TextureDataError> {
let raw = self.inner();
let layout = validate_native_texture_layout(raw.Width, raw.Height, raw.BytesPerPixel)?;
let status = validate_mutable_texture(raw)?;
let validated = validate_subresource(raw, layout, update, status)?;
let destination = unsafe {
std::slice::from_raw_parts_mut(self.inner().Pixels.cast::<u8>(), layout.byte_len)
};
let region = update.region();
let row_count = usize::try_from(region.height()).expect("validated height must fit usize");
let y = usize::try_from(region.y()).expect("validated y must fit usize");
for row in 0..row_count {
let source_start = row * update.row_pitch();
let destination_start = (y + row) * layout.row_pitch + validated.x_bytes;
destination[destination_start..destination_start + validated.tight_row_bytes]
.copy_from_slice(
&update.pixels()[source_start..source_start + validated.tight_row_bytes],
);
}
if let Some(rect) = validated.queued_rect {
queue_texture_upload(self.as_raw_mut(), rect);
}
Ok(())
}
pub(crate) fn raw_width_i32(&self) -> i32 {
self.inner().Width
}
pub(crate) fn raw_height_i32(&self) -> i32 {
self.inner().Height
}
pub(crate) fn raw_bytes_per_pixel_i32(&self) -> i32 {
self.inner().BytesPerPixel
}
}
#[derive(Clone, Copy, Debug)]
struct ValidatedSubresource {
x_bytes: usize,
tight_row_bytes: usize,
queued_rect: Option<sys::ImTextureRect>,
}
fn validate_mutable_texture(raw: &sys::ImTextureData) -> Result<TextureStatus, TextureDataError> {
let status = TextureStatus::from(raw.Status);
if matches!(
status,
TextureStatus::Destroyed | TextureStatus::WantDestroy
) {
return Err(TextureDataError::InvalidStatus(status));
}
if raw.Pixels.is_null() {
return Err(TextureDataError::MissingPixelStorage(status));
}
Ok(status)
}
fn validate_subresource(
raw: &sys::ImTextureData,
layout: TextureLayout,
update: TextureSubresource<'_>,
status: TextureStatus,
) -> Result<ValidatedSubresource, TextureDataError> {
let region = update.region();
validate_region_bounds(layout, region)?;
let region_width = usize::try_from(region.width()).expect("validated width must fit usize");
let region_height = usize::try_from(region.height()).expect("validated height must fit usize");
let tight_row_bytes = region_width.checked_mul(layout.bytes_per_pixel).ok_or(
TextureDataError::PayloadSizeOutOfRange {
row_pitch: update.row_pitch(),
height: region.height(),
},
)?;
if update.row_pitch() < tight_row_bytes {
return Err(TextureDataError::RowPitchTooSmall {
minimum: tight_row_bytes,
actual: update.row_pitch(),
});
}
let expected = update
.row_pitch()
.checked_mul(region_height - 1)
.and_then(|bytes| bytes.checked_add(tight_row_bytes))
.ok_or(TextureDataError::PayloadSizeOutOfRange {
row_pitch: update.row_pitch(),
height: region.height(),
})?;
require_exact_payload(expected, update.pixels().len())?;
let x = usize::try_from(region.x()).expect("validated x must fit usize");
let y = usize::try_from(region.y()).expect("validated y must fit usize");
let x_bytes =
x.checked_mul(layout.bytes_per_pixel)
.ok_or(TextureDataError::PayloadSizeOutOfRange {
row_pitch: update.row_pitch(),
height: region.height(),
})?;
let last_row = y + region_height - 1;
let destination_end = last_row
.checked_mul(layout.row_pitch)
.and_then(|offset| offset.checked_add(x_bytes))
.and_then(|offset| offset.checked_add(tight_row_bytes))
.ok_or(TextureDataError::PayloadSizeOutOfRange {
row_pitch: update.row_pitch(),
height: region.height(),
})?;
debug_assert!(destination_end <= layout.byte_len);
let queued_rect = if status == TextureStatus::WantCreate {
None
} else {
Some(validate_live_update_region(raw, layout, region)?)
};
Ok(ValidatedSubresource {
x_bytes,
tight_row_bytes,
queued_rect,
})
}
fn validate_region_bounds(
layout: TextureLayout,
region: TextureRegion,
) -> Result<(), TextureDataError> {
let right = region.x().checked_add(region.width());
let bottom = region.y().checked_add(region.height());
if right.is_none_or(|right| right > layout.width)
|| bottom.is_none_or(|bottom| bottom > layout.height)
{
return Err(TextureDataError::UpdateRegionOutOfBounds {
region,
width: layout.width,
height: layout.height,
});
}
Ok(())
}
fn validate_live_update_region(
raw: &sys::ImTextureData,
layout: TextureLayout,
region: TextureRegion,
) -> Result<sys::ImTextureRect, TextureDataError> {
validate_region_bounds(layout, region)?;
let right = region
.x()
.checked_add(region.width())
.expect("validated region endpoint must not overflow");
let bottom = region
.y()
.checked_add(region.height())
.expect("validated region endpoint must not overflow");
let native_endpoint_limit = u32::from(u16::MAX) + 1;
if right > native_endpoint_limit || bottom > native_endpoint_limit {
return Err(TextureDataError::UpdateRegionNotRepresentable(region));
}
let rect = sys::ImTextureRect {
x: u16::try_from(region.x())
.map_err(|_| TextureDataError::UpdateRegionNotRepresentable(region))?,
y: u16::try_from(region.y())
.map_err(|_| TextureDataError::UpdateRegionNotRepresentable(region))?,
w: u16::try_from(region.width())
.map_err(|_| TextureDataError::UpdateRegionNotRepresentable(region))?,
h: u16::try_from(region.height())
.map_err(|_| TextureDataError::UpdateRegionNotRepresentable(region))?,
};
if !queued_union_is_representable(raw.UpdateRect, rect, true)
|| !queued_union_is_representable(raw.UsedRect, rect, false)
{
return Err(TextureDataError::UpdateRegionNotRepresentable(region));
}
Ok(rect)
}
fn queued_union_is_representable(
existing: sys::ImTextureRect,
request: sys::ImTextureRect,
empty_axis_starts_at_zero: bool,
) -> bool {
union_axis_is_representable(
existing.x,
existing.w,
request.x,
request.w,
empty_axis_starts_at_zero,
) && union_axis_is_representable(
existing.y,
existing.h,
request.y,
request.h,
empty_axis_starts_at_zero,
)
}
fn union_axis_is_representable(
existing_start: u16,
existing_len: u16,
request_start: u16,
request_len: u16,
empty_axis_starts_at_zero: bool,
) -> bool {
let existing_start = u32::from(existing_start);
let existing_end = if empty_axis_starts_at_zero && existing_len == 0 {
0
} else {
existing_start + u32::from(existing_len)
};
let request_start = u32::from(request_start);
let request_end = request_start + u32::from(request_len);
let union_start = existing_start.min(request_start);
let union_end = existing_end.max(request_end);
union_end - union_start <= u32::from(u16::MAX)
}
fn queue_texture_upload(texture: *mut sys::ImTextureData, rect: sys::ImTextureRect) {
unsafe {
sys::igImTextureDataQueueUpload(
texture,
i32::from(rect.x),
i32::from(rect.y),
i32::from(rect.w),
i32::from(rect.h),
);
}
}