infuse 0.5.0

Minimalist wasm based webgl renderer
Documentation
use js_sys::WebAssembly;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{WebGlProgram, WebGl2RenderingContext, WebGlShader, WebGlContextAttributes};

use std::collections::HashMap;

pub mod macros;


pub enum Uniform {
    Vec4(f32, f32, f32 ,f32),
    Vec3(f32, f32, f32),
    Vec2(f32, f32),
    Float(f32)
}

pub struct RenderItem {
    vertices: Vec<f32>,
    shader_name: String,
    uniforms: Option<HashMap<String, Uniform>>,
}

impl RenderItem {
    pub fn new(
        vertices: Vec<f32>,
        shader_name: String,
        uniforms: Option<HashMap<String, Uniform>>,
    ) -> RenderItem {
        RenderItem {
            vertices,
            shader_name,
            uniforms,
        }
    }
}

impl RenderItem {
    pub fn set_uniform(&mut self, name: String, value: Uniform) {
        if let Some(uniforms) = self.uniforms.as_mut() {
            uniforms.insert(name, value);
        }
    }
}

pub struct Renderer {
    context: WebGl2RenderingContext,
    shaders: HashMap<String, WebGlProgram>,
}

impl Renderer {
    pub fn new() -> Result<Renderer, JsValue> {
        let document = web_sys::window().unwrap().document().unwrap();
        let canvas = document.get_element_by_id("canvas").unwrap();
        let canvas: web_sys::HtmlCanvasElement = canvas.dyn_into::<web_sys::HtmlCanvasElement>()?;

        let context = canvas
            .get_context_with_context_options(
                "webgl2",
                WebGlContextAttributes::new()
                    .antialias(true)
                    .preserve_drawing_buffer(true)
                    .as_ref()
            )?
            .unwrap()
            .dyn_into::<WebGl2RenderingContext>()?;

        let shaders = HashMap::<String, WebGlProgram>::new();

        let mut renderer = Renderer { context, shaders };

        renderer.add_shader(
            "default".into(),
            "
            attribute vec4 position;
            void main() {
                gl_Position = position;
            }
            "
            .into(),
            "
            void main() {
                gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
            }
            "
            .into(),
        )?;

        Ok(renderer)
    }

    pub fn draw(&self, render_items: &Vec<RenderItem>) -> Result<(), JsValue> {
        self.context.clear_color(0.0, 0.0, 0.0, 1.0);

        for render_item in render_items {
            let shader_name = &render_item.shader_name;
            let program = self.shaders.get(shader_name.as_str()).unwrap();
            self.context.use_program(Some(&program));

            if let Some(uni) = &render_item.uniforms {
                for (key, value) in uni.iter() {
                    let location = self.context.get_uniform_location(program, key.as_str());

                    match *value {
                        Uniform::Vec4(x, y, z, w) => {
                            self.context
                                .uniform4f(location.as_ref(), x, y, z, w);
                        },
                        Uniform::Vec3(x, y, z) => {
                            self.context
                                .uniform3f(location.as_ref(), x, y, z);
                        },
                        Uniform::Vec2(x, y) => {
                            self.context
                                .uniform2f(location.as_ref(), x, y);
                        },
                        Uniform::Float(x) => {
                            self.context
                                .uniform1f(location.as_ref(), x);
                        }
                    }
                }
            }

            let vertices = &render_item.vertices;
            let memory_buffer = wasm_bindgen::memory()
                .dyn_into::<WebAssembly::Memory>()?
                .buffer();
            let vertices_location = vertices.as_ptr() as u32 / 4;
            let vert_array = js_sys::Float32Array::new(&memory_buffer)
                .subarray(vertices_location, vertices_location + vertices.len() as u32);

            let buffer = self
                .context
                .create_buffer()
                .ok_or("failed to create buffer")?;
            self.context
                .bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&buffer));
            self.context.buffer_data_with_array_buffer_view(
                WebGl2RenderingContext::ARRAY_BUFFER,
                &vert_array,
                WebGl2RenderingContext::STATIC_DRAW,
            );
            self.context.vertex_attrib_pointer_with_i32(
                0,
                3,
                WebGl2RenderingContext::FLOAT,
                false,
                0,
                0,
            );
            self.context.enable_vertex_attrib_array(0);

            self.context.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);

            self.context.draw_arrays(
                WebGl2RenderingContext::TRIANGLES,
                0,
                (vertices.len() / 3) as i32,
            );
        }

        Ok(())
    }

    pub fn add_shader(
        &mut self,
        name: String,
        vertex_shader: String,
        fragment_shader: String,
    ) -> Result<(), String> {
        let vert_shader = Self::compile_shader(
            &self.context,
            WebGl2RenderingContext::VERTEX_SHADER,
            vertex_shader.as_str(),
        )?;
        let frag_shader = Self::compile_shader(
            &self.context,
            WebGl2RenderingContext::FRAGMENT_SHADER,
            fragment_shader.as_str(),
        )?;
        let program = Self::link_program(&self.context, &vert_shader, &frag_shader)?;

        self.shaders.insert(name.into(), program);

        Ok(())
    }

    fn compile_shader(
        context: &WebGl2RenderingContext,
        shader_type: u32,
        source: &str,
    ) -> Result<WebGlShader, String> {
        let shader = context
            .create_shader(shader_type)
            .ok_or_else(|| String::from("Unable to create shader object"))?;
        context.shader_source(&shader, source);
        context.compile_shader(&shader);

        if context
            .get_shader_parameter(&shader, WebGl2RenderingContext::COMPILE_STATUS)
            .as_bool()
            .unwrap_or(false)
        {
            Ok(shader)
        } else {
            Err(context
                .get_shader_info_log(&shader)
                .unwrap_or_else(|| String::from("Unknown error creating shader")))
        }
    }

    fn link_program(
        context: &WebGl2RenderingContext,
        vert_shader: &WebGlShader,
        frag_shader: &WebGlShader,
    ) -> Result<WebGlProgram, String> {
        let program = context
            .create_program()
            .ok_or_else(|| String::from("Unable to create shader object"))?;

        context.attach_shader(&program, vert_shader);
        context.attach_shader(&program, frag_shader);
        context.link_program(&program);

        if context
            .get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS)
            .as_bool()
            .unwrap_or(false)
        {
            Ok(program)
        } else {
            Err(context
                .get_program_info_log(&program)
                .unwrap_or_else(|| String::from("Unknown error creating program object")))
        }
    }
}