├── .gitignore ├── Cargo.toml ├── README.md ├── public └── index.html ├── index.html ├── package.json ├── LICENSE └── src ├── emscripten.rs ├── matrix.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | node_modules 4 | npm-debug.log 5 | public/rust-webgl2-example.js 6 | public/rust-webgl2-example.wasm 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-webgl2-example" 3 | version = "0.1.0" 4 | authors = ["Yosuke Onoue "] 5 | 6 | [dependencies] 7 | gleam = "0.5" 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-webgl2-example 2 | 3 | Rust + WebAssembly + WebGL 2.0 Demo 4 | 5 | Demo: https://likr-sandbox.github.io/rust-webgl2-example 6 | 7 | # How to build and run 8 | 9 | ```console 10 | $ source path/to/emsdk/emsdk_env.sh 11 | $ npm start 12 | ``` 13 | 14 | And then, open http://127.0.0.1:8080 in your browser. 15 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | rust-webgl2-example 6 | 7 | 8 |
9 |

Rust + WebAssembly + WebGL 2.0 Demo

10 | source code 11 |
12 |
13 | 14 |
15 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rust-webgl2-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "EMCC_CFLAGS='-s USE_WEBGL2=1' cargo build --release --target=wasm32-unknown-emscripten", 8 | "copy": "npm run copy:js && npm run copy:wasm", 9 | "copy:js": "cp target/wasm32-unknown-emscripten/release/rust-webgl2-example.js public/rust-webgl2-example.js", 10 | "copy:wasm": "cp target/wasm32-unknown-emscripten/release/deps/rust_webgl2_example.wasm public/rust-webgl2-example.wasm", 11 | "deploy": "npm run prepare && gh-pages -d public", 12 | "prepare": "npm run build && npm run copy", 13 | "start": "npm run prepare && http-server public" 14 | }, 15 | "keywords": [], 16 | "author": "Yosuke Onoue", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "gh-pages": "^0.12.0", 20 | "http-server": "^0.9.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Yosuke Onoue 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/emscripten.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | #![allow(non_snake_case)] 3 | 4 | pub type em_arg_callback_func = Option; 5 | 6 | pub type EMSCRIPTEN_WEBGL_CONTEXT_HANDLE = ::std::os::raw::c_int; 7 | 8 | #[repr(C)] 9 | #[derive(Debug, Copy)] 10 | pub struct EmscriptenWebGLContextAttributes { 11 | pub alpha: ::std::os::raw::c_int, 12 | pub depth: ::std::os::raw::c_int, 13 | pub stencil: ::std::os::raw::c_int, 14 | pub antialias: ::std::os::raw::c_int, 15 | pub premultipliedAlpha: ::std::os::raw::c_int, 16 | pub preserveDrawingBuffer: ::std::os::raw::c_int, 17 | pub preferLowPowerToHighPerformance: ::std::os::raw::c_int, 18 | pub failIfMajorPerformanceCaveat: ::std::os::raw::c_int, 19 | pub majorVersion: ::std::os::raw::c_int, 20 | pub minorVersion: ::std::os::raw::c_int, 21 | pub enableExtensionsByDefault: ::std::os::raw::c_int, 22 | pub explicitSwapControl: ::std::os::raw::c_int, 23 | } 24 | 25 | impl Clone for EmscriptenWebGLContextAttributes { 26 | fn clone(&self) -> Self { 27 | *self 28 | } 29 | } 30 | 31 | extern "C" { 32 | pub fn emscripten_set_main_loop_arg( 33 | func: em_arg_callback_func, 34 | arg: *mut ::std::os::raw::c_void, 35 | fps: ::std::os::raw::c_int, 36 | simulate_infinite_loop: ::std::os::raw::c_int, 37 | ); 38 | 39 | pub fn emscripten_GetProcAddress( 40 | name: *const ::std::os::raw::c_char, 41 | ) -> *const ::std::os::raw::c_void; 42 | 43 | pub fn emscripten_webgl_init_context_attributes( 44 | attributes: *mut EmscriptenWebGLContextAttributes, 45 | ); 46 | 47 | pub fn emscripten_webgl_create_context( 48 | target: *const ::std::os::raw::c_char, 49 | attributes: *const EmscriptenWebGLContextAttributes, 50 | ) -> EMSCRIPTEN_WEBGL_CONTEXT_HANDLE; 51 | 52 | pub fn emscripten_webgl_make_context_current( 53 | context: EMSCRIPTEN_WEBGL_CONTEXT_HANDLE, 54 | ) -> ::std::os::raw::c_int; 55 | 56 | pub fn emscripten_get_element_css_size( 57 | target: *const ::std::os::raw::c_char, 58 | width: *mut f64, 59 | height: *mut f64, 60 | ) -> ::std::os::raw::c_int; 61 | } 62 | -------------------------------------------------------------------------------- /src/matrix.rs: -------------------------------------------------------------------------------- 1 | pub type Vec3 = [f32; 3]; 2 | pub type Matrix44 = [f32; 16]; 3 | 4 | pub fn zeros() -> Matrix44 { 5 | [0f32; 16] 6 | } 7 | 8 | pub fn identity() -> Matrix44 { 9 | let mut matrix = zeros(); 10 | matrix[0] = 1.0; 11 | matrix[5] = 1.0; 12 | matrix[10] = 1.0; 13 | matrix[15] = 1.0; 14 | matrix 15 | } 16 | 17 | pub fn rotate_x(theta: f32) -> Matrix44 { 18 | let mut matrix = identity(); 19 | matrix[5] = theta.cos(); 20 | matrix[6] = theta.sin(); 21 | matrix[9] = -theta.sin(); 22 | matrix[10] = theta.cos(); 23 | matrix 24 | } 25 | 26 | pub fn rotate_y(theta: f32) -> Matrix44 { 27 | let mut matrix = identity(); 28 | matrix[0] = theta.cos(); 29 | matrix[2] = theta.sin(); 30 | matrix[8] = -theta.sin(); 31 | matrix[10] = theta.cos(); 32 | matrix 33 | } 34 | 35 | pub fn translate(x: f32, y: f32, z: f32) -> Matrix44 { 36 | let mut matrix = identity(); 37 | matrix[12] = x; 38 | matrix[13] = y; 39 | matrix[14] = z; 40 | matrix 41 | } 42 | 43 | pub fn cross(v1: Vec3, v2: Vec3) -> Vec3 { 44 | [ 45 | v1[1] * v2[2] - v1[2] * v2[1], 46 | v1[2] * v2[0] - v1[0] * v2[2], 47 | v1[0] * v2[1] - v1[1] * v2[0], 48 | ] 49 | } 50 | 51 | pub fn normalize(v: Vec3) -> Vec3 { 52 | let sum = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt(); 53 | [v[0] / sum, v[1] / sum, v[2] / sum] 54 | } 55 | 56 | pub fn viewing_matrix(eye: Vec3, up: Vec3, target: Vec3) -> Matrix44 { 57 | let d = [target[0] - eye[0], target[1] - eye[1], target[2] - eye[2]]; 58 | let r = cross(up, d); 59 | let f = cross(d, r); 60 | let d = normalize(d); 61 | let r = normalize(r); 62 | let f = normalize(f); 63 | let mut matrix = identity(); 64 | matrix[0] = r[0]; 65 | matrix[4] = r[1]; 66 | matrix[8] = r[2]; 67 | matrix[1] = f[0]; 68 | matrix[5] = f[1]; 69 | matrix[9] = f[2]; 70 | matrix[2] = -d[0]; 71 | matrix[6] = -d[1]; 72 | matrix[10] = -d[2]; 73 | matmul(translate(-eye[0], -eye[1], -eye[2]), matrix) 74 | } 75 | 76 | pub fn orthogonal_matrix( 77 | left: f32, 78 | right: f32, 79 | top: f32, 80 | bottom: f32, 81 | near: f32, 82 | far: f32, 83 | ) -> Matrix44 { 84 | let mut matrix = zeros(); 85 | let w = right - left; 86 | let x = right + left; 87 | let h = top - bottom; 88 | let y = top + bottom; 89 | let d = far - near; 90 | let z = far + near; 91 | matrix[0] = 2.0 / w; 92 | matrix[5] = 2.0 / h; 93 | matrix[10] = -1.0 / d; 94 | matrix[12] = -x / w; 95 | matrix[13] = -y / h; 96 | matrix[14] = -z / d; 97 | matrix[15] = 1.0; 98 | matrix 99 | } 100 | 101 | pub fn perspective_matrix(fov: f32, aspect: f32, near: f32, far: f32) -> Matrix44 { 102 | let mut matrix = zeros(); 103 | matrix[0] = 1.0 / fov.tan() / aspect; 104 | matrix[5] = 1.0 / fov.tan(); 105 | matrix[10] = -(far + near) / (far - near); 106 | matrix[11] = -1.0; 107 | matrix[14] = -2.0 * far * near / (far - near); 108 | matrix 109 | } 110 | 111 | pub fn matmul(a: Matrix44, b: Matrix44) -> Matrix44 { 112 | let mut c = zeros(); 113 | for i in 0..4 { 114 | for j in 0..4 { 115 | for k in 0..4 { 116 | c[i * 4 + j] += a[i * 4 + k] * b[k * 4 + j]; 117 | } 118 | } 119 | } 120 | c 121 | } 122 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate gleam; 2 | 3 | mod emscripten; 4 | mod matrix; 5 | 6 | use emscripten::{ 7 | emscripten_GetProcAddress, emscripten_get_element_css_size, emscripten_set_main_loop_arg, 8 | emscripten_webgl_create_context, emscripten_webgl_init_context_attributes, 9 | emscripten_webgl_make_context_current, EmscriptenWebGLContextAttributes, 10 | }; 11 | use gleam::gl; 12 | use gleam::gl::{GLenum, GLuint}; 13 | use matrix::{matmul, perspective_matrix, rotate_x, rotate_y, viewing_matrix, Matrix44}; 14 | 15 | type GlPtr = std::rc::Rc; 16 | 17 | #[repr(C)] 18 | struct Context { 19 | gl: GlPtr, 20 | program: GLuint, 21 | buffer: GLuint, 22 | theta: f32, 23 | camera: Matrix44, 24 | p_matrix: Matrix44, 25 | width: u32, 26 | height: u32, 27 | } 28 | 29 | fn load_shader(gl: &GlPtr, shader_type: GLenum, source: &[&[u8]]) -> Option { 30 | let shader = gl.create_shader(shader_type); 31 | if shader == 0 { 32 | return None; 33 | } 34 | gl.shader_source(shader, source); 35 | gl.compile_shader(shader); 36 | let mut compiled = [0]; 37 | unsafe { 38 | gl.get_shader_iv(shader, gl::COMPILE_STATUS, &mut compiled); 39 | } 40 | if compiled[0] == 0 { 41 | let log = gl.get_shader_info_log(shader); 42 | println!("{}", log); 43 | gl.delete_shader(shader); 44 | return None; 45 | } 46 | Some(shader) 47 | } 48 | 49 | fn init_buffer(gl: &GlPtr, program: GLuint) -> Option { 50 | let vertices: Vec = vec![ 51 | -50.0, -50.0, -50.0, 0.0, 0.0, 0.0, 50.0, -50.0, -50.0, 1.0, 0.0, 0.0, 50.0, 50.0, -50.0, 52 | 1.0, 1.0, 0.0, -50.0, 50.0, -50.0, 0.0, 1.0, 0.0, -50.0, -50.0, 50.0, 0.0, 0.0, 1.0, 50.0, 53 | -50.0, 50.0, 1.0, 0.0, 1.0, 50.0, 50.0, 50.0, 1.0, 1.0, 1.0, -50.0, 50.0, 50.0, 0.0, 1.0, 54 | 1.0, 55 | ]; 56 | let elements: Vec = vec![ 57 | 3, 2, 0, 2, 0, 1, 0, 1, 4, 1, 4, 5, 1, 2, 5, 2, 5, 6, 2, 3, 6, 3, 6, 7, 3, 0, 7, 0, 7, 4, 58 | 4, 5, 7, 5, 7, 6, 59 | ]; 60 | let buffers = gl.gen_buffers(2); 61 | let vertex_buffer = buffers[0]; 62 | let element_buffer = buffers[1]; 63 | let position_location = gl.get_attrib_location(program, "aPosition") as u32; 64 | let color_location = gl.get_attrib_location(program, "aColor") as u32; 65 | let array = gl.gen_vertex_arrays(1)[0]; 66 | gl.bind_vertex_array(array); 67 | gl.enable_vertex_attrib_array(position_location); 68 | gl.enable_vertex_attrib_array(color_location); 69 | gl.bind_buffer(gl::ARRAY_BUFFER, vertex_buffer); 70 | gl.buffer_data_untyped( 71 | gl::ARRAY_BUFFER, 72 | 4 * vertices.len() as isize, 73 | vertices.as_ptr() as *const _, 74 | gl::STATIC_DRAW, 75 | ); 76 | gl.vertex_attrib_pointer(position_location, 3, gl::FLOAT, false, 24, 0); 77 | gl.vertex_attrib_pointer(color_location, 3, gl::FLOAT, false, 24, 12); 78 | gl.bind_buffer(gl::ELEMENT_ARRAY_BUFFER, element_buffer); 79 | gl.buffer_data_untyped( 80 | gl::ELEMENT_ARRAY_BUFFER, 81 | 2 * elements.len() as isize, 82 | elements.as_ptr() as *const _, 83 | gl::STATIC_DRAW, 84 | ); 85 | gl.bind_vertex_array(0); 86 | Some(array) 87 | } 88 | 89 | impl Context { 90 | fn new(gl: GlPtr) -> Context { 91 | let v_shader = load_shader(&gl, gl::VERTEX_SHADER, VS_SRC).unwrap(); 92 | let f_shader = load_shader(&gl, gl::FRAGMENT_SHADER, FS_SRC).unwrap(); 93 | let program = gl.create_program(); 94 | gl.attach_shader(program, v_shader); 95 | gl.attach_shader(program, f_shader); 96 | gl.link_program(program); 97 | gl.use_program(program); 98 | let position_location = gl.get_attrib_location(program, "aPosition") as u32; 99 | let color_location = gl.get_attrib_location(program, "aColor") as u32; 100 | gl.enable_vertex_attrib_array(position_location); 101 | gl.enable_vertex_attrib_array(color_location); 102 | let buffer = init_buffer(&gl, program).unwrap(); 103 | gl.clear_color(0.0, 0.0, 0.0, 1.0); 104 | gl.enable(gl::DEPTH_TEST); 105 | let (width, height) = get_canvas_size(); 106 | Context { 107 | gl: gl, 108 | program: program, 109 | buffer: buffer, 110 | theta: 0.0, 111 | camera: viewing_matrix([0.0, 0.0, 200.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0]), 112 | p_matrix: perspective_matrix( 113 | (45.0 as f32).to_radians(), 114 | width as f32 / height as f32, 115 | 0.001, 116 | 1000.0, 117 | ), 118 | width: width, 119 | height: height, 120 | } 121 | } 122 | 123 | fn draw(&self) { 124 | let gl = &self.gl; 125 | gl.viewport(0, 0, self.width as i32, self.height as i32); 126 | gl.clear(gl::COLOR_BUFFER_BIT); 127 | gl.use_program(self.program); 128 | let mv_location = gl.get_uniform_location(self.program, "uMVMatrix"); 129 | let mv_matrix = matmul( 130 | matmul(rotate_x(self.theta), rotate_y(self.theta)), 131 | self.camera, 132 | ); 133 | gl.uniform_matrix_4fv(mv_location, false, &mv_matrix); 134 | let p_location = gl.get_uniform_location(self.program, "uPMatrix"); 135 | gl.uniform_matrix_4fv(p_location, false, &self.p_matrix); 136 | gl.bind_vertex_array(self.buffer); 137 | gl.draw_elements(gl::TRIANGLES, 36, gl::UNSIGNED_SHORT, 0); 138 | gl.bind_vertex_array(0); 139 | } 140 | } 141 | 142 | fn get_canvas_size() -> (u32, u32) { 143 | unsafe { 144 | let mut width = std::mem::uninitialized(); 145 | let mut height = std::mem::uninitialized(); 146 | emscripten_get_element_css_size(std::ptr::null(), &mut width, &mut height); 147 | (width as u32, height as u32) 148 | } 149 | } 150 | 151 | fn step(ctx: &mut Context) { 152 | ctx.theta += 0.01; 153 | ctx.draw(); 154 | } 155 | 156 | extern "C" fn loop_wrapper(ctx: *mut std::os::raw::c_void) { 157 | unsafe { 158 | let mut ctx = &mut *(ctx as *mut Context); 159 | step(&mut ctx); 160 | } 161 | } 162 | 163 | fn main() { 164 | unsafe { 165 | let mut attributes: EmscriptenWebGLContextAttributes = std::mem::uninitialized(); 166 | emscripten_webgl_init_context_attributes(&mut attributes); 167 | attributes.majorVersion = 2; 168 | let handle = emscripten_webgl_create_context(std::ptr::null(), &attributes); 169 | emscripten_webgl_make_context_current(handle); 170 | let gl = gl::GlesFns::load_with(|addr| { 171 | let addr = std::ffi::CString::new(addr).unwrap(); 172 | emscripten_GetProcAddress(addr.into_raw() as *const _) as *const _ 173 | }); 174 | let mut ctx = Context::new(gl); 175 | let ptr = &mut ctx as *mut _ as *mut std::os::raw::c_void; 176 | emscripten_set_main_loop_arg(Some(loop_wrapper), ptr, 0, 1); 177 | } 178 | } 179 | 180 | const VS_SRC: &'static [&[u8]] = &[b"#version 300 es 181 | layout(location = 0) in vec3 aPosition; 182 | layout(location = 1) in vec3 aColor; 183 | uniform mat4 uMVMatrix; 184 | uniform mat4 uPMatrix; 185 | out vec4 vColor; 186 | void main() { 187 | gl_Position = uPMatrix * uMVMatrix * vec4(aPosition, 1.0); 188 | vColor = vec4(aColor, 1.0); 189 | }"]; 190 | 191 | const FS_SRC: &'static [&[u8]] = &[b"#version 300 es 192 | precision mediump float; 193 | in vec4 vColor; 194 | out vec4 oFragColor; 195 | void main() { 196 | oFragColor = vColor; 197 | }"]; 198 | --------------------------------------------------------------------------------