A First Shader
Now that we can get input into our game, it's time to display output for the user. We'll be using WebGL2.
Mozilla provides a great bunch of tutorials on webgl, the first of which is here This (and the next few pages) are heavily based on these tutorials.
A HTML canvas con be a whole bunch of things, only one of which is webgl. As a result, we have to specifically fetch webgl2 from the canvas:
#![allow(unused)] fn main() { fn get_gl_context(canvas: &HtmlCanvasElement) -> Result<WebGl2RenderingContext, JsValue> { Ok(canvas.get_context("webgl2")?.unwrap().dyn_into()?) } }
That's the easy part. From their to the first triangle is quite a long way. The reason it is so complex is because it is a complex thing. We need to:
- Provide a matching vertex and fragment shader (that compile with no errors)
- Provide a bunch of vertices for the shader to operate on
Porting from the Mozilla tutorials wasn't too hard, but:
- Because Rust is amazing, you have to in a bunch of error checking for JS errors
- I stripped out all the uniforms for now to make this example simpler
- Because Rust doesn't seem to have the mat4 object, I removed the perspective matrix projection from the vertex shader
After that, we have:
A triangle!
Most of the ported code for is in the file triangle.rs
:
#![allow(unused)] fn main() { use wasm_bindgen::{JsCast, JsValue}; use web_sys::{WebGl2RenderingContext, WebGlBuffer, WebGlProgram, WebGlShader}; /// An error to represent problems with a shader. #[derive(Debug)] pub enum ShaderError { /// Call to gl.create_shader returned null ShaderAllocError, /// Call to create_program returned null ShaderProgramAllocError, ShaderCompileError { shader_type: u32, compiler_output: String, }, /// Failed to receive error information about why the shader failed to compile /// Generally this is indicative of trying to get the error when one hasn't occured ShaderGetInfoError, /// I think this means that the Vertex and Fragment shaders incompatible ShaderLinkError(), } /// An error with this whole object. #[derive(Debug)] pub enum TriangleError { /// Failed to upload buffer data to the GPU BufferCreationFailed, /// An unhandled/unspecified error JsError(JsValue), /// Something wrong with the shader ShaderError(ShaderError), } impl From<JsValue> for TriangleError { fn from(err: JsValue) -> TriangleError { TriangleError::JsError(err) } } impl From<ShaderError> for TriangleError { fn from(err: ShaderError) -> TriangleError { TriangleError::ShaderError(err) } } pub struct FirstTriangle { position_buffer: WebGlBuffer, program: WebGlProgram, attrib_vertex_positions: u32, } impl FirstTriangle { pub fn new(gl: &WebGl2RenderingContext) -> Result<Self, TriangleError> { let position_buffer = upload_array_f32(gl, vec![-1.0, 1.0, 1.0, 1.0, 0.0, -1.0])?; let program = init_shader_program( gl, include_str!("resources/shader.vert"), include_str!("resources/shader.frag"), )?; let attrib_vertex_positions = gl.get_attrib_location(&program, "aVertexPosition") as u32; Ok(Self { position_buffer, program, attrib_vertex_positions, }) } pub fn render(&mut self, gl: &WebGl2RenderingContext) { gl.use_program(Some(&self.program)); gl.bind_buffer( WebGl2RenderingContext::ARRAY_BUFFER, Some(&self.position_buffer), ); gl.vertex_attrib_pointer_with_i32( self.attrib_vertex_positions, 2, // num components WebGl2RenderingContext::FLOAT, false, // normalize 0, // stride 0, // offset ); gl.enable_vertex_attrib_array(self.attrib_vertex_positions); gl.draw_arrays( WebGl2RenderingContext::TRIANGLE_STRIP, 0, //offset, 3, // vertex count ); } } fn upload_array_f32( gl: &WebGl2RenderingContext, vertices: Vec<f32>, ) -> Result<WebGlBuffer, TriangleError> { let position_buffer = gl .create_buffer() .ok_or(TriangleError::BufferCreationFailed)?; gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&position_buffer)); let memory_buffer = wasm_bindgen::memory() .dyn_into::<js_sys::WebAssembly::Memory>()? .buffer(); let vertices_location = vertices.as_ptr() as u32 / 4; let vert_array = js_sys::Float32Array::new(&memory_buffer) .subarray(vertices_location, vertices_location + vertices.len() as u32); gl.buffer_data_with_array_buffer_view( WebGl2RenderingContext::ARRAY_BUFFER, &vert_array, WebGl2RenderingContext::STATIC_DRAW, ); Ok(position_buffer) } fn load_shader( gl: &WebGl2RenderingContext, shader_type: u32, shader_text: &str, ) -> Result<WebGlShader, ShaderError> { let shader = gl .create_shader(shader_type) .ok_or(ShaderError::ShaderAllocError)?; gl.shader_source(&shader, shader_text); gl.compile_shader(&shader); if !gl .get_shader_parameter(&shader, WebGl2RenderingContext::COMPILE_STATUS) .is_truthy() { let compiler_output = &gl .get_shader_info_log(&shader) .ok_or(ShaderError::ShaderGetInfoError)?; gl.delete_shader(Some(&shader)); return Err(ShaderError::ShaderCompileError { shader_type, compiler_output: compiler_output.to_string(), }); } Ok(shader) } pub fn init_shader_program( gl: &WebGl2RenderingContext, vert_source: &str, frag_source: &str, ) -> Result<WebGlProgram, ShaderError> { let vert_shader = load_shader(gl, WebGl2RenderingContext::VERTEX_SHADER, vert_source)?; let frag_shader = load_shader(gl, WebGl2RenderingContext::FRAGMENT_SHADER, frag_source)?; let shader_program = gl .create_program() .ok_or(ShaderError::ShaderProgramAllocError)?; gl.attach_shader(&shader_program, &vert_shader); gl.attach_shader(&shader_program, &frag_shader); gl.link_program(&shader_program); if !(gl.get_program_parameter(&shader_program, WebGl2RenderingContext::LINK_STATUS)).is_truthy() { gl.delete_program(Some(&shader_program)); gl.delete_shader(Some(&vert_shader)); gl.delete_shader(Some(&frag_shader)); return Err(ShaderError::ShaderLinkError()); } Ok(shader_program) } }