roast2d_internal 0.3.0-alpha.1

Roast2D internal crate
Documentation
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),
}

/// Backend state, used to create shaders or other wgpu resources
#[derive(Debug, Clone)]
pub struct BackendState {
    pub device: wgpu::Device,
    pub queue: wgpu::Queue,
    pub surface_config: wgpu::SurfaceConfiguration,
}

/// Render subsystem
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);
    }

    /// Set the sprite renderer
    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)
    }

    /// Send the renderer args
    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;
        // calculate scale
        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");
        };

        // Apply DPI factor
        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
    }
}