webgl-rc 0.1.12

WebGL wrapper with resources reference counting
Documentation
use js_sys::JsString;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{
    AngleInstancedArrays, ExtColorBufferHalfFloat, HtmlCanvasElement, OesElementIndexUint,
    OesTextureHalfFloat, OesTextureHalfFloatLinear, WebGlRenderingContext as Context,
};

use super::data_buffer::ItemsBuffer;
use super::program::Program;
use super::settings::{EmptySetting, Settings, SettingsCache};
use super::texture::{Texture, TextureContent, TextureFormat, TextureType};
use crate::buffer_usage::BufferUsage;
use crate::{DepthBuffer, ElementsBuffer, FrameBuffer};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GlError {
    UnknownError(Option<String>),
    ShaderCompilationError {
        source: String,
        info: Option<String>,
    },
    ProgramLinkingError {
        vertex: String,
        fragment: String,
        info: Option<String>,
    },
    ExtensionNotFound(String),
    UnsupportedType(Option<String>),
    BufferAllocationError,
    ReadPixelsError(Option<String>),
    WritePixelsError(Option<String>),
    InitTextureBufferError(Option<String>),
    InvalidBufferSize {
        expected: u32,
        received: u32,
    },
    DepthBufferError,
    FrameBufferError,
}

impl From<GlError> for js_sys::Error {
    fn from(error: GlError) -> Self {
        js_sys::Error::new(&format!("{:?}", error))
    }
}

impl From<GlError> for JsValue {
    fn from(error: GlError) -> Self {
        let js_error: js_sys::Error = error.into();
        js_error.into()
    }
}

impl Into<GlError> for js_sys::Error {
    fn into(self) -> GlError {
        GlError::UnknownError(Some(self.message().into()))
    }
}

impl Into<GlError> for JsValue {
    fn into(self) -> GlError {
        let error: js_sys::Error = self.into();
        error.into()
    }
}

#[derive(Debug)]
pub(self) struct GlInfo {
    pub(crate) context: Context,
    pub(self) settings_cache: RefCell<SettingsCache>,
    pub(self) ex_instanced_arrays: AngleInstancedArrays,
    pub(self) ex_color_buffer_half_float: ExtColorBufferHalfFloat,
    pub(self) ex_texture_half_float: OesTextureHalfFloat,
    pub(self) ex_texture_half_float_linear: OesTextureHalfFloatLinear,
    pub(self) ex_element_index_uint: OesElementIndexUint,
}

#[derive(Clone, Debug)]
pub struct Gl {
    data: Rc<GlInfo>,
}

impl Gl {
    fn get_extension<Ex: JsCast>(context: &Context, name: &str) -> Result<Ex, GlError> {
        context
            .get_extension(name)
            .map_err(|_| GlError::ExtensionNotFound(name.into()))
            .map(|value| match value {
                Some(extension) => Ok(Ex::unchecked_from_js(extension.into())),
                None => Err(GlError::ExtensionNotFound(name.into())),
            })
            .unwrap_or_else(|error| Err(error))
    }

    pub fn new(canvas: &HtmlCanvasElement) -> Result<Gl, GlError> {
        let context = canvas
            .get_context("webgl")
            .map_err(|err| GlError::UnknownError(Some(JsString::from(err).into())))?
            .map(|context| Context::from(JsValue::from(context)))
            .ok_or_else(|| GlError::UnknownError(None))?;

        Ok(Gl {
            data: Rc::new(GlInfo {
                ex_instanced_arrays: Gl::get_extension(&context, "ANGLE_instanced_arrays")?,
                ex_color_buffer_half_float: Gl::get_extension(
                    &context,
                    "EXT_color_buffer_half_float",
                )?,
                ex_texture_half_float: Gl::get_extension(&context, "OES_texture_half_float")?,
                ex_texture_half_float_linear: Gl::get_extension(
                    &context,
                    "OES_texture_half_float_linear",
                )?,
                ex_element_index_uint: Gl::get_extension(&context, "OES_element_index_uint")?,
                settings_cache: Default::default(),
                context,
            }),
        })
    }

    pub fn context(&self) -> &Context {
        &self.data.context
    }

    pub fn instanced_arrays(&self) -> &AngleInstancedArrays {
        &self.data.ex_instanced_arrays
    }

    pub fn settings() -> impl Settings {
        EmptySetting {}
    }

    pub fn apply<R>(&self, settings: impl Settings, callback: impl FnOnce() -> R) -> R {
        settings.apply(self, &self.data.settings_cache, callback)
    }

    pub fn program(&self, fragment: &str, vertex: &str) -> Result<Program, GlError> {
        Program::new(self.clone(), fragment, vertex)
    }

    pub fn items_buffer<I>(&self, data: &[I], usage: BufferUsage) -> Result<ItemsBuffer<I>, GlError>
    where
        I: super::data_buffer::Item,
    {
        ItemsBuffer::new(self.clone(), data, usage)
    }

    pub fn elements_buffer(
        &self,
        data: &[u32],
        usage: BufferUsage,
    ) -> Result<ElementsBuffer, GlError> {
        ElementsBuffer::new(self.clone(), data, usage)
    }

    pub fn clear_color_buffer(&self) {
        self.context().clear(Context::COLOR_BUFFER_BIT);
    }

    pub fn clear_depth_buffer(&self) {
        self.context().clear(Context::DEPTH_BUFFER_BIT);
    }

    pub fn clear_buffers(&self) {
        self.context()
            .clear(Context::COLOR_BUFFER_BIT | Context::DEPTH_BUFFER_BIT);
    }

    pub fn texture(
        &self,
        width: u32,
        height: u32,
        data_type: TextureType,
        format: TextureFormat,
        data: TextureContent,
    ) -> Result<Texture, GlError> {
        Texture::new(self.clone(), width, height, data_type, format, data)
    }

    pub fn depth_buffer(&self, width: u32, height: u32) -> Result<DepthBuffer, GlError> {
        DepthBuffer::new(self.clone(), width, height)
    }

    pub fn frame_buffer(&self) -> Result<FrameBuffer, GlError> {
        FrameBuffer::new(self.clone())
    }

    pub fn frame_buffer_with_color(&self, texture: Texture) -> Result<FrameBuffer, GlError> {
        let mut result = FrameBuffer::new(self.clone())?;
        result.set_color_buffer(Some(texture));
        Ok(result)
    }

    pub fn frame_buffer_with_depth(
        &self,
        texture: Texture,
        depth_buffer: DepthBuffer,
    ) -> Result<FrameBuffer, GlError> {
        let mut result = FrameBuffer::new(self.clone())?;
        result.set_color_buffer(Some(texture));
        result.set_depth_buffer(Some(depth_buffer));
        Ok(result)
    }
}