pub const Z_MIN: i16 = 0;
pub const Z_MAX: i16 = 32_767i16;
use super::gpu::{
new_fragment_shader, new_vertex_shader, Gpu, GpuConfig, GpuMesh, RenderPipelineHandle,
UniformValue, VertexDescriptor, WasmHeapBuffer,
};
use crate::{
quicksilver_compat::Color, ErrorMessage, JsError, NutsCheck, PaddleResult, Paint, Rectangle,
Render, Transform, Vector,
};
use wasm_bindgen::JsCast;
use web_sys::{HtmlCanvasElement, WebGlRenderingContext};
pub(crate) struct WebGLCanvas {
pixels: Vector,
mesh: GpuMesh,
canvas: HtmlCanvasElement,
gl: WebGlRenderingContext,
buffer: WasmHeapBuffer,
gpu: Gpu,
}
impl WebGLCanvas {
pub fn new(
canvas: HtmlCanvasElement,
pixels: impl Into<Vector>,
gpu_config: &GpuConfig,
) -> PaddleResult<Self> {
let pixels = pixels.into();
canvas.set_width(pixels.x as u32);
canvas.set_height(pixels.y as u32);
let gl = canvas
.get_context("webgl")
.map_err(|_| ErrorMessage::technical("Failed loading WebGL".to_owned()))?
.unwrap()
.dyn_into::<WebGlRenderingContext>()
.map_err(|_| ErrorMessage::technical("Failed loading WebGL".to_owned()))?;
let buffer = WasmHeapBuffer::new();
let projection = Transform::scale((1.0, -1.0))
* Transform::translate((-1.0, -1.0))
* Transform::scale(pixels.recip() * 2.0);
let gpu = Gpu::new(&gl, projection, gpu_config)?;
let window = WebGLCanvas {
pixels,
mesh: GpuMesh::new(),
canvas,
gl,
buffer,
gpu,
};
Ok(window)
}
pub fn html_element(&self) -> &HtmlCanvasElement {
&self.canvas
}
pub fn clone_webgl(&self) -> WebGlRenderingContext {
self.gl.clone()
}
pub fn resolution(&self) -> Vector {
self.pixels
}
pub fn render(
&mut self,
draw: &impl Render,
area: Rectangle,
trans: Transform,
paint: &impl Paint,
z: i16,
) {
debug_assert!(z >= Z_MIN);
debug_assert!(z <= Z_MAX);
self.ensure_render_pipeline(paint.render_pipeline())
.expect("Failed to set render pipeline");
draw.render(&mut self.mesh, area, trans, paint, z);
}
pub(crate) fn set_size(&mut self, size: impl Into<Vector>) {
let target_size = size.into();
self.canvas
.set_attribute(
"style",
&format!("width: {}px; height: {}px", target_size.x, target_size.y),
)
.map_err(JsError::from_js_value)
.map_err(ErrorMessage::from)
.nuts_check();
}
pub fn flush(&mut self) -> PaddleResult<()> {
if self.gpu.depth_tests_enabled {
self.mesh.triangles.sort_by(|a, b| b.cmp(a));
self.gl.clear(WebGlRenderingContext::DEPTH_BUFFER_BIT);
} else {
self.mesh.triangles.sort();
}
self.gpu.perform_draw_calls(
&mut self.buffer,
&self.gl,
&self.mesh.vertices,
self.mesh.triangles.as_slice(),
)?;
self.mesh.clear();
Ok(())
}
pub fn clear(&mut self, color: Color) {
self.gl.clear_color(color.r, color.g, color.b, color.a);
self.gl.clear(
WebGlRenderingContext::COLOR_BUFFER_BIT | WebGlRenderingContext::DEPTH_BUFFER_BIT,
);
}
pub fn ensure_render_pipeline(&mut self, rp: RenderPipelineHandle) -> PaddleResult<()> {
if rp != self.gpu.active_render_pipeline() {
self.flush()?;
self.gpu.use_render_pipeline(&self.gl, rp);
}
Ok(())
}
pub fn new_render_pipeline(
&mut self,
vertex_shader_text: &'static str,
fragment_shader_text: &'static str,
vertex_descriptor: VertexDescriptor,
uniform_values: &[(&'static str, UniformValue)],
) -> PaddleResult<RenderPipelineHandle> {
let vertex_shader = new_vertex_shader(&self.gl, vertex_shader_text)?;
let fragment_shader = new_fragment_shader(&self.gl, fragment_shader_text)?;
self.gpu.new_render_pipeline(
&self.gl,
vertex_shader,
fragment_shader,
vertex_descriptor,
uniform_values,
)
}
pub fn update_uniform(
&mut self,
rp: RenderPipelineHandle,
name: &'static str,
value: &super::gpu::UniformValue,
) {
self.gpu.update_uniform(&self.gl, rp, name, value)
}
}
impl Drop for WebGLCanvas {
fn drop(&mut self) {
self.gpu.custom_drop(&self.gl);
}
}