use std::future::Future;
use std::pin::{pin, Pin};
use std::task::{Poll, Waker};
use euclid::Size2D;
use qwac_sys::graphics;
mod error;
use crate::{Canvas, Pixels, Rgba};
pub use self::error::CreateTextureError;
pub trait Texture {
fn id(&self) -> i32;
fn size(&self) -> Size2D<u16, Pixels> {
let width = unsafe { graphics::texture_width(self.id()) }
.try_into()
.expect("out of range");
let height = unsafe { graphics::texture_height(self.id()) }
.try_into()
.expect("out of range");
Size2D::new(width, height)
}
}
#[derive(Debug)]
pub struct ImageTexture {
pub(crate) id: i32,
}
#[inline]
fn get_texture(texture: i32) -> Result<ImageTexture, CreateTextureError> {
if texture >= 0 {
Ok(ImageTexture { id: texture })
} else {
Err(CreateTextureError::from_code(!texture))
}
}
extern "C" fn callback_trampoline<F>(texture: i32, userdata: *mut ())
where
F: FnOnce(Result<ImageTexture, CreateTextureError>) + 'static,
{
let func = *unsafe { Box::from_raw(userdata as *mut F) };
func(get_texture(texture));
}
extern "C" fn async_callback(texture: i32, userdata: *mut ()) {
let image_future = unsafe { Pin::new_unchecked(&mut *(userdata as *mut ImageFuture)) };
image_future.set_result(get_texture(texture));
}
#[derive(Debug, Default)]
struct ImageFuture {
result: Option<Result<ImageTexture, CreateTextureError>>,
waker: Option<Waker>,
}
impl ImageFuture {
fn set_result(mut self: Pin<&mut Self>, value: Result<ImageTexture, CreateTextureError>) {
let result = self.as_mut().pin_get_result();
*result = Some(value);
if let Some(waker) = self.pin_get_waker().take() {
waker.wake();
}
}
fn pin_get_waker(self: Pin<&mut Self>) -> &mut Option<Waker> {
unsafe { &mut self.get_unchecked_mut().waker }
}
fn pin_get_result(
self: Pin<&mut Self>,
) -> &mut Option<Result<ImageTexture, CreateTextureError>> {
unsafe { &mut self.get_unchecked_mut().result }
}
fn set_waker<'a, 'b>(self: Pin<&'a mut Self>, cx_waker: &'b Waker) {
let waker = self.pin_get_waker();
if let Some(waker) = waker.as_mut() {
waker.clone_from(cx_waker);
} else {
*waker = Some(cx_waker.clone());
}
}
}
impl Future for ImageFuture {
type Output = Result<ImageTexture, CreateTextureError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll<Self::Output> {
match self.as_mut().pin_get_result().take() {
Some(result) => Poll::Ready(result),
None => {
self.set_waker(cx.waker());
Poll::Pending
}
}
}
}
impl ImageTexture {
pub async fn from_canvas<C>(canvas: &C) -> Result<Self, CreateTextureError>
where
C: Canvas,
{
let mut image_future = pin!(ImageFuture::default());
unsafe {
graphics::texture_from_canvas(
canvas.id(),
async_callback,
image_future.as_mut().get_unchecked_mut() as *mut ImageFuture as *mut (),
);
}
image_future.await
}
pub fn from_canvas_callback<C, F>(canvas: &C, callback: F)
where
C: Canvas,
F: FnOnce(Result<Self, CreateTextureError>) + 'static,
{
unsafe {
graphics::texture_from_canvas(
canvas.id(),
callback_trampoline::<F>,
Box::into_raw(Box::new(callback)) as *mut (),
);
}
}
pub async fn from_rgba<P>(
pixels: P,
width: u32,
height: u32,
) -> Result<Self, CreateTextureError>
where
P: AsRef<[u8]>,
{
let pixels = pixels.as_ref().as_ptr();
let mut image_future = pin!(ImageFuture::default());
unsafe {
graphics::texture_from_rgba(
pixels,
width.try_into().expect("out of range"),
height.try_into().expect("out of range"),
async_callback,
image_future.as_mut().get_unchecked_mut() as *mut ImageFuture as *mut (),
);
}
image_future.await
}
pub fn from_rgba_callback<P, F>(pixels: P, width: u32, height: u32, callback: F)
where
P: AsRef<[u8]>,
F: FnOnce(Result<Self, CreateTextureError>) + 'static,
{
let pixels = pixels.as_ref().as_ptr();
unsafe {
graphics::texture_from_rgba(
pixels,
width.try_into().expect("out of range"),
height.try_into().expect("out of range"),
callback_trampoline::<F>,
Box::into_raw(Box::new(callback)) as *mut (),
);
}
}
pub async fn from_file<D, M>(data: D, mime_type: M) -> Result<Self, CreateTextureError>
where
D: AsRef<[u8]>,
M: AsRef<str>,
{
let data = data.as_ref();
let data_size: i32 = data.len().try_into().expect("out of range");
let mime_type = mime_type.as_ref();
let mime_type_size: i32 = mime_type.len().try_into().expect("out of range");
let mut image_future = pin!(ImageFuture::default());
unsafe {
graphics::texture_from_file(
data.as_ptr(),
data_size,
mime_type.as_ptr(),
mime_type_size,
async_callback,
image_future.as_mut().get_unchecked_mut() as *mut ImageFuture as *mut (),
);
}
image_future.await
}
pub fn from_file_callback<D, M, F>(data: D, mime_type: M, callback: F)
where
D: AsRef<[u8]>,
M: AsRef<str>,
F: FnOnce(Result<Self, CreateTextureError>) + 'static,
{
let data = data.as_ref();
let data_size: i32 = data.len().try_into().expect("out of range");
let mime_type = mime_type.as_ref();
let mime_type_size: i32 = mime_type.len().try_into().expect("out of range");
unsafe {
graphics::texture_from_file(
data.as_ptr(),
data_size,
mime_type.as_ptr(),
mime_type_size,
callback_trampoline::<F>,
Box::into_raw(Box::new(callback)) as *mut (),
);
}
}
pub async fn from_pixels<P>(
pixels: P,
width: u32,
height: u32,
) -> Result<Self, CreateTextureError>
where
P: IntoIterator,
P::Item: Into<Rgba>,
{
let pixels: Vec<u8> = pixels
.into_iter()
.map(Into::into)
.flat_map(|color| [color.r, color.g, color.b, color.a].into_iter())
.collect();
Self::from_rgba(pixels, width, height).await
}
pub fn from_pixels_callback<P, F>(pixels: P, width: u32, height: u32, callback: F)
where
P: IntoIterator,
P::Item: Into<Rgba>,
F: FnOnce(Result<Self, CreateTextureError>) + 'static,
{
let pixels: Vec<u8> = pixels
.into_iter()
.map(Into::into)
.flat_map(|color| [color.r, color.g, color.b, color.a].into_iter())
.collect();
Self::from_rgba_callback(pixels, width, height, callback);
}
}
impl Texture for ImageTexture {
fn id(&self) -> i32 {
self.id
}
}
impl Drop for ImageTexture {
fn drop(&mut self) {
unsafe {
graphics::texture_drop(self.id);
}
}
}