#![warn(clippy::clone_on_ref_ptr)]
use std::{
any, fmt,
marker::PhantomData,
path::Path,
rc::Rc,
sync::atomic::{AtomicBool, Ordering},
};
use static_assertions::assert_not_impl_any;
use glutin::{EventsLoop, Window, WindowBuilder};
use image::RgbaImage;
mod backend;
pub mod color;
mod error;
pub mod target;
pub use error::*;
pub use glutin;
pub use image;
use backend::{tex::RawTexture, Backend};
#[derive(Clone, Copy)]
struct SkipDebug<T>(T);
impl<T> fmt::Debug for SkipDebug<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SkipDebug<{}>", any::type_name::<T>())
}
}
pub trait DrawTarget {
fn receive_draw(
&mut self,
ctx: &mut Context,
texture: &Texture,
position: (i32, i32),
config: &DrawConfig,
) -> Result<(), ErrDontCare>;
fn receive_clear_color(
&mut self,
ctx: &mut Context,
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare>;
fn receive_line(
&mut self,
ctx: &mut Context,
from: (i32, i32),
to: (i32, i32),
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare>;
fn receive_rectangle(
&mut self,
ctx: &mut Context,
lower_left: (i32, i32),
upper_right: (i32, i32),
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare>;
}
impl<T: DrawTarget> DrawTarget for &mut T {
fn receive_draw(
&mut self,
ctx: &mut Context,
texture: &Texture,
position: (i32, i32),
config: &DrawConfig,
) -> Result<(), ErrDontCare> {
<T>::receive_draw(self, ctx, texture, position, config)
}
fn receive_clear_color(
&mut self,
ctx: &mut Context,
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare> {
<T>::receive_clear_color(self, ctx, color)
}
fn receive_line(
&mut self,
ctx: &mut Context,
from: (i32, i32),
to: (i32, i32),
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare> {
<T>::receive_line(self, ctx, from, to, color)
}
fn receive_rectangle(
&mut self,
ctx: &mut Context,
lower_left: (i32, i32),
upper_right: (i32, i32),
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare> {
<T>::receive_rectangle(self, ctx, lower_left, upper_right, color)
}
}
static INITIALIZED: AtomicBool = AtomicBool::new(false);
#[derive(Debug)]
pub struct Context {
backend: Backend,
}
assert_not_impl_any!(Context: Send, Sync, Clone);
impl Context {
pub fn new(window: WindowBuilder, events_loop: EventsLoop) -> Result<Self, ErrDontCare> {
if INITIALIZED.compare_and_swap(false, true, Ordering::AcqRel) {
panic!("Tried to initialize a second Context");
}
let backend = Backend::initialize(window, events_loop)?;
Ok(Self { backend })
}
pub fn window_dimensions(&self) -> (u32, u32) {
self.backend.window_dimensions()
}
pub fn window_width(&self) -> u32 {
self.window_dimensions().0
}
pub fn window_height(&self) -> u32 {
self.window_dimensions().1
}
pub fn resize_window(&mut self, width: u32, height: u32) {
self.backend.resize_window(width, height)
}
pub fn window_surface(&self) -> WindowSurface {
WindowSurface {
_marker: PhantomData,
}
}
pub fn draw<T>(
&mut self,
target: &mut T,
source: &Texture,
position: (i32, i32),
config: &DrawConfig,
) -> Result<(), ErrDontCare>
where
T: DrawTarget,
{
target.receive_draw(self, source, position, config)
}
pub fn debug_line<T>(
&mut self,
target: &mut T,
from: (i32, i32),
to: (i32, i32),
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare>
where
T: DrawTarget,
{
target.receive_line(self, from, to, color)
}
pub fn debug_rectangle<T>(
&mut self,
target: &mut T,
lower_left: (i32, i32),
upper_right: (i32, i32),
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare>
where
T: DrawTarget,
{
target.receive_rectangle(self, lower_left, upper_right, color)
}
pub fn clear_color<T>(
&mut self,
target: &mut T,
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare>
where
T: DrawTarget,
{
target.receive_clear_color(self, color)
}
pub fn take_screenshot(&mut self) -> RgbaImage {
let (width, height) = self.window_dimensions();
let data = self.backend.take_screenshot((width, height));
let reversed_data = data
.chunks(width as usize * 4)
.rev()
.flat_map(|row| row.iter())
.copied()
.collect();
RgbaImage::from_vec(width, height, reversed_data).unwrap()
}
pub fn window(&self) -> &Window {
self.backend.window()
}
pub fn events_loop(&mut self) -> &mut EventsLoop {
self.backend.events_loop()
}
pub fn finalize_frame(&mut self) -> Result<(), ErrDontCare> {
self.backend.finalize_frame()
}
pub unsafe fn unlock_unchecked(self) {
INITIALIZED.store(false, Ordering::Release);
}
}
#[derive(Debug)]
pub struct WindowSurface {
_marker: PhantomData<*const ()>,
}
assert_not_impl_any!(WindowSurface: Send, Sync);
impl DrawTarget for WindowSurface {
fn receive_draw(
&mut self,
ctx: &mut Context,
texture: &Texture,
position: (i32, i32),
config: &DrawConfig,
) -> Result<(), ErrDontCare> {
let dim = ctx.backend.window_dimensions();
ctx.backend.draw(
0,
dim,
&texture.inner,
texture.position,
texture.size,
position,
config,
)
}
fn receive_clear_color(
&mut self,
ctx: &mut Context,
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare> {
ctx.backend.clear_color(0, color)
}
fn receive_line(
&mut self,
ctx: &mut Context,
from: (i32, i32),
to: (i32, i32),
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare> {
let dim = ctx.backend.window_dimensions();
ctx.backend.debug_draw(false, 0, dim, from, to, color)
}
fn receive_rectangle(
&mut self,
ctx: &mut Context,
lower_left: (i32, i32),
upper_right: (i32, i32),
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare> {
let dim = ctx.backend.window_dimensions();
ctx.backend
.debug_draw(true, 0, dim, lower_left, upper_right, color)
}
}
#[derive(Debug, Clone)]
pub struct Texture {
inner: Rc<RawTexture>,
position: (u32, u32),
size: (u32, u32),
}
assert_not_impl_any!(Texture: Send, Sync);
impl Texture {
fn from_raw(raw: RawTexture) -> Self {
let size = raw.dimensions;
Texture {
inner: Rc::new(raw),
position: (0, 0),
size,
}
}
pub fn new(ctx: &mut Context, dimensions: (u32, u32)) -> Result<Self, ErrDontCare> {
let raw = RawTexture::new(&mut ctx.backend, dimensions)?;
Ok(Self::from_raw(raw))
}
pub fn from_image(ctx: &mut Context, image: RgbaImage) -> Result<Self, ErrDontCare> {
let raw = RawTexture::from_image(&mut ctx.backend, image)?;
Ok(Self::from_raw(raw))
}
pub fn load<P: AsRef<Path>>(ctx: &mut Context, path: P) -> Result<Texture, LoadTextureError> {
let raw = RawTexture::load(&mut ctx.backend, path)?;
Ok(Self::from_raw(raw))
}
pub fn get_section(&self, position: (u32, u32), size: (u32, u32)) -> Texture {
assert!(
position.0 + size.0 <= self.size.0,
"invalid section width: {} + {} > {}",
position.0,
size.0,
self.size.0
);
assert!(
position.1 + size.1 <= self.size.1,
"invalid section heigth: {} + {} > {}",
position.1,
size.1,
self.size.1
);
Texture {
inner: Rc::clone(&self.inner),
position: (self.position.0 + position.0, self.position.1 + position.1),
size,
}
}
pub fn dimensions(&self) -> (u32, u32) {
self.size
}
pub fn width(&self) -> u32 {
self.size.0
}
pub fn height(&self) -> u32 {
self.size.1
}
fn prepare_as_draw_target<'a>(
&'a mut self,
ctx: &mut Context,
) -> Result<&'a mut RawTexture, ErrDontCare> {
if self.position != (0, 0) || self.size != self.inner.dimensions {
let mut inner = RawTexture::new(&mut ctx.backend, self.size)?;
inner.add_framebuffer(&mut ctx.backend)?;
ctx.backend.draw(
inner.frame_buffer_id,
self.size,
&self.inner,
self.position,
self.size,
(0, 0),
&Default::default(),
)?;
self.inner = Rc::new(inner);
} else if let Some(inner) = Rc::get_mut(&mut self.inner) {
if !inner.is_framebuffer {
inner.add_framebuffer(&mut ctx.backend)?;
}
} else {
self.inner = Rc::new(RawTexture::clone_as_target(&self.inner, &mut ctx.backend)?);
}
Rc::get_mut(&mut self.inner).ok_or_else(|| unreachable!())
}
pub fn get_image_data(&self, ctx: &mut Context) -> RgbaImage {
let _ = ctx;
let data = ctx.backend.get_image_data(&self.inner);
let (width, height) = self.inner.dimensions;
let skip_above = height - (self.position.1 + self.size.1);
let skip_vertical = self.position.0 * 4;
let take_vertical = self.size.0 * 4;
let image_data = data
.chunks(width as usize * 4)
.skip(skip_above as usize)
.rev()
.skip(self.position.1 as usize)
.flat_map(|row| {
row.iter()
.skip(skip_vertical as usize)
.take(take_vertical as usize)
})
.copied()
.collect();
RgbaImage::from_vec(self.size.0, self.size.1, image_data).unwrap()
}
pub fn clear_depth(&mut self, ctx: &mut Context) -> Result<(), ErrDontCare> {
let target = self.prepare_as_draw_target(ctx)?;
ctx.backend.clear_texture_depth(target)
}
}
impl DrawTarget for Texture {
fn receive_draw(
&mut self,
ctx: &mut Context,
texture: &Texture,
position: (i32, i32),
config: &DrawConfig,
) -> Result<(), ErrDontCare> {
let target = self.prepare_as_draw_target(ctx)?;
ctx.backend.draw(
target.frame_buffer_id,
target.dimensions,
&texture.inner,
texture.position,
texture.size,
position,
config,
)
}
fn receive_clear_color(
&mut self,
ctx: &mut Context,
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare> {
let target = self.prepare_as_draw_target(ctx)?;
ctx.backend.clear_color(target.frame_buffer_id, color)
}
fn receive_line(
&mut self,
ctx: &mut Context,
from: (i32, i32),
to: (i32, i32),
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare> {
let target = self.prepare_as_draw_target(ctx)?;
ctx.backend.debug_draw(
false,
target.frame_buffer_id,
target.dimensions,
from,
to,
color,
)
}
fn receive_rectangle(
&mut self,
ctx: &mut Context,
lower_left: (i32, i32),
upper_right: (i32, i32),
color: (f32, f32, f32, f32),
) -> Result<(), ErrDontCare> {
let target = self.prepare_as_draw_target(ctx)?;
ctx.backend.debug_draw(
true,
target.frame_buffer_id,
target.dimensions,
lower_left,
upper_right,
color,
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BlendMode {
Alpha,
Additive,
}
impl Default for BlendMode {
fn default() -> Self {
BlendMode::Alpha
}
}
#[derive(Debug, Clone)]
pub struct DrawConfig {
pub scale: (u32, u32),
pub flip_vertically: bool,
pub flip_horizontally: bool,
pub depth: Option<f32>,
pub color_modulation: [[f32; 4]; 4],
pub invert_color: bool,
pub blend_mode: BlendMode,
#[doc(hidden)]
pub __non_exhaustive: (),
}
impl Default for DrawConfig {
fn default() -> Self {
Self {
scale: (1, 1),
depth: None,
color_modulation: color::IDENTITY,
invert_color: false,
flip_vertically: false,
flip_horizontally: false,
blend_mode: Default::default(),
__non_exhaustive: (),
}
}
}