use std::{fmt, mem, ops, panic, slice, time};
use std::any::type_name_of_val as name_of;
use std::collections::HashMap;
mod api;
mod draw;
mod shader;
mod texture2d;
mod objects;
use self::objects::*;
use api::types::*;
type NameBuf = crate::sstring::SmallString<64>;
pub fn log(s: impl fmt::Display) {
let s = s.to_string();
unsafe { api::consoleLog(s.as_ptr(), s.len()) };
}
pub fn setup_panic_hook() {
panic::set_hook(Box::new(|info| log(info)));
}
fn gl_texture_wrap(wrap: crate::TextureWrap) -> GLint {
(match wrap {
crate::TextureWrap::Edge => api::CLAMP_TO_EDGE,
crate::TextureWrap::Border => api::CLAMP_TO_EDGE, crate::TextureWrap::Repeat => api::REPEAT,
crate::TextureWrap::Mirror => api::MIRRORED_REPEAT,
}) as GLint
}
fn gl_texture_filter_mag(filter: crate::TextureFilter) -> GLint {
(match filter {
crate::TextureFilter::Nearest => api::NEAREST,
crate::TextureFilter::Linear => api::LINEAR,
}) as GLint
}
fn gl_texture_filter_min(props: &crate::TextureProps) -> GLint {
(if props.mip_levels > 1 {
match props.filter_min {
crate::TextureFilter::Nearest => api::NEAREST_MIPMAP_NEAREST,
crate::TextureFilter::Linear => api::LINEAR_MIPMAP_LINEAR,
}
}
else {
match props.filter_min {
crate::TextureFilter::Nearest => api::NEAREST,
crate::TextureFilter::Linear => api::LINEAR,
}
}) as GLint
}
fn gl_depth_func(v: crate::Compare) -> GLenum {
match v {
crate::Compare::Never => api::NEVER,
crate::Compare::Less => api::LESS,
crate::Compare::Equal => api::EQUAL,
crate::Compare::LessEqual => api::LEQUAL,
crate::Compare::Greater => api::GREATER,
crate::Compare::NotEqual => api::NOTEQUAL,
crate::Compare::GreaterEqual => api::GEQUAL,
crate::Compare::Always => api::ALWAYS,
}
}
#[derive(Clone, Debug)]
pub struct WebGLConfig {
pub srgb: bool,
}
impl Default for WebGLConfig {
fn default() -> Self {
WebGLConfig {
srgb: true,
}
}
}
pub struct WebGLGraphics {
objects: ObjectMap,
texture2d_default: crate::Texture2D,
_config: WebGLConfig,
drawing: bool,
draw_begin: f64,
metrics: crate::DrawMetrics,
immediate_fbo: Option<GLuint>,
}
impl WebGLGraphics {
pub fn new(config: WebGLConfig) -> Self {
let default_texture2d;
unsafe {
default_texture2d = api::createTexture();
api::bindTexture(api::TEXTURE_2D, default_texture2d);
api::texParameteri(api::TEXTURE_2D, api::TEXTURE_WRAP_S, api::CLAMP_TO_EDGE as GLint);
api::texParameteri(api::TEXTURE_2D, api::TEXTURE_WRAP_T, api::CLAMP_TO_EDGE as GLint);
api::texParameteri(api::TEXTURE_2D, api::TEXTURE_MIN_FILTER, api::NEAREST as GLint);
api::texParameteri(api::TEXTURE_2D, api::TEXTURE_MAG_FILTER, api::NEAREST as GLint);
let data = [0u8; 4];
api::texImage2D(api::TEXTURE_2D, 0, api::RGBA, 1, 1, 0, api::RGBA, api::UNSIGNED_BYTE, data.as_ptr() as *const _, 4);
api::bindTexture(api::TEXTURE_2D, 0);
}
let mut objects = ObjectMap::new();
let texture2d_default = objects.insert(WebGLTexture2D {
texture: default_texture2d,
info: crate::Texture2DInfo {
width: 1,
height: 1,
format: crate::TextureFormat::RGBA8,
props: crate::TextureProps {
mip_levels: 1,
usage: crate::TextureUsage::TEXTURE,
filter_min: crate::TextureFilter::Nearest,
filter_mag: crate::TextureFilter::Nearest,
wrap_u: crate::TextureWrap::Edge,
wrap_v: crate::TextureWrap::Edge,
..Default::default()
},
},
});
WebGLGraphics {
objects,
texture2d_default,
_config: config,
drawing: false,
draw_begin: 0.0,
metrics: Default::default(),
immediate_fbo: None,
}
}
#[inline]
pub fn as_graphics(&mut self) -> &mut crate::Graphics {
crate::Graphics(self)
}
}
impl crate::IGraphics for WebGLGraphics {
fn get_type(&self, object: crate::BaseObject) -> Option<crate::ObjectType> {
self.objects.get_type(object)
}
fn add_ref(&mut self, object: crate::BaseObject) {
self.objects.add_ref(object);
}
fn release(&mut self, object: crate::BaseObject) -> u32 {
self.objects.release(object)
}
fn begin(&mut self, args: &crate::BeginArgs) {
draw::begin(self, args)
}
fn clear(&mut self, args: &crate::ClearArgs) {
draw::clear(self, args)
}
fn draw(&mut self, args: &crate::DrawArgs) {
draw::arrays(self, args)
}
fn draw_indexed(&mut self, args: &crate::DrawIndexedArgs) {
draw::indexed(self, args)
}
fn end(&mut self) {
draw::end(self)
}
fn get_draw_metrics(&mut self, reset: bool) -> crate::DrawMetrics {
if reset {
mem::take(&mut self.metrics)
}
else {
self.metrics
}
}
fn vertex_buffer_create(&mut self, size: usize, layout: &'static crate::VertexLayout, usage: crate::BufferUsage) -> crate::VertexBuffer {
let alloc_size = if size == 0 { 1 } else { size };
if layout.size >= 256 {
panic!("Vertex layout size {} exceeds WebGL 1.0 limit of 256 bytes", layout.size);
}
let buffer = unsafe { api::createBuffer() };
let usage_enum = match usage {
crate::BufferUsage::Static => api::STATIC_DRAW,
crate::BufferUsage::Dynamic => api::DYNAMIC_DRAW,
crate::BufferUsage::Stream => api::STREAM_DRAW,
};
unsafe { api::bindBuffer(api::ARRAY_BUFFER, buffer) };
unsafe { api::bufferData(api::ARRAY_BUFFER, alloc_size as GLsizeiptr, std::ptr::null(), usage_enum) };
unsafe { api::bindBuffer(api::ARRAY_BUFFER, 0) };
return self.objects.insert(WebGLVertexBuffer { buffer, size, layout, _usage: usage });
}
fn vertex_buffer_write(&mut self, id: crate::VertexBuffer, offset: usize, data: &[u8]) {
let Some(buf) = self.objects.get_vertex_buffer(id) else { return };
let size = mem::size_of_val(data);
debug_assert!(offset + size <= buf.size, "Vertex buffer write out of bounds: {}..{} > {}", offset, offset + size, buf.size);
self.metrics.bytes_uploaded = usize::wrapping_add(self.metrics.bytes_uploaded, size);
unsafe { api::bindBuffer(api::ARRAY_BUFFER, buf.buffer) };
unsafe { api::bufferSubData(api::ARRAY_BUFFER, offset as GLintptr, size as GLsizeiptr, data.as_ptr()) };
unsafe { api::bindBuffer(api::ARRAY_BUFFER, 0) };
}
fn index_buffer_create(&mut self, size: usize, ty: crate::IndexType, usage: crate::BufferUsage) -> crate::IndexBuffer {
let alloc_size = if size == 0 { 1 } else { size };
let buffer = unsafe { api::createBuffer() };
let usage_enum = match usage {
crate::BufferUsage::Static => api::STATIC_DRAW,
crate::BufferUsage::Dynamic => api::DYNAMIC_DRAW,
crate::BufferUsage::Stream => api::STREAM_DRAW,
};
unsafe { api::bindBuffer(api::ELEMENT_ARRAY_BUFFER, buffer) };
unsafe { api::bufferData(api::ELEMENT_ARRAY_BUFFER, alloc_size as GLsizeiptr, std::ptr::null(), usage_enum) };
unsafe { api::bindBuffer(api::ELEMENT_ARRAY_BUFFER, 0) };
return self.objects.insert(WebGLIndexBuffer { buffer, size, _usage: usage, ty });
}
fn index_buffer_write(&mut self, id: crate::IndexBuffer, offset: usize, data: &[u8]) {
let Some(buf) = self.objects.get_index_buffer(id) else { return };
let size = mem::size_of_val(data);
debug_assert!(offset + size <= buf.size, "Index buffer write out of bounds: {}..{} > {}", offset, offset + size, buf.size);
self.metrics.bytes_uploaded = usize::wrapping_add(self.metrics.bytes_uploaded, size);
unsafe { api::bindBuffer(api::ELEMENT_ARRAY_BUFFER, buf.buffer) };
unsafe { api::bufferSubData(api::ELEMENT_ARRAY_BUFFER, offset as GLintptr, size as GLsizeiptr, data.as_ptr()) };
unsafe { api::bindBuffer(api::ELEMENT_ARRAY_BUFFER, 0) };
}
fn shader_compile(&mut self, vertex_source: &str, fragment_source: &str) -> crate::ShaderProgram {
shader::create(self, vertex_source, fragment_source)
}
fn texture2d_create(&mut self, info: &crate::Texture2DInfo) -> crate::Texture2D {
texture2d::create(self, info)
}
fn texture2d_generate_mipmap(&mut self, id: crate::Texture2D) {
texture2d::generate_mipmap(self, id)
}
fn texture2d_update(&mut self, id: crate::Texture2D, info: &crate::Texture2DInfo) -> crate::Texture2D {
texture2d::update(self, id, info)
}
fn texture2d_write(&mut self, id: crate::Texture2D, level: u8, data: &[u8]) {
texture2d::write(self, id, level, data)
}
fn texture2d_read_into(&mut self, id: crate::Texture2D, level: u8, data: &mut [u8]) {
texture2d::read_into(self, id, level, data)
}
fn texture2d_get_info(&self, id: crate::Texture2D) -> Option<&crate::Texture2DInfo> {
texture2d::get_info(self, id)
}
}
impl ops::Deref for WebGLGraphics {
type Target = crate::Graphics;
#[inline]
fn deref(&self) -> &crate::Graphics {
unsafe { mem::transmute(self as &dyn crate::IGraphics) }
}
}
impl ops::DerefMut for WebGLGraphics {
#[inline]
fn deref_mut(&mut self) -> &mut crate::Graphics {
crate::Graphics(self)
}
}