#![warn(missing_docs)]
use std::marker::PhantomData;
use std::mem;
use std::ops::{Bound, RangeBounds};
pub use willow_codegen::Program;
pub use anyhow::{self, Error, Result};
#[doc(hidden)]
pub use field_offset::offset_of;
#[doc(hidden)]
pub use log;
#[doc(hidden)]
pub use paste::paste;
#[doc(hidden)]
pub use web_sys::{
WebGlBuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, WebGlUniformLocation,
};
mod index;
pub use index::*;
mod types;
pub use types::*;
mod program;
pub use program::*;
mod traits;
pub use traits::*;
pub struct Context {
#[doc(hidden)]
pub native: WebGlRenderingContext,
}
impl Context {
pub fn from_canvas(canvas: web_sys::Element) -> Result<Self> {
use anyhow::Context;
use wasm_bindgen::JsCast;
Ok(Self {
native: canvas
.dyn_into::<web_sys::HtmlCanvasElement>()
.ok()
.context("The element is not a <canvas>")?
.get_context("webgl")
.ok()
.flatten()
.context("Could not initialize WebGL context")?
.dyn_into()
.ok()
.context("WebGL context has an incorrect type")?,
})
}
}
#[macro_export]
macro_rules! create_programs {
($context:expr => $($ty:ty),* $(,)?) => {
{
$crate::paste! {
$(
#[allow(non_snake_case)]
let [<var_ $ty>] = $ty::create_internally(&$context);
)*;
$(
[<var_ $ty>].compile_shaders(&$context);
)*
$(
[<var_ $ty>].link_shaders(&$context);
)*
($(
[<var_ $ty>],
)*)
}
}
}
}
pub struct Buffer<T: AttrStruct> {
#[doc(hidden)]
pub buf: WebGlBuffer,
count: usize, _ph: PhantomData<*const T>,
}
impl<T: AttrStruct> Buffer<T> {
pub fn from_slice(context: &Context, slice: &[T], usage: BufferDataUsage) -> Self {
let gl = &context.native;
let buf = gl.create_buffer().expect("Failed to create WebGL buffer");
gl.bind_buffer(WebGlRenderingContext::ARRAY_BUFFER, Some(&buf));
let bytes = unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const u8, slice.len()) };
gl.buffer_data_with_u8_array(WebGlRenderingContext::ARRAY_BUFFER, bytes, usage.to_const());
Self {
buf,
count: slice.len(),
_ph: PhantomData,
}
}
pub fn bind_to_attr(&self, context: &Context, attr_index: u32, field_index: usize) {
context.native.vertex_attrib_pointer_with_i32(
attr_index,
T::field_num_comps(field_index) as i32, T::field_type(field_index), T::field_normalized(field_index), mem::align_of::<T>() as i32, T::field_offset(field_index) as i32, );
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BufferDataUsage {
StaticDraw,
DynamicDraw,
StreamDraw,
}
impl BufferDataUsage {
fn to_const(self) -> u32 {
match self {
Self::StaticDraw => WebGlRenderingContext::STATIC_DRAW,
Self::DynamicDraw => WebGlRenderingContext::DYNAMIC_DRAW,
Self::StreamDraw => WebGlRenderingContext::STREAM_DRAW,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RenderPrimitiveType {
Points,
LineStrip,
LineLoop,
Lines,
TriangleStrip,
TriangleFan,
Triangles,
}
impl RenderPrimitiveType {
fn to_const(self) -> u32 {
match self {
Self::Points => WebGlRenderingContext::POINTS,
Self::LineStrip => WebGlRenderingContext::LINE_STRIP,
Self::LineLoop => WebGlRenderingContext::LINE_LOOP,
Self::Lines => WebGlRenderingContext::LINES,
Self::TriangleStrip => WebGlRenderingContext::TRIANGLE_STRIP,
Self::TriangleFan => WebGlRenderingContext::TRIANGLE_FAN,
Self::Triangles => WebGlRenderingContext::TRIANGLES,
}
}
}
fn resolve_range(items: impl RangeBounds<usize>, len: usize) -> (i32, i32) {
let start = match items.start_bound() {
Bound::Included(&x) => x as i32,
Bound::Excluded(&x) => x as i32 - 1,
Bound::Unbounded => 0,
};
let end = match items.end_bound() {
Bound::Included(&x) => x as i32 + 1,
Bound::Excluded(&x) => x as i32,
Bound::Unbounded => len as i32,
};
assert!(end <= len as i32, "items range exceeds buffer size");
(start, end)
}