picodraw 0.2.0

A smol gpu graphics library
Documentation
use crate::{graph::ShaderGraph, types::GlType, Bounds, Float2, Float4, Shader, ShaderContext};
use encoding::{InputStructure, BUILTIN_BOUNDS, BUILTIN_POSITION, BUILTIN_RESOLUTION};
use rustc_hash::FxHashMap;
use std::any::{type_name, TypeId};

mod atlas;
mod encoding;
mod glsl;

pub use atlas::TextureAtlas;
pub use encoding::QuadEncoder;
pub use glsl::VERTEX_SHADER;

struct ShaderData {
    id: u32,
    graph: ShaderGraph<Float4>,
    input: InputStructure,
}

pub struct ShaderMap {
    shaders: FxHashMap<TypeId, ShaderData>,
    dirty: bool,
}

impl ShaderMap {
    pub fn new() -> Self {
        Self {
            shaders: FxHashMap::default(),
            dirty: false,
        }
    }

    pub fn register<T: Shader>(&mut self) {
        let id = T::id();
        if self.shaders.contains_key(&id) {
            return;
        }

        let mut input = None;
        let graph = ShaderGraph::collect(|| {
            let (structure, vars) = InputStructure::of::<T>();
            input = Some(structure);
            T::draw(ShaderContext {
                vars: &vars,
                position: Float2::input_raw(BUILTIN_POSITION),
                resolution: Float2::input_raw(BUILTIN_RESOLUTION),
                bounds: Float4::input_raw(BUILTIN_BOUNDS),
            })
        });

        self.dirty = true;
        self.shaders.insert(
            id,
            ShaderData {
                id: self.shaders.len() as u32,
                graph,
                input: input.unwrap(),
            },
        );
    }

    pub fn is_dirty(&self) -> bool {
        self.dirty
    }

    pub fn recompile(&mut self, max_texture_size: u32) -> (String, TextureAtlas) {
        self.dirty = false;

        let atlas = TextureAtlas::pack(
            self.shaders.values().flat_map(|data| {
                data.input
                    .textures
                    .iter()
                    .enumerate()
                    .map(move |(id, generator)| (data.id, id as u32, generator()))
            }),
            max_texture_size,
        );

        let fragment_src = glsl::generate_fragment_shader(
            self.shaders
                .values()
                .map(|data| (data.id, &data.graph, &data.input)),
            &atlas,
        );

        (fragment_src, atlas)
    }

    pub fn write<T: Shader>(
        &mut self,
        encoder: &mut QuadEncoder,
        bounds: Bounds,
        value: &T,
        width: u32,
        height: u32,
    ) {
        let data = self.shaders.get(&T::id()).unwrap_or_else(|| {
            if cfg!(debug_assertions) {
                panic!("register the drawable first ({})", type_name::<T>())
            } else {
                panic!("register the drawable first")
            }
        });

        encoder.push(
            value,
            data.id,
            bounds,
            &data.input,
            width as f32,
            height as f32,
        );
    }
}