niko 0.1.8

the niko wasm game engine
Documentation
use crate::{Error, NikoError, graphics::{ShaderError, check_error, ShaderId, ProgramId}};
use glow::HasContext;
use std::collections::HashMap;

macro_rules! log {
    ( $( $t:tt )* ) => {
        web_sys::console::log_1(&format!( $( $t )* ).into());
    }
}

unsafe fn compile_shader(gl: &glow::Context, source: &str, kind: u32) -> Result<ShaderId, Error> {
    let shader = gl.create_shader(kind)
        .map_err(|error| NikoError::PlatformError(error))?;

    gl.shader_source(shader, source);
    gl.compile_shader(shader);

    if !gl.get_shader_compile_status(shader) {
        let error = gl.get_shader_info_log(shader);
        gl.delete_shader(shader);
        return Err(ShaderError::ShaderCompileError(error).into());
    }

    Ok(shader)
}

unsafe fn reflect_attributes(gl: &glow::Context, program: ProgramId) -> HashMap<String, u32> {
    let mut attributes = HashMap::new();
    let attribute_count = gl.get_active_attributes(program);
    for index in 0..attribute_count {
        if let Some(attribute) = gl.get_active_attribute(program, index) {
            log!("attribute found at {}: {}, {}, {}", index, attribute.name, attribute.atype, attribute.size);
            attributes.insert(attribute.name, index);
        } else {
            log!("no attribute found at {}", index);
        }
    }

    attributes
}

unsafe fn reflect_uniforms(gl: &glow::Context, program: ProgramId) -> HashMap<String, glow::UniformLocation> {
    let mut uniforms = HashMap::new();
    let uniform_count = gl.get_active_uniforms(program);
    for index in 0..uniform_count {
        if let Some(uniform) = gl.get_active_uniform(program, index) {
            if let Some(location) = gl.get_uniform_location(program, &uniform.name) {
                log!("uniform found at {}: {}, {}, {}", index, uniform.name, uniform.utype, uniform.size);
                uniforms.insert(uniform.name, location);
            } else {
                log!("no uniform found at {}", index);
            }
        }
    }

    uniforms
}

unsafe fn build_program(gl: &glow::Context, vertex_shader_source: &str, fragment_shader_source: &str) -> Result<ProgramId, Error> {
    let vertex_shader = compile_shader(gl, vertex_shader_source, glow::VERTEX_SHADER)?;
    let fragment_shader = compile_shader(gl, fragment_shader_source, glow::FRAGMENT_SHADER)?;
    
    let program = gl.create_program()
        .map_err(|error| NikoError::PlatformError(error))?;

    gl.attach_shader(program, vertex_shader);
    gl.attach_shader(program, fragment_shader);
    
    gl.link_program(program);

    if !gl.get_program_link_status(program) {
        let error = gl.get_program_info_log(program);
        gl.delete_program(program);
        return Err(NikoError::PlatformError(error).into());
    }

    gl.detach_shader(program, vertex_shader);
    gl.detach_shader(program, fragment_shader);

    gl.delete_shader(vertex_shader);
    gl.delete_shader(fragment_shader);

    Ok(program)
}

#[derive(Debug)]
pub struct Shader {
    inner: ProgramId,
    attributes: HashMap<String, u32>,
    uniforms: HashMap<String, glow::UniformLocation>,
}

impl Shader {
    pub fn create(gl: &glow::Context, vertex_shader: &str, fragment_shader: &str) -> Result<Self, Error> {
        let inner = unsafe { build_program(gl, vertex_shader, fragment_shader)? };

        let attributes = unsafe { reflect_attributes(gl, inner) };
        let uniforms = unsafe { reflect_uniforms(gl, inner) };

        Ok(Self {
            inner,
            attributes,
            uniforms,
        })
    }

    pub fn get_attribute_location(&self, name: &str) -> Option<u32> {
        match self.attributes.get(name) {
            Some(id) => Some(*id),
            None => None,
        }
    }

    pub fn query_attribute_location(&self, gl: &glow::Context, name: &str) -> Option<u32> {
        let result = unsafe {
            gl.get_attrib_location(self.inner, name)
        };

        result
    }

    pub fn get_uniform_location(&self, name: &str) -> Option<&glow::UniformLocation> {
        self.uniforms.get(name)
    }

    pub fn query_uniform_location(&self, gl: &glow::Context, name: &str) -> Option<glow::UniformLocation> {
        let result = unsafe {
            gl.get_uniform_location(self.inner, name)
        };

        result
    }

    pub(crate) fn get_inner(&self) -> ProgramId {
        self.inner
    }
}