Creating a Mandelbrot set visualization with Rust and WebAssembly involves using wasm-bindgen and web-sys to interact with the WebGL API. Here's how you can translate your Web Component into Rust and compile it to WebAssembly:
-
Install Rust and the wasm32 target:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup target add wasm32-unknown-unknown
-
Install
wasm-bindgen-cli:cargo install wasm-bindgen-cli
-
Create a new Rust project:
cargo new mandelbrot --lib cd mandelbrot -
Add dependencies in
Cargo.toml:[dependencies] wasm-bindgen = "0.2" web-sys = { version = "0.3", features = ["Window", "Document", "HtmlCanvasElement", "WebGl2RenderingContext"] } [lib] crate-type = ["cdylib"] [profile.release] opt-level = "z"
Create the src/lib.rs file with the following content:
use wasm_bindgen::prelude::*;
use web_sys::{window, HtmlCanvasElement, WebGl2RenderingContext, WebGlProgram, WebGlShader};
#[wasm_bindgen(start)]
pub fn start() {
let window = window().expect("no global `window` exists");
let document = window.document().expect("should have a document on window");
let body = document.body().expect("document should have a body");
let canvas = document.create_element("canvas").unwrap();
canvas.set_id("mandelbrot-canvas");
body.append_child(&canvas).unwrap();
let canvas: HtmlCanvasElement = canvas.dyn_into::<HtmlCanvasElement>().unwrap();
canvas.set_width(window.inner_width().unwrap().as_f64().unwrap() as u32);
canvas.set_height(window.inner_height().unwrap().as_f64().unwrap() as u32);
let gl: WebGl2RenderingContext = canvas
.get_context("webgl2")
.unwrap()
.unwrap()
.dyn_into::<WebGl2RenderingContext>()
.unwrap();
let vertex_shader = compile_shader(
&gl,
WebGl2RenderingContext::VERTEX_SHADER,
r#"#version 300 es
in vec4 a_position;
void main() {
gl_Position = a_position;
}"#,
)
.unwrap();
let fragment_shader = compile_shader(
&gl,
WebGl2RenderingContext::FRAGMENT_SHADER,
r#"#version 300 es
precision highp float;
uniform float u_zoomFactor;
uniform vec2 u_center;
uniform vec2 u_resolution;
out vec4 outColor;
vec3 getColor(int iterations, int maxIterations) {
float t = float(iterations) / float(maxIterations);
float r = 9.0 * (1.0 - t) * t * t * t;
float g = 15.0 * (1.0 - t) * (1.0 - t) * t * t;
float b = 8.5 * (1.0 - t) * (1.0 - t) * (1.0 - t) * t;
return vec3(r, g, b);
}
void main() {
vec2 c = (gl_FragCoord.xy - u_resolution / 2.0) * 4.0 / (u_resolution * (u_zoomFactor * 0.5)) + u_center;
vec2 z = vec2(0.0);
int maxIterations = int(min(200.0, max(u_zoomFactor, 30.)));; // Dynamically adjust iterations
int i;
for (i = 0; i < maxIterations; i++) {
vec2 z2 = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
if (dot(z2, z2) > 4.0) break;
z = z2;
}
outColor = vec4(getColor(i, maxIterations), 1.0);
}"#,
)
.unwrap();
let program = link_program(&gl, &vertex_shader, &fragment_shader).unwrap();
gl.use_program(Some(&program));
// Define the positions for the rectangle.
let positions: [f32; 12] = [
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0,
];
let buffer = gl.create_buffer().unwrap();
gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&buffer));
unsafe {
let positions_array_buf_view = js_sys::Float32Array::view(&positions);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&positions_array_buf_view,
WebGl2RenderingContext::STATIC_DRAW,
);
}
let position_location = gl.get_attrib_location(&program, "a_position") as u32;
gl.enable_vertex_attrib_array(position_location);
gl.vertex_attrib_pointer_with_i32(position_location, 2, WebGl2RenderingContext::FLOAT, false, 0, 0);
let zoom_factor_location = gl.get_uniform_location(&program, "u_zoomFactor").unwrap();
let center_location = gl.get_uniform_location(&program, "u_center").unwrap();
let resolution_location = gl.get_uniform_location(&program, "u_resolution").unwrap();
gl.uniform1f(Some(&zoom_factor_location), 1.0);
gl.uniform2f(Some(¢er_location), -0.743643887037151, 0.131825904205330);
gl.uniform2f(Some(&resolution_location), canvas.width() as f32, canvas.height() as f32);
gl.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, 6);
}
fn compile_shader(
gl: &WebGl2RenderingContext,
shader_type: u32,
source: &str,
) -> Result<WebGlShader, String> {
let shader = gl.create_shader(shader_type).ok_or_else(|| String::from("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_else(|| String::from("Unknown error creating shader")))
}
}
fn link_program(
gl: &WebGl2RenderingContext,
vertex_shader: &WebGlShader,
fragment_shader: &WebGlShader,
) -> Result<WebGlProgram, String> {
let program = gl.create_program().ok_or_else(|| String::from("Unable to create shader object"))?;
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_else(|| String::from("Unknown error creating program object")))
}
}-
Build the Rust project:
cargo build --release --target wasm32-unknown-unknown
-
Generate WebAssembly bindings:
wasm-bindgen target/wasm32-unknown-unknown/release/mandelbrot.wasm --out-dir ./pkg --web
Create an index.html file to load the WebAssembly module:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mandelbrot Set</title>
</head>
<body>
<script type="module">
import init from './pkg/mandelbrot.js';
init();
</script>
</body>
</html>Use a simple HTTP server to serve your project. You can use http-server from npm:
npx http-server .Open the URL provided by the server to see your Mandelbrot set visualization in action.
You now have a Rust and WebAssembly implementation of the Mandelbrot set!