use super::*;
thread_local! {
static GL_TEX_IMAGE_2D_CACHE: RefCell<Option<Function>> = const { RefCell::new(None) };
}
impl WebGl2InitError {
pub fn code(&self) -> &'static str {
match self {
Self::CanvasNotFound(_) => GL_ERROR_CANVAS_NOT_FOUND,
Self::CanvasQuery(_) => GL_ERROR_CANVAS_QUERY,
Self::ContextUnavailable => GL_ERROR_CONTEXT_UNAVAILABLE,
Self::ContextLookup(_) => GL_ERROR_CONTEXT_LOOKUP,
Self::ContextCast => GL_ERROR_CONTEXT_CAST,
}
}
}
impl Display for WebGl2InitError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::CanvasNotFound(selector) => {
write!(
formatter,
"[{}] canvas element {selector:?} not found in DOM",
self.code()
)
}
Self::CanvasQuery(selector) => {
write!(
formatter,
"[{}] querySelector({selector:?}) threw",
self.code()
)
}
Self::ContextUnavailable => write!(
formatter,
"[{}] canvas.get_context('webgl2') returned null - the browser does not support WebGL 2 or the canvas already uses another context type",
self.code()
),
Self::ContextLookup(selector) => write!(
formatter,
"[{}] canvas.get_context('webgl2') threw while resolving {selector:?}",
self.code()
),
Self::ContextCast => write!(
formatter,
"[{}] get_context('webgl2') result could not be cast to WebGl2RenderingContext",
self.code()
),
}
}
}
impl Display for WebGlProgramError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::ShaderCompile(log) => write!(formatter, "WebGL shader compilation failed: {log}"),
Self::ProgramLink(log) => write!(formatter, "WebGL program link failed: {log}"),
}
}
}
impl Error for WebGl2InitError {}
impl Error for WebGlProgramError {}
impl GlBuffer {
pub fn create(
context: &WebGl2RenderingContext,
target: u32,
size: u32,
usage: u32,
) -> Option<GlBuffer> {
let buffer: WebGlBuffer = context.create_buffer()?;
context.bind_buffer(target, Some(&buffer));
context.buffer_data_with_i32(target, size as i32, usage);
Some(GlBuffer {
buffer,
capacity: size,
usage,
})
}
pub fn create_for(
context: &WebGl2RenderingContext,
usage: BufferUsage,
size: u32,
) -> Option<GlBuffer> {
GlBuffer::create(
context,
gl_buffer_target(usage),
size,
gl_buffer_usage_hint(usage),
)
}
pub fn fits(&self, size: u32) -> bool {
size <= self.get_capacity()
}
pub fn bind(&self, context: &WebGl2RenderingContext, target: u32) {
context.bind_buffer(target, Some(self.get_buffer()));
}
pub fn upload(&mut self, context: &WebGl2RenderingContext, target: u32, data: &[u8]) {
let size: u32 = data.len() as u32;
if !self.fits(size) {
self.grow(context, target, size);
}
context.bind_buffer(target, Some(self.get_buffer()));
context.buffer_sub_data_with_i32_and_u8_array(target, 0, data);
}
pub fn update(
&mut self,
context: &WebGl2RenderingContext,
target: u32,
offset: u32,
data: &[u8],
) -> bool {
let end: u32 = offset.saturating_add(data.len() as u32);
if end > self.get_capacity() {
return false;
}
context.bind_buffer(target, Some(self.get_buffer()));
context.buffer_sub_data_with_i32_and_u8_array(target, offset as i32, data);
true
}
pub fn delete(&mut self, context: &WebGl2RenderingContext, target: u32) {
context.bind_buffer(target, None);
context.delete_buffer(Some(self.get_buffer()));
self.set_capacity(0);
}
fn grow(&mut self, context: &WebGl2RenderingContext, target: u32, size: u32) {
context.bind_buffer(target, Some(self.get_buffer()));
context.buffer_data_with_i32(target, size as i32, self.get_usage());
self.set_capacity(size);
}
}
impl GlTexture {
pub fn create(
context: &WebGl2RenderingContext,
width: u32,
height: u32,
format: GpuTextureFormat,
pixels: &[u8],
) -> Option<GlTexture> {
let texture: WebGlTexture = context.create_texture()?;
let target: u32 = gl_texture_target_2d();
context.bind_texture(target, Some(&texture));
let (internal, pixel_format, pixel_type): (i32, u32, u32) = gl_texture_layout(format);
let payload: Option<&[u8]> = if pixels.is_empty() {
None
} else {
Some(pixels)
};
let _: Result<(), JsValue> = context
.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
target,
0,
internal,
width as i32,
height as i32,
0,
pixel_format,
pixel_type,
payload,
);
GlTexture::apply_default_parameters(context);
Some(GlTexture {
texture,
width,
height,
levels: 1,
mipmapped: false,
})
}
fn apply_default_parameters(context: &WebGl2RenderingContext) {
let target: u32 = gl_texture_target_2d();
let linear: i32 = gl_filter_mode(FilterMode::Linear) as i32;
let clamp: i32 = gl_address_mode(AddressMode::ClampToEdge) as i32;
context.tex_parameteri(target, WebGl2RenderingContext::TEXTURE_MIN_FILTER, linear);
context.tex_parameteri(target, WebGl2RenderingContext::TEXTURE_MAG_FILTER, linear);
context.tex_parameteri(target, WebGl2RenderingContext::TEXTURE_WRAP_S, clamp);
context.tex_parameteri(target, WebGl2RenderingContext::TEXTURE_WRAP_T, clamp);
}
pub fn create_from_image(
context: &WebGl2RenderingContext,
image: &HtmlImageElement,
format: GpuTextureFormat,
) -> Option<GlTexture> {
let width: u32 = image.natural_width().max(1);
let height: u32 = image.natural_height().max(1);
let texture: WebGlTexture = context.create_texture()?;
let target: u32 = gl_texture_target_2d();
context.pixel_storei(WebGl2RenderingContext::UNPACK_FLIP_Y_WEBGL, gl_flip_y(true));
context.bind_texture(target, Some(&texture));
let (internal, pixel_format, pixel_type): (i32, u32, u32) = gl_texture_layout(format);
let _: Result<(), JsValue> = context.tex_image_2d_with_u32_and_u32_and_html_image_element(
target,
0,
internal,
pixel_format,
pixel_type,
image,
);
context.pixel_storei(
WebGl2RenderingContext::UNPACK_FLIP_Y_WEBGL,
gl_flip_y(false),
);
GlTexture::apply_default_parameters(context);
Some(GlTexture {
texture,
width,
height,
levels: 1,
mipmapped: false,
})
}
pub fn create_from_canvas(
context: &WebGl2RenderingContext,
canvas: &HtmlCanvasElement,
format: GpuTextureFormat,
) -> Option<GlTexture> {
let width: u32 = canvas.width().max(1);
let height: u32 = canvas.height().max(1);
let texture: WebGlTexture = context.create_texture()?;
let target: u32 = gl_texture_target_2d();
context.bind_texture(target, Some(&texture));
let (internal, pixel_format, pixel_type): (i32, u32, u32) = gl_texture_layout(format);
let _: Result<(), JsValue> = context.tex_image_2d_with_u32_and_u32_and_html_canvas_element(
target,
0,
internal,
pixel_format,
pixel_type,
canvas,
);
GlTexture::apply_default_parameters(context);
Some(GlTexture {
texture,
width,
height,
levels: 1,
mipmapped: false,
})
}
pub fn create_from_bitmap(
context: &WebGl2RenderingContext,
bitmap: &Object,
width: u32,
height: u32,
format: GpuTextureFormat,
) -> Option<GlTexture> {
let texture: WebGlTexture = context.create_texture()?;
let target: u32 = gl_texture_target_2d();
context.pixel_storei(WebGl2RenderingContext::UNPACK_FLIP_Y_WEBGL, gl_flip_y(true));
context.bind_texture(target, Some(&texture));
let (internal, pixel_format, pixel_type): (i32, u32, u32) = gl_texture_layout(format);
let outcome: Result<JsValue, JsValue> = GlTexture::call_tex_image_2d(
context.as_ref(),
internal,
pixel_format,
pixel_type,
bitmap.as_ref(),
);
context.pixel_storei(
WebGl2RenderingContext::UNPACK_FLIP_Y_WEBGL,
gl_flip_y(false),
);
if outcome.is_err() {
return None;
}
GlTexture::apply_default_parameters(context);
Some(GlTexture {
texture,
width: width.max(1),
height: height.max(1),
levels: 1,
mipmapped: false,
})
}
fn call_tex_image_2d(
context: &JsValue,
internal: i32,
pixel_format: u32,
pixel_type: u32,
source: &JsValue,
) -> Result<JsValue, JsValue> {
let cached: Option<Function> = GL_TEX_IMAGE_2D_CACHE
.with(|slot: &RefCell<Option<Function>>| slot.borrow().as_ref().cloned());
let function: Function = match cached {
Some(function) => function,
None => Self::resolve_tex_image_2d(context)?,
};
function.call6(
context,
&JsValue::from_f64(gl_texture_target_2d() as f64),
&JsValue::from_f64(GL_MIP_LEVEL_ZERO),
&JsValue::from_f64(internal as f64),
&JsValue::from_f64(pixel_format as f64),
&JsValue::from_f64(pixel_type as f64),
source,
)
}
fn resolve_tex_image_2d(context: &JsValue) -> Result<Function, JsValue> {
let name: &JsValue = &JsValue::from_str(GL_METHOD_TEX_IMAGE_2D);
let found: JsValue = Reflect::get(context, name)?;
let function: Function = found.unchecked_into();
Self::store_tex_image_2d(&function);
Ok(function)
}
fn store_tex_image_2d(function: &Function) {
GL_TEX_IMAGE_2D_CACHE.with(|slot: &RefCell<Option<Function>>| {
match slot.try_borrow_mut() {
Ok(mut borrow) => {
*borrow = Some(function.clone());
}
Err(_already_borrowed) => {}
}
});
}
pub fn set_parameters(
&self,
context: &WebGl2RenderingContext,
filter: FilterMode,
mipmap: MipmapFilter,
address: AddressMode,
) {
let target: u32 = gl_texture_target_2d();
let wrap: i32 = gl_address_mode(address) as i32;
context.tex_parameteri(
target,
WebGl2RenderingContext::TEXTURE_MIN_FILTER,
gl_min_filter(filter, mipmap) as i32,
);
context.tex_parameteri(
target,
WebGl2RenderingContext::TEXTURE_MAG_FILTER,
gl_filter_mode(filter) as i32,
);
context.tex_parameteri(target, WebGl2RenderingContext::TEXTURE_WRAP_S, wrap);
context.tex_parameteri(target, WebGl2RenderingContext::TEXTURE_WRAP_T, wrap);
}
pub fn generate_mipmap(
&mut self,
context: &WebGl2RenderingContext,
filter: FilterMode,
mipmap: MipmapFilter,
address: AddressMode,
) {
let mut levels: u32 = 1;
let mut halved: u32 = self.get_width().max(self.get_height()).max(1);
while halved > 1 {
halved /= 2;
levels += 1;
}
self.set_levels(levels);
self.set_mipmapped(true);
context.generate_mipmap(gl_texture_target_2d());
self.set_parameters(context, filter, mipmap, address);
}
pub fn update(
&self,
context: &WebGl2RenderingContext,
region: &GlScissor,
format: GpuTextureFormat,
data: &[u8],
) -> bool {
let x: u32 = (*region.get_x()).max(0) as u32;
let y: u32 = (*region.get_y()).max(0) as u32;
let width: u32 = (*region.get_width()).max(0) as u32;
let height: u32 = (*region.get_height()).max(0) as u32;
let inside: bool = x.saturating_add(width) <= self.get_width()
&& y.saturating_add(height) <= self.get_height();
if !inside {
return false;
}
let (_, pixel_format, pixel_type): (i32, u32, u32) = gl_texture_layout(format);
let _: Result<(), JsValue> = context
.tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_opt_u8_array(
gl_texture_target_2d(),
0,
x as i32,
y as i32,
width as i32,
height as i32,
pixel_format,
pixel_type,
Some(data),
);
true
}
pub fn delete(&mut self, context: &WebGl2RenderingContext) {
context.delete_texture(Some(self.get_texture()));
self.set_width(0);
self.set_height(0);
self.set_mipmapped(false);
}
}
impl GlVertexArray {
pub fn create(context: &WebGl2RenderingContext) -> Option<GlVertexArray> {
let vao: WebGlVertexArrayObject = context.create_vertex_array()?;
Some(GlVertexArray { vao })
}
pub fn bind(&self, context: &WebGl2RenderingContext) {
context.bind_vertex_array(Some(self.get_vao()));
}
pub fn set_layout(
&self,
context: &WebGl2RenderingContext,
buffer: &GlBuffer,
layout: &VertexBufferLayout,
) {
self.bind(context);
buffer.bind(context, WebGl2RenderingContext::ARRAY_BUFFER);
let stride: i32 = layout.array_stride as i32;
let divisor: u32 = match layout.step_mode {
VertexStepMode::Vertex => 0,
VertexStepMode::Instance => 1,
};
for attribute in layout.attributes.iter() {
let index: u32 = attribute.shader_location;
let (components, attribute_type, normalized): (u32, u32, bool) =
gl_attribute_layout(attribute.format);
context.enable_vertex_attrib_array(index);
context.vertex_attrib_pointer_with_i32(
index,
components as i32,
attribute_type,
normalized,
stride,
attribute.offset as i32,
);
context.vertex_attrib_divisor(index, divisor);
}
}
pub fn set_instance_layout(
&self,
context: &WebGl2RenderingContext,
buffer: &GlBuffer,
layout: &VertexBufferLayout,
) {
self.set_layout(context, buffer, layout);
}
pub fn set_divisor(&self, context: &WebGl2RenderingContext, index: u32, divisor: u32) {
self.bind(context);
context.vertex_attrib_divisor(index, divisor);
}
pub fn unbind(context: &WebGl2RenderingContext) {
context.bind_vertex_array(None);
}
pub fn delete(&self, context: &WebGl2RenderingContext) {
context.delete_vertex_array(Some(self.get_vao()));
}
}
impl GlFramebuffer {
pub fn create(
context: &WebGl2RenderingContext,
width: u32,
height: u32,
color_format: GpuTextureFormat,
depth_format: Option<GpuTextureFormat>,
) -> Option<GlFramebuffer> {
let color: GlTexture = GlTexture::create(context, width, height, color_format, &[])?;
let framebuffer: WebGlFramebuffer = context.create_framebuffer()?;
let mut depth: Option<WebGlRenderbuffer> = None;
context.bind_framebuffer(WebGl2RenderingContext::FRAMEBUFFER, Some(&framebuffer));
context.framebuffer_texture_2d(
WebGl2RenderingContext::FRAMEBUFFER,
GL_DEFAULT_COLOR_ATTACHMENT,
gl_texture_target_2d(),
Some(color.get_texture()),
0,
);
if let Some(format) = depth_format {
let renderbuffer: WebGlRenderbuffer = context.create_renderbuffer()?;
context.bind_renderbuffer(WebGl2RenderingContext::RENDERBUFFER, Some(&renderbuffer));
context.renderbuffer_storage(
WebGl2RenderingContext::RENDERBUFFER,
gl_renderbuffer_format(format),
width as i32,
height as i32,
);
let attachment: u32 = if format == GpuTextureFormat::Depth24PlusStencil8 {
WebGl2RenderingContext::DEPTH_STENCIL_ATTACHMENT
} else {
WebGl2RenderingContext::DEPTH_ATTACHMENT
};
context.framebuffer_renderbuffer(
WebGl2RenderingContext::FRAMEBUFFER,
attachment,
WebGl2RenderingContext::RENDERBUFFER,
Some(&renderbuffer),
);
depth = Some(renderbuffer);
}
context.bind_renderbuffer(WebGl2RenderingContext::RENDERBUFFER, None);
context.bind_framebuffer(WebGl2RenderingContext::FRAMEBUFFER, None);
Some(GlFramebuffer {
framebuffer,
depth,
color,
width,
height,
})
}
pub fn bind(&self, context: &WebGl2RenderingContext) -> GlFramebufferStatus {
let handle: &WebGlFramebuffer = self.get_framebuffer();
context.bind_framebuffer(WebGl2RenderingContext::DRAW_FRAMEBUFFER, Some(handle));
context.bind_framebuffer(WebGl2RenderingContext::READ_FRAMEBUFFER, Some(handle));
gl_framebuffer_status(context.check_framebuffer_status(WebGl2RenderingContext::FRAMEBUFFER))
}
pub fn matches(&self, width: u32, height: u32) -> bool {
self.get_width() == width && self.get_height() == height
}
pub fn delete(&mut self, context: &WebGl2RenderingContext) {
if let Some(renderbuffer) = self.try_get_depth() {
context.delete_renderbuffer(Some(&renderbuffer));
}
context.delete_framebuffer(Some(self.get_framebuffer()));
self.set_width(0);
self.set_height(0);
}
}
impl GlProgram {
pub fn create(
context: &WebGl2RenderingContext,
vertex_source: &str,
fragment_source: &str,
) -> Result<GlProgram, WebGlProgramError> {
let vertex_shader: WebGlShader =
GlProgram::compile(context, GlShaderKind::Vertex, vertex_source)?;
let fragment_shader: WebGlShader =
GlProgram::compile(context, GlShaderKind::Fragment, fragment_source)?;
let program: WebGlProgram = match context.create_program() {
Some(value) => value,
None => {
return Err(WebGlProgramError::ProgramLink(
GL_PROGRAM_CREATE_FAILED.to_string(),
));
}
};
context.attach_shader(&program, &vertex_shader);
context.attach_shader(&program, &fragment_shader);
context.link_program(&program);
let linked: bool = context
.get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS)
.as_bool()
.unwrap_or_default();
context.delete_shader(Some(&vertex_shader));
context.delete_shader(Some(&fragment_shader));
if !linked {
let log: String = context.get_program_info_log(&program).unwrap_or_default();
context.delete_program(Some(&program));
return Err(WebGlProgramError::ProgramLink(log));
}
Ok(GlProgram {
program,
uniforms: HashMap::new(),
matrix_scratch: [0.0; GL_MAT4_FLOATS],
blocks: HashMap::new(),
})
}
fn compile(
context: &WebGl2RenderingContext,
kind: GlShaderKind,
source: &str,
) -> Result<WebGlShader, WebGlProgramError> {
let shader: WebGlShader = match context.create_shader(gl_shader_type(kind)) {
Some(value) => value,
None => {
return Err(WebGlProgramError::ShaderCompile(
GL_SHADER_CREATE_FAILED.to_string(),
));
}
};
context.shader_source(&shader, source);
context.compile_shader(&shader);
let compiled: bool = context
.get_shader_parameter(&shader, WebGl2RenderingContext::COMPILE_STATUS)
.as_bool()
.unwrap_or_default();
if !compiled {
let log: String = context.get_shader_info_log(&shader).unwrap_or_default();
context.delete_shader(Some(&shader));
return Err(WebGlProgramError::ShaderCompile(log));
}
Ok(shader)
}
pub fn bind(&self, context: &WebGl2RenderingContext) {
context.use_program(Some(self.get_program()));
}
pub fn uniform(
&mut self,
context: &WebGl2RenderingContext,
name: &str,
) -> Option<WebGlUniformLocation> {
match self.get_uniforms().get(name) {
Some(cached) => cached.clone(),
None => {
let resolved: Option<WebGlUniformLocation> =
context.get_uniform_location(self.get_program(), name);
let mut table: HashMap<String, Option<WebGlUniformLocation>> =
self.get_uniforms().clone();
table.insert(name.to_string(), resolved.clone());
self.set_uniforms(table);
resolved
}
}
}
pub fn set_uniform_1f(&mut self, context: &WebGl2RenderingContext, name: &str, value: f32) {
let location: Option<WebGlUniformLocation> = self.uniform(context, name);
context.uniform1f(location.as_ref(), value);
}
pub fn set_uniform_2f(&mut self, context: &WebGl2RenderingContext, name: &str, x: f32, y: f32) {
let location: Option<WebGlUniformLocation> = self.uniform(context, name);
context.uniform2f(location.as_ref(), x, y);
}
pub fn set_uniform_3f(
&mut self,
context: &WebGl2RenderingContext,
name: &str,
x: f32,
y: f32,
z: f32,
) {
let location: Option<WebGlUniformLocation> = self.uniform(context, name);
context.uniform3f(location.as_ref(), x, y, z);
}
pub fn set_uniform_4f(
&mut self,
context: &WebGl2RenderingContext,
name: &str,
x: f32,
y: f32,
z: f32,
w: f32,
) {
let location: Option<WebGlUniformLocation> = self.uniform(context, name);
context.uniform4f(location.as_ref(), x, y, z, w);
}
pub fn set_uniform_mat4(
&mut self,
context: &WebGl2RenderingContext,
name: &str,
matrix: &Matrix4x4,
) {
let location: Option<WebGlUniformLocation> = self.uniform(context, name);
let mut scratch: [f32; GL_MAT4_FLOATS] = [0.0; GL_MAT4_FLOATS];
gl_matrix4_into_f32(matrix, &mut scratch);
context.uniform_matrix4fv_with_f32_array(location.as_ref(), false, &scratch);
}
pub fn set_uniform_vec4_array(
&mut self,
context: &WebGl2RenderingContext,
name: &str,
data: &[f32],
) {
let location: Option<WebGlUniformLocation> = self.uniform(context, name);
context.uniform4fv_with_f32_array(location.as_ref(), data);
}
pub fn bind_uniform_block(
&mut self,
context: &WebGl2RenderingContext,
name: &str,
binding: u32,
) -> bool {
let cached: Option<u32> = self.get_blocks().get(name).copied();
let index: Option<u32> = match cached {
Some(value) => Some(value),
None => {
let resolved: u32 = context.get_uniform_block_index(self.get_program(), name);
if resolved == WebGl2RenderingContext::UNIFORM_BLOCK_INDEX {
None
} else {
let mut table: HashMap<String, u32> = self.get_blocks().clone();
table.insert(name.to_string(), resolved);
self.set_blocks(table);
Some(resolved)
}
}
};
match index {
Some(value) => {
context.uniform_block_binding(self.get_program(), value, binding);
true
}
None => false,
}
}
pub fn delete(&mut self, context: &WebGl2RenderingContext) {
context.delete_program(Some(self.get_program()));
self.set_uniforms(HashMap::new());
self.set_blocks(HashMap::new());
}
}
impl GlUniformBlock {
pub fn create(
context: &WebGl2RenderingContext,
size: u32,
binding: u32,
) -> Option<GlUniformBlock> {
let capacity: u32 = size.max(1);
let buffer: GlBuffer = GlBuffer::create(
context,
WebGl2RenderingContext::UNIFORM_BUFFER,
capacity,
WebGl2RenderingContext::DYNAMIC_DRAW,
)?;
let block: GlUniformBlock = GlUniformBlock {
buffer: buffer.get_buffer().clone(),
binding,
capacity,
};
block.bind(context);
Some(block)
}
pub fn bind(&self, context: &WebGl2RenderingContext) {
context.bind_buffer_base(
WebGl2RenderingContext::UNIFORM_BUFFER,
self.get_binding(),
Some(self.get_buffer()),
);
}
pub fn update(&mut self, context: &WebGl2RenderingContext, offset: u32, data: &[u8]) -> bool {
let end: u32 = offset.saturating_add(data.len() as u32);
if end > self.get_capacity() {
context.bind_buffer(
WebGl2RenderingContext::UNIFORM_BUFFER,
Some(self.get_buffer()),
);
context.buffer_data_with_i32(
WebGl2RenderingContext::UNIFORM_BUFFER,
end as i32,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
self.set_capacity(end);
}
context.bind_buffer(
WebGl2RenderingContext::UNIFORM_BUFFER,
Some(self.get_buffer()),
);
context.buffer_sub_data_with_i32_and_u8_array(
WebGl2RenderingContext::UNIFORM_BUFFER,
offset as i32,
data,
);
true
}
pub fn set_mat4(
&mut self,
context: &WebGl2RenderingContext,
offset: u32,
matrix: &Matrix4x4,
) -> bool {
if offset.saturating_add(GL_MAT4_BYTES) > self.get_capacity() {
return false;
}
let mut scratch: [f32; GL_MAT4_FLOATS] = [0.0; GL_MAT4_FLOATS];
gl_matrix4_into_f32(matrix, &mut scratch);
let mut bytes: [u8; GL_MAT4_BYTES as usize] = [0; GL_MAT4_BYTES as usize];
for (index, value) in scratch.iter().enumerate() {
let raw: [u8; GL_F32_SIZE as usize] = value.to_ne_bytes();
let base: usize = index * GL_F32_SIZE as usize;
for (slot, byte) in bytes[base..base + GL_F32_SIZE as usize]
.iter_mut()
.zip(raw.iter())
{
*slot = *byte;
}
}
self.update(context, offset, &bytes)
}
pub fn delete(&mut self, context: &WebGl2RenderingContext) {
context.bind_buffer_base(
WebGl2RenderingContext::UNIFORM_BUFFER,
self.get_binding(),
None,
);
context.delete_buffer(Some(self.get_buffer()));
self.set_capacity(0);
}
}
impl GlRenderState {
pub fn context_defaults(width: i32, height: i32) -> GlRenderState {
GlRenderState {
depth: GlDepthState {
enabled: false,
compare: CompareFunction::LessEqual,
write_enabled: true,
},
blend: GlBlendState::default(),
cull: GlCullState::default(),
color_mask: GlColorMask {
bits: GL_COLOR_WRITE_ALL,
},
scissor: None,
viewport: GlViewport {
x: 0,
y: 0,
width,
height,
},
}
}
}
impl WebGl2Backend {
pub fn init(config: &RenderConfig) -> Result<WebGl2Backend, WebGl2InitError> {
let selector: String = config.get_canvas_selector().clone();
let Some(window_value) = window() else {
return Err(WebGl2InitError::CanvasNotFound(
config.get_canvas_selector().clone(),
));
};
let Some(document_value) = window_value.document() else {
return Err(WebGl2InitError::CanvasNotFound(
config.get_canvas_selector().clone(),
));
};
let element: Element = document_value
.query_selector(selector.as_str())
.map_err(|_| WebGl2InitError::CanvasQuery(selector.to_string()))?
.ok_or_else(|| WebGl2InitError::CanvasNotFound(config.get_canvas_selector().clone()))?;
let canvas: HtmlCanvasElement = element.unchecked_into();
let dpr: f64 = CanvasRenderer::detect_dpr();
let physical_width: u32 = (config.get_width() * dpr).round() as u32;
let physical_height: u32 = (config.get_height() * dpr).round() as u32;
canvas.set_width(physical_width);
canvas.set_height(physical_height);
let context_object: Object = canvas
.get_context(GL_CONTEXT_ID_WEBGL2)
.map_err(|_| WebGl2InitError::ContextLookup(selector.clone()))?
.ok_or(WebGl2InitError::ContextUnavailable)?;
let context: WebGl2RenderingContext = context_object
.dyn_into()
.map_err(|_| WebGl2InitError::ContextCast)?;
context.viewport(0, 0, physical_width as i32, physical_height as i32);
Ok(WebGl2Backend {
canvas,
context,
shadow: GlRenderState::context_defaults(physical_width as i32, physical_height as i32),
active_unit: GL_TEXTURE_UNIT_NONE,
bound_program: JsValue::UNDEFINED,
clear_color_field: Color::new(0.0, 0.0, 0.0, 1.0),
readback: Vec::new(),
})
}
pub fn is_available() -> bool {
let Some(window_value) = window() else {
return false;
};
let Some(document_value) = window_value.document() else {
return false;
};
let element: Element = match document_value.create_element(GL_DOM_TAG_CANVAS) {
Ok(value) => value,
Err(_) => return false,
};
let canvas: HtmlCanvasElement = element.unchecked_into();
canvas
.get_context(GL_CONTEXT_ID_WEBGL2)
.ok()
.flatten()
.is_some()
}
pub fn resize(&mut self, width: u32, height: u32) {
let mut state: GlRenderState = *self.get_shadow();
state.viewport = GlViewport {
x: 0,
y: 0,
width: width as i32,
height: height as i32,
};
self.set_shadow(state);
}
pub fn resize_now(&mut self, width: u32, height: u32) {
self.resize(width, height);
self.get_context()
.viewport(0, 0, width as i32, height as i32);
}
pub fn is_context_lost(&self) -> bool {
self.get_context().is_context_lost()
}
}
impl WebGl2Backend {
pub fn apply_state(&mut self, state: &GlRenderState) {
let context: &WebGl2RenderingContext = self.get_context();
let current: GlRenderState = *self.get_shadow();
let depth_toggled: bool = current.depth.enabled != state.depth.enabled;
if depth_toggled {
WebGl2Backend::set_capability(
context,
WebGl2RenderingContext::DEPTH_TEST,
state.depth.enabled,
);
}
if depth_toggled || current.depth.compare != state.depth.compare {
context.depth_func(gl_compare_function(state.depth.compare));
}
if current.depth.write_enabled != state.depth.write_enabled {
let write: bool = state.depth.write_enabled;
context.depth_mask(write);
}
let blend_toggled: bool = current.blend.enabled != state.blend.enabled;
if blend_toggled {
WebGl2Backend::set_capability(
context,
WebGl2RenderingContext::BLEND,
state.blend.enabled,
);
}
if blend_toggled
|| current.blend.color != state.blend.color
|| current.blend.alpha != state.blend.alpha
{
WebGl2Backend::apply_blend(context, state);
}
if current.cull.mode != state.cull.mode {
let culling: bool = state.cull.mode != CullMode::None;
WebGl2Backend::set_capability(context, WebGl2RenderingContext::CULL_FACE, culling);
if culling {
context.cull_face(gl_cull_face(state.cull.mode));
}
}
if current.cull.front_face != state.cull.front_face {
context.front_face(gl_front_face(state.cull.front_face));
}
if current.color_mask.bits != state.color_mask.bits {
let bits: u32 = state.color_mask.bits;
context.color_mask(
bits & GL_COLOR_CHANNEL_RED != 0,
bits & GL_COLOR_CHANNEL_GREEN != 0,
bits & GL_COLOR_CHANNEL_BLUE != 0,
bits & GL_COLOR_CHANNEL_ALPHA != 0,
);
}
if current.scissor != state.scissor {
match state.scissor {
Some(rectangle) => {
WebGl2Backend::set_capability(
context,
WebGl2RenderingContext::SCISSOR_TEST,
true,
);
context.scissor(
*rectangle.get_x(),
*rectangle.get_y(),
*rectangle.get_width(),
*rectangle.get_height(),
);
}
None => {
WebGl2Backend::set_capability(
context,
WebGl2RenderingContext::SCISSOR_TEST,
false,
);
}
}
}
if current.viewport != state.viewport {
context.viewport(
*state.viewport.get_x(),
*state.viewport.get_y(),
*state.viewport.get_width(),
*state.viewport.get_height(),
);
}
self.set_shadow(*state);
}
fn apply_blend(context: &WebGl2RenderingContext, state: &GlRenderState) {
context.blend_equation_separate(
gl_blend_equation(state.blend.color.get_operation()),
gl_blend_equation(state.blend.alpha.get_operation()),
);
context.blend_func_separate(
gl_blend_factor(state.blend.color.get_source()),
gl_blend_factor(state.blend.color.get_destination()),
gl_blend_factor(state.blend.alpha.get_source()),
gl_blend_factor(state.blend.alpha.get_destination()),
);
}
fn set_capability(context: &WebGl2RenderingContext, capability: u32, enabled: bool) {
if enabled {
context.enable(capability);
} else {
context.disable(capability);
}
}
pub fn invalidate_state(&mut self, width: u32, height: u32) {
let fresh: GlRenderState = GlRenderState::context_defaults(width as i32, height as i32);
self.set_shadow(fresh);
self.set_active_unit(GL_TEXTURE_UNIT_NONE);
self.set_bound_program(JsValue::UNDEFINED);
}
pub fn set_clear_color(&mut self, color: Color) {
self.set_clear_color_field(color);
}
pub fn begin_frame(&mut self, context: &WebGl2RenderingContext, width: u32, height: u32) {
let color: Color = self.get_clear_color_field();
context.clear_color(
color.get_red() as f32,
color.get_green() as f32,
color.get_blue() as f32,
color.get_alpha() as f32,
);
context.clear(
WebGl2RenderingContext::COLOR_BUFFER_BIT | WebGl2RenderingContext::DEPTH_BUFFER_BIT,
);
let mut state: GlRenderState = *self.get_shadow();
state.viewport = GlViewport {
x: 0,
y: 0,
width: width as i32,
height: height as i32,
};
self.set_shadow(state);
}
pub fn clear_depth(&self, context: &WebGl2RenderingContext) {
context.clear(WebGl2RenderingContext::DEPTH_BUFFER_BIT);
}
pub fn render_frame(
&mut self,
context: &WebGl2RenderingContext,
program: &GlProgram,
color: Color,
vertex_count: u32,
) {
self.set_clear_color(color);
self.begin_frame(context, self.canvas_width(), self.canvas_height());
self.use_program(context, program);
let args: DrawArgs = DrawArgs::whole_stream(vertex_count, 1);
self.draw_arrays(context, PrimitiveTopology::TriangleList, &args);
}
fn canvas_width(&self) -> u32 {
self.get_canvas().width()
}
fn canvas_height(&self) -> u32 {
self.get_canvas().height()
}
}
impl WebGl2Backend {
pub fn use_program(&mut self, context: &WebGl2RenderingContext, program: &GlProgram) {
let candidate: &JsValue = program.get_program().as_ref();
if !Object::is(self.get_bound_program(), candidate) {
context.use_program(Some(program.get_program()));
let owned: JsValue = candidate.clone();
self.set_bound_program(owned);
}
}
pub fn bind_texture_unit(
&mut self,
context: &WebGl2RenderingContext,
unit: u32,
texture: &GlTexture,
) {
if self.get_active_unit() != unit {
context.active_texture(gl_texture_unit(unit));
self.set_active_unit(unit);
}
context.bind_texture(gl_texture_target_2d(), Some(texture.get_texture()));
}
pub fn draw_arrays(
&self,
context: &WebGl2RenderingContext,
topology: PrimitiveTopology,
args: &DrawArgs,
) {
let mode: u32 = gl_primitive_mode(topology);
let first: i32 = args.get_first_vertex() as i32;
let count: i32 = args.get_vertex_count() as i32;
if args.get_instance_count() <= 1 {
context.draw_arrays(mode, first, count);
} else {
context.draw_arrays_instanced(mode, first, count, args.get_instance_count() as i32);
}
}
pub fn draw_elements(
&self,
context: &WebGl2RenderingContext,
topology: PrimitiveTopology,
args: &DrawIndexedArgs,
index_format: IndexFormat,
) -> bool {
if args.get_base_vertex() != 0 {
return false;
}
let (index_type, stride): (u32, u32) = gl_index_type(index_format);
let mode: u32 = gl_primitive_mode(topology);
let count: i32 = args.get_index_count() as i32;
let offset: i32 = (args.get_first_index() * stride) as i32;
if args.get_instance_count() <= 1 {
context.draw_elements_with_i32(mode, count, index_type, offset);
} else {
context.draw_elements_instanced_with_i32(
mode,
count,
index_type,
offset,
args.get_instance_count() as i32,
);
}
true
}
pub fn draw_element_range(
&self,
context: &WebGl2RenderingContext,
topology: PrimitiveTopology,
start: u32,
end: u32,
index_format: IndexFormat,
) -> bool {
if end < start {
return false;
}
let (index_type, _stride): (u32, u32) = gl_index_type(index_format);
context.draw_range_elements_with_i32(
gl_primitive_mode(topology),
start,
end,
(end - start + 1) as i32,
index_type,
0,
);
true
}
pub fn draw_arrays_instanced(
&self,
context: &WebGl2RenderingContext,
topology: PrimitiveTopology,
args: &DrawArgs,
) {
let instances: i32 = args.get_instance_count().max(1) as i32;
context.draw_arrays_instanced(
gl_primitive_mode(topology),
args.get_first_vertex() as i32,
args.get_vertex_count() as i32,
instances,
);
}
pub fn draw_elements_instanced(
&self,
context: &WebGl2RenderingContext,
topology: PrimitiveTopology,
args: &DrawIndexedArgs,
index_format: IndexFormat,
) -> bool {
self.draw_elements(context, topology, args, index_format)
}
pub fn read_pixels(
&mut self,
context: &WebGl2RenderingContext,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Vec<u8> {
if width == 0 || height == 0 {
return Vec::new();
}
let needed: usize = (width as usize) * (height as usize) * GL_RGBA_TEXEL_SIZE;
if self.get_readback().len() < needed {
self.set_readback(vec![0; needed]);
}
let mut storage: Vec<u8> = mem::take(self.get_mut_readback());
let _: Result<(), JsValue> = context.read_pixels_with_opt_u8_array(
x,
y,
width as i32,
height as i32,
WebGl2RenderingContext::RGBA,
WebGl2RenderingContext::UNSIGNED_BYTE,
Some(&mut storage[..needed]),
);
let bytes: Vec<u8> = storage[..needed].to_vec();
self.set_readback(storage);
bytes
}
}