Rust WebGL Example: A Beginner’s Guide to Web Graphics with Rust

WebGL is a powerful JavaScript API for rendering interactive 2D and 3D graphics within a web browser. Rust, with its performance and memory safety, is an excellent choice for developing WebGL applications. With tools like wasm-bindgen and web-sys, you can create WebGL programs in Rust that run seamlessly in the browser.

This article provides a practical example of setting up a WebGL application using Rust, WebAssembly (WASM), and WebGL.


Why Use Rust for WebGL?

  1. Performance: Rust compiles to highly efficient WebAssembly, ideal for graphics-intensive tasks.
  2. Memory Safety: Eliminates common bugs like buffer overflows and null pointer dereferences.
  3. Cross-Platform: Develop once and run on any WebAssembly-supported browser.
  4. Rich Ecosystem: Libraries like wasm-bindgen and web-sys streamline WebGL development.

Setting Up Your Rust WebGL Project

Step 1: Install Rust and Wasm Target

Ensure you have Rust installed. Then, add the WebAssembly target:

rustup target add wasm32-unknown-unknown

Step 2: Create a New Rust Project

Create a new Rust project:

cargo new rust_webgl_example --lib
cd rust_webgl_example

Step 3: Add Dependencies

Update your Cargo.toml file to include the necessary dependencies:

[dependencies]
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["WebGl2"] }
  • wasm-bindgen: Enables interaction between Rust and JavaScript.
  • web-sys: Provides bindings for Web APIs, including WebGL.

Step 4: Set Up the Project Structure

Your project structure should look like this:

rust_webgl_example/
├── Cargo.toml
├── src/
│   └── lib.rs

Step 5: Write the Rust Code

Replace src/lib.rs with the following code:

use wasm_bindgen::prelude::*;
use web_sys::{WebGl2RenderingContext, WebGlProgram, WebGlShader};

#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
    // Access the document and canvas
    let window = web_sys::window().ok_or("No global window found")?;
    let document = window.document().ok_or("No document found")?;
    let canvas = document
        .get_element_by_id("canvas")
        .ok_or("No canvas found")?
        .dyn_into::<web_sys::HtmlCanvasElement>()?;

    // Get the WebGL rendering context
    let gl = canvas
        .get_context("webgl2")?
        .ok_or("Failed to get WebGL2 context")?
        .dyn_into::<WebGl2RenderingContext>()?;

    // Initialize shaders
    let vertex_shader = compile_shader(
        &gl,
        WebGl2RenderingContext::VERTEX_SHADER,
        r#"
        attribute vec4 position;
        void main() {
            gl_Position = position;
        }
        "#,
    )?;
    let fragment_shader = compile_shader(
        &gl,
        WebGl2RenderingContext::FRAGMENT_SHADER,
        r#"
        precision mediump float;
        void main() {
            gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
        }
        "#,
    )?;

    let program = link_program(&gl, &vertex_shader, &fragment_shader)?;
    gl.use_program(Some(&program));

    // Set up the vertices
    let vertices: [f32; 6] = [
        0.0,  0.5,  // Top vertex
       -0.5, -0.5,  // Bottom left vertex
        0.5, -0.5,  // Bottom right vertex
    ];

    let buffer = gl.create_buffer().ok_or("Failed to create buffer")?;
    gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&buffer));
    unsafe {
        let vertices_array = js_sys::Float32Array::view(&vertices);
        gl.buffer_data_with_array_buffer_view(
            WebGl2RenderingContext::ARRAY_BUFFER,
            &vertices_array,
            WebGl2RenderingContext::STATIC_DRAW,
        );
    }

    // Link the position attribute in the vertex shader
    let position = gl.get_attrib_location(&program, "position") as u32;
    gl.vertex_attrib_pointer_with_i32(position, 2, WebGl2RenderingContext::FLOAT, false, 0, 0);
    gl.enable_vertex_attrib_array(position);

    // Clear the canvas and draw the triangle
    gl.clear_color(0.0, 0.0, 0.0, 1.0);
    gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
    gl.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, 3);

    Ok(())
}

fn compile_shader(
    gl: &WebGl2RenderingContext,
    shader_type: u32,
    source: &str,
) -> Result<WebGlShader, String> {
    let shader = gl
        .create_shader(shader_type)
        .ok_or("Unable to create shader object")?;
    gl.shader_source(&shader, source);
    gl.compile_shader(&shader);

    if gl.get_shader_parameter(&shader, WebGl2RenderingContext::COMPILE_STATUS)
        .as_bool()
        .unwrap_or(false)
    {
        Ok(shader)
    } else {
        Err(gl.get_shader_info_log(&shader).unwrap_or_default())
    }
}

fn link_program(
    gl: &WebGl2RenderingContext,
    vertex_shader: &WebGlShader,
    fragment_shader: &WebGlShader,
) -> Result<WebGlProgram, String> {
    let program = gl
        .create_program()
        .ok_or("Unable to create shader program")?;
    gl.attach_shader(&program, vertex_shader);
    gl.attach_shader(&program, fragment_shader);
    gl.link_program(&program);

    if gl.get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS)
        .as_bool()
        .unwrap_or(false)
    {
        Ok(program)
    } else {
        Err(gl.get_program_info_log(&program).unwrap_or_default())
    }
}

Step 6: Set Up JavaScript and HTML

Create an index.html file in the project root:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rust WebGL Example</title>
</head>
<body>
    <canvas id="canvas" width="800" height="600"></canvas>
    <script type="module">
        import init from "./pkg/rust_webgl_example.js";
        init();
    </script>
</body>
</html>

Step 7: Build and Serve the Project

  1. Open http://localhost:8000 in your browser.

Serve the project using a local HTTP server (e.g., Python):

python3 -m http.server

Build the project:

wasm-pack build --target web

Install wasm-pack:

cargo install wasm-pack

Result

You’ll see a black canvas displaying a green triangle. This demonstrates how Rust and WebGL can work together to create simple, efficient graphics.


Conclusion

Rust and WebGL, when combined with WebAssembly, provide a powerful tool for building high-performance web graphics applications. This example demonstrates the basics of initializing a WebGL context, creating shaders, and rendering a simple triangle. With this foundation, you can expand to more complex 2D or 3D applications.