use std::{fmt::Debug, rc::Rc};
use anyhow::{Result, bail};
use glam::{UVec2, Vec2};
use image::{DynamicImage, GenericImageView};
use crate::{
color::Color,
font::Text,
handle::Handle,
prelude::Transform,
renderer::{
default::DefaultRenderer,
screen::ScreenRenderer,
traits::{PostRenderer, SpriteRenderer},
},
sprite::Sprite,
text_cache::TextCache,
types::Rect,
};
const COMMANDS_BUFFER: usize = 1024;
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
pub enum ScaleMode {
#[default]
Window,
Fixed {
width: u32,
height: u32,
},
FixedHeight(u32),
FixedWidth(u32),
}
pub struct CreateTextureCommand {
pub handle: Handle,
pub data: DynamicImage,
}
impl Debug for CreateTextureCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CreateTextureCommand")
.field("data", &self.data.dimensions())
.finish()
}
}
#[derive(Debug)]
pub struct RemoveTextureCommand(pub u64);
pub struct DrawCommand {
pub renderer: Rc<dyn SpriteRenderer>,
pub renderer_data: Option<Vec<u8>>,
pub handle: Handle,
pub color: Color,
pub src: Option<Rect>,
pub transform: Transform,
pub flip_x: bool,
pub flip_y: bool,
pub anchor: Vec2,
}
impl Debug for DrawCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DrawCommand")
.field("renderer", &self.renderer.key())
.field("handle", &self.handle)
.field("color", &self.color)
.field("src", &self.src)
.field("transform", &self.transform)
.field("flip_x", &self.flip_x)
.field("flip_y", &self.flip_y)
.field("anchor", &self.anchor)
.finish()
}
}
impl DrawCommand {
pub fn new(
sprite: &Sprite,
transform: Transform,
renderer: Rc<dyn SpriteRenderer>,
renderer_data: Option<Vec<u8>>,
) -> Self {
DrawCommand {
renderer,
renderer_data,
handle: sprite.texture.clone(),
color: sprite.color,
src: sprite.src.clone(),
flip_x: sprite.flip_x,
flip_y: sprite.flip_y,
anchor: sprite.anchor,
transform,
}
}
}
#[derive(Debug)]
pub enum Command {
CreateTexture(CreateTextureCommand),
RemoveTexture(RemoveTextureCommand),
Draw(DrawCommand),
DrawUi(DrawCommand),
}
#[derive(Debug, Clone)]
pub struct BackendState {
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub surface_config: wgpu::SurfaceConfiguration,
}
pub(crate) struct Render {
pub(crate) screen_scale: Vec2,
pub(crate) inv_screen_scale: Vec2,
pub(crate) screen_size: Vec2,
logical_size: Vec2,
scale_mode: ScaleMode,
pub(crate) commands_buffer: Vec<Command>,
pub(crate) default_font: Option<Handle>,
pub(crate) dpi_scale_factor: f64,
pub(crate) inv_dpi_scale_factor: f64,
pub(crate) backend_state: BackendState,
pub(crate) renderer: Rc<dyn SpriteRenderer>,
pub(crate) send_renderer_data: Option<Vec<u8>>,
pub(crate) post_renderer: Box<dyn PostRenderer>,
}
impl Render {
pub(crate) fn new(backend_state: BackendState) -> Self {
let renderer = Rc::new(DefaultRenderer::new(&backend_state));
let post_renderer = Box::new(ScreenRenderer::setup(&backend_state));
Self {
screen_scale: Vec2::splat(1.0),
inv_screen_scale: Vec2::splat(1.0),
screen_size: Vec2::default(),
logical_size: Vec2::default(),
scale_mode: ScaleMode::default(),
commands_buffer: Vec::with_capacity(COMMANDS_BUFFER),
default_font: None,
dpi_scale_factor: 1.0,
inv_dpi_scale_factor: 1.0,
backend_state,
renderer,
send_renderer_data: None,
post_renderer,
}
}
pub(crate) fn set_default_font(&mut self, handle: Handle) {
self.default_font.replace(handle);
}
pub(crate) fn set_renderer(
&mut self,
renderer: Rc<dyn SpriteRenderer>,
) -> Rc<dyn SpriteRenderer> {
self.send_renderer_data = None;
core::mem::replace(&mut self.renderer, renderer)
}
pub(crate) fn send_renderer_data(&mut self, data: Vec<u8>) {
self.send_renderer_data = Some(data);
}
pub(crate) fn set_post_renderer(&mut self, post_renderer: Box<dyn PostRenderer>) {
self.post_renderer = post_renderer;
}
pub(crate) fn draw(&mut self, cmd: DrawCommand) {
self.push_cmd(Command::Draw(cmd));
}
pub(crate) fn draw_ui(&mut self, cmd: DrawCommand) {
self.push_cmd(Command::DrawUi(cmd));
}
pub(crate) fn take_commands(&mut self) -> Vec<Command> {
std::mem::replace(
&mut self.commands_buffer,
Vec::with_capacity(COMMANDS_BUFFER),
)
}
pub(crate) fn resize(&mut self, size: UVec2, dpi_scale_factor: f64) {
self.screen_size = Vec2::new(size.x as f32, size.y as f32);
self.dpi_scale_factor = dpi_scale_factor;
self.inv_dpi_scale_factor = 1.0 / dpi_scale_factor;
match self.scale_mode {
ScaleMode::Window => {
self.screen_scale = Vec2::splat(1.0);
}
ScaleMode::Fixed { width, height } => {
self.screen_scale =
Vec2::new(size.x as f32 / width as f32, size.y as f32 / height as f32);
}
ScaleMode::FixedHeight(height) => {
self.screen_scale = Vec2::splat(size.y as f32 / height as f32);
}
ScaleMode::FixedWidth(width) => {
self.screen_scale = Vec2::splat(size.x as f32 / width as f32);
}
}
self.logical_size = (self.screen_size / self.screen_scale).ceil();
self.inv_screen_scale = 1.0 / self.screen_scale;
}
pub(crate) fn create_text_texture(
&mut self,
text_cache: &mut TextCache,
handle: Handle,
text: &Text,
) -> Result<UVec2> {
let Some(font) = text_cache.get_font(
text.font
.as_ref()
.or(self.default_font.as_ref())
.expect("no default font")
.id(),
) else {
bail!("can't find font by handle id");
};
let mut text = text.clone();
text.scale *= self.dpi_scale_factor as f32;
if let Some(w) = text.max_width.as_mut() {
*w *= self.dpi_scale_factor as f32;
}
if let Some(h) = text.max_height.as_mut() {
*h *= self.dpi_scale_factor as f32;
}
let buffer = font.render_text_texture(&text);
let width = buffer.width();
let height = buffer.height();
let size =
(UVec2::new(width, height).as_vec2() * self.inv_dpi_scale_factor as f32).as_uvec2();
let cmd = CreateTextureCommand {
handle,
data: buffer.into(),
};
self.push_cmd(Command::CreateTexture(cmd));
Ok(size)
}
pub(crate) fn push_cmd(&mut self, cmd: Command) {
self.commands_buffer.push(cmd);
}
pub(crate) fn scale_mode(&self) -> ScaleMode {
self.scale_mode
}
pub(crate) fn set_scale_mode(&mut self, mode: ScaleMode) {
self.scale_mode = mode;
}
pub(crate) fn logical_size(&self) -> Vec2 {
self.logical_size
}
pub(crate) fn screen_size(&self) -> Vec2 {
self.screen_size
}
}