web-glitz 0.2.1

Rusty low-level web-graphics library on top of WebGL 2.0.
Documentation
use std::{cmp, mem, slice};

use js_sys::{
    Float32Array, Int16Array, Int32Array, Int8Array, Object, Uint16Array, Uint32Array, Uint8Array,
};
use web_sys::WebGl2RenderingContext as Gl;

use crate::image::format::{InternalFormat, PixelUnpack};
use crate::image::{Region2D, Region3D};

pub(crate) fn max_mipmap_levels(width: u32, height: u32) -> usize {
    (cmp::max(width, height) as f64).log2() as usize + 1
}

pub(crate) fn mipmap_size(base_size: u32, level: usize) -> u32 {
    let level_size = base_size / 2u32.pow(level as u32);

    if level_size < 1 {
        1
    } else {
        level_size
    }
}

pub(crate) fn region_2d_overlap_width(base_width: u32, level: usize, region: &Region2D) -> u32 {
    let level_width = mipmap_size(base_width, level);

    match *region {
        Region2D::Area((offset_x, _), width, _) => {
            if offset_x >= level_width {
                0
            } else {
                let max_width = level_width - offset_x;

                cmp::min(max_width, width)
            }
        }
        Region2D::Fill => level_width,
    }
}

pub(crate) fn region_2d_overlap_height(base_height: u32, level: usize, region: &Region2D) -> u32 {
    let level_height = mipmap_size(base_height, level);

    match *region {
        Region2D::Area((_, offset_y), _, height) => {
            if offset_y >= level_height {
                0
            } else {
                let max_height = level_height - offset_y;

                cmp::min(max_height, height)
            }
        }
        Region2D::Fill => level_height,
    }
}

pub(crate) fn region_3d_overlap_width(base_width: u32, level: usize, region: &Region3D) -> u32 {
    let level_width = mipmap_size(base_width, level);

    match *region {
        Region3D::Area((offset_x, _, _), width, ..) => {
            if offset_x >= level_width {
                0
            } else {
                let max_width = level_width - offset_x;

                cmp::min(max_width, width)
            }
        }
        Region3D::Fill => level_width,
    }
}

pub(crate) fn region_3d_overlap_height(base_height: u32, level: usize, region: &Region3D) -> u32 {
    let level_height = mipmap_size(base_height, level);

    match *region {
        Region3D::Area((_, offset_y, _), _, height, _) => {
            if offset_y >= level_height {
                0
            } else {
                let max_height = level_height - offset_y;

                cmp::min(max_height, height)
            }
        }
        Region3D::Fill => level_height,
    }
}

pub(crate) fn region_3d_overlap_depth(base_depth: u32, region: &Region3D) -> u32 {
    match *region {
        Region3D::Area((_, _, offset_z), _, _, depth) => {
            if offset_z >= base_depth {
                0
            } else {
                let max_depth = base_depth - offset_z;

                cmp::min(max_depth, depth)
            }
        }
        Region3D::Fill => base_depth,
    }
}

pub(crate) fn region_2d_sub_image(region_a: Region2D, region_b: Region2D) -> Region2D {
    match region_b {
        Region2D::Fill => region_a,
        Region2D::Area((b_offset_x, b_offset_y), width, height) => match region_a {
            Region2D::Fill => region_b,
            Region2D::Area((a_offset_x, a_offset_y), ..) => Region2D::Area(
                (a_offset_x + b_offset_x, a_offset_y + b_offset_y),
                width,
                height,
            ),
        },
    }
}

pub(crate) fn region_3d_sub_image(region_a: Region3D, region_b: Region3D) -> Region3D {
    match region_b {
        Region3D::Fill => region_a,
        Region3D::Area((b_offset_x, b_offset_y, b_offset_z), width, height, depth) => {
            match region_a {
                Region3D::Fill => region_b,
                Region3D::Area((a_offset_x, a_offset_y, a_offset_z), ..) => {
                    let offset_x = a_offset_x + b_offset_x;
                    let offset_y = a_offset_y + b_offset_y;
                    let offset_z = a_offset_z + b_offset_z;

                    Region3D::Area((offset_x, offset_y, offset_z), width, height, depth)
                }
            }
        }
    }
}

enum TextureBufferType {
    Float32,
    Uint8,
    Uint16,
    Uint32,
    Int8,
    Int16,
    Int32,
}

impl TextureBufferType {
    fn from_type_id(id: u32) -> Self {
        match id {
            Gl::BYTE => TextureBufferType::Int8,
            Gl::SHORT => TextureBufferType::Int16,
            Gl::INT => TextureBufferType::Int32,
            Gl::UNSIGNED_BYTE => TextureBufferType::Uint8,
            Gl::UNSIGNED_SHORT => TextureBufferType::Uint16,
            Gl::UNSIGNED_INT => TextureBufferType::Uint32,
            Gl::FLOAT => TextureBufferType::Float32,
            Gl::UNSIGNED_SHORT_5_6_5 => TextureBufferType::Uint16,
            Gl::UNSIGNED_INT_10F_11F_11F_REV => TextureBufferType::Uint32,
            Gl::UNSIGNED_INT_5_9_9_9_REV => TextureBufferType::Uint32,
            Gl::UNSIGNED_SHORT_5_5_5_1 => TextureBufferType::Uint16,
            Gl::UNSIGNED_INT_2_10_10_10_REV => TextureBufferType::Uint32,
            Gl::UNSIGNED_SHORT_4_4_4_4 => TextureBufferType::Uint16,
            Gl::UNSIGNED_INT_24_8 => TextureBufferType::Uint32,
            _ => panic!("Unsupported texture data type."),
        }
    }
}

pub(crate) fn texture_data_as_js_buffer<F, T>(data: &[F], elements: usize) -> Object
where
    F: PixelUnpack<T>,
    T: InternalFormat,
{
    let element_size = mem::size_of::<F>();
    let len_in_bytes = data.len() * element_size;
    let max_len_in_bytes = element_size * elements;

    let buffer_type = TextureBufferType::from_type_id(F::TYPE_ID);

    match buffer_type {
        TextureBufferType::Float32 => {
            let len = len_in_bytes / 4;
            let max_len = max_len_in_bytes / 4;

            unsafe {
                let mut data = slice::from_raw_parts(data as *const _ as *const f32, len as usize);

                if max_len < len {
                    data = &data[0..max_len];
                }

                let js_buffer = Float32Array::from(data);

                js_buffer.into()
            }
        }
        TextureBufferType::Uint8 => unsafe {
            let mut data =
                slice::from_raw_parts(data as *const _ as *const u8, len_in_bytes as usize);

            if max_len_in_bytes < len_in_bytes {
                data = &data[0..max_len_in_bytes];
            }

            let js_buffer = Uint8Array::from(data);

            js_buffer.into()
        },
        TextureBufferType::Uint16 => {
            let len = len_in_bytes / 2;
            let max_len = max_len_in_bytes / 2;

            unsafe {
                let mut data = slice::from_raw_parts(data as *const _ as *const u16, len as usize);

                if max_len < len {
                    data = &data[0..max_len];
                }

                let js_buffer = Uint16Array::from(data);

                js_buffer.into()
            }
        }
        TextureBufferType::Uint32 => {
            let len = len_in_bytes / 4;
            let max_len = max_len_in_bytes / 4;

            unsafe {
                let mut data = slice::from_raw_parts(data as *const _ as *const u32, len as usize);

                if max_len < len {
                    data = &data[0..max_len];
                }

                let js_buffer = Uint32Array::from(data);

                js_buffer.into()
            }
        }
        TextureBufferType::Int8 => unsafe {
            let mut data =
                slice::from_raw_parts(data as *const _ as *const i8, len_in_bytes as usize);

            if max_len_in_bytes < len_in_bytes {
                data = &data[0..max_len_in_bytes];
            }

            let js_buffer = Int8Array::from(data);

            js_buffer.into()
        },
        TextureBufferType::Int16 => {
            let len = len_in_bytes / 2;
            let max_len = max_len_in_bytes / 2;

            unsafe {
                let mut data = slice::from_raw_parts(data as *const _ as *const i16, len as usize);

                if max_len < len {
                    data = &data[0..max_len];
                }

                let js_buffer = Int16Array::from(data);

                js_buffer.into()
            }
        }
        TextureBufferType::Int32 => {
            let len = len_in_bytes / 4;
            let max_len = max_len_in_bytes / 4;

            unsafe {
                let mut data = slice::from_raw_parts(data as *const _ as *const i32, len as usize);

                if max_len < len {
                    data = &data[0..max_len];
                }

                let js_buffer = Int32Array::from(data);

                js_buffer.into()
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_mipmap_size() {
        assert_eq!(mipmap_size(256, 0), 256);
        assert_eq!(mipmap_size(256, 1), 128);
        assert_eq!(mipmap_size(256, 2), 64);
        assert_eq!(mipmap_size(256, 3), 32);
        assert_eq!(mipmap_size(256, 4), 16);
        assert_eq!(mipmap_size(256, 5), 8);
        assert_eq!(mipmap_size(256, 6), 4);
        assert_eq!(mipmap_size(256, 7), 2);
        assert_eq!(mipmap_size(256, 8), 1);
    }
}