// based on the WebGL example in the `wasm-bindgen` guide // // https://rustwasm.github.io/wasm-bindgen/examples/webgl.html // // and this StackOverflow answer by wangdq // // https://stackoverflow.com/a/39684775 // extern crate js_sys; /* use std::f64::consts::PI as PI; */ use sycamore::{prelude::*, rt::{JsCast, JsValue}}; use web_sys::{console, WebGl2RenderingContext, WebGlProgram, WebGlShader}; fn compile_shader( context: &WebGl2RenderingContext, shader_type: u32, source: &str, ) -> WebGlShader { let shader = context.create_shader(shader_type).unwrap(); context.shader_source(&shader, source); context.compile_shader(&shader); shader } // load the given data into the vertex input of the given name fn bind_vertex_attrib( context: &WebGl2RenderingContext, program: &WebGlProgram, name: &str, size: i32, data: &[f32] ) { // create a data buffer and bind it to ARRAY_BUFFER let buffer = context.create_buffer().unwrap(); context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&buffer)); // load the given data into the buffer. the function `Float32Array::view` // creates a raw view into our module's `WebAssembly.Memory` buffer. // allocating more memory will change the buffer, invalidating the view. // that means we have to make sure we don't allocate any memory until the // view is dropped unsafe { context.buffer_data_with_array_buffer_view( WebGl2RenderingContext::ARRAY_BUFFER, &js_sys::Float32Array::view(&data), WebGl2RenderingContext::STATIC_DRAW, ); } // find the target attribute in the program's attribute list let attrib_index = context.get_attrib_location(&program, name); // allow the target attribute to be used context.enable_vertex_attrib_array(attrib_index as u32); // take whatever's bound to ARRAY_BUFFER---here, the data buffer created // above---and bind it to the target attribute // // https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer // context.vertex_attrib_pointer_with_i32( attrib_index as u32, size, WebGl2RenderingContext::FLOAT, false, // don't normalize 0, // zero stride 0, // zero offset ); } fn main() { // set up a config option that forwards panic messages to `console.error` #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); sycamore::render(|| { let tip = create_signal(0.0); let display = create_node_ref(); on_mount(move || { // get the display canvas let canvas = display .get::() .unchecked_into::(); let ctx = canvas .get_context("webgl2") .unwrap() .unwrap() .dyn_into::() .unwrap(); // compile and attach the vertex and fragment shaders let vertex_shader = compile_shader( &ctx, WebGl2RenderingContext::VERTEX_SHADER, r##"#version 300 es in vec3 position; in vec3 color; out vec4 vertexColor; const float focal_length = 3.0; const float near_clip = 0.1; const float far_clip = 20.0; const float depth_inv = 1. / (far_clip - near_clip); void main() { const mat4 world_to_clip = mat4( focal_length, 0.0, 0.0, 0.0, 0.0, focal_length, 0.0, 0.0, 0.0, 0.0, (near_clip + far_clip) * depth_inv, -1., 0.0, 0.0, 2. * near_clip * far_clip * depth_inv, 0.0 ); gl_Position = world_to_clip * vec4(position, 1.); vertexColor = vec4(color, 1.); } "##, ); let fragment_shader = compile_shader( &ctx, WebGl2RenderingContext::FRAGMENT_SHADER, r##"#version 300 es precision highp float; in vec4 vertexColor; out vec4 outColor; void main() { outColor = vertexColor; } "##, ); let program = ctx.create_program().unwrap(); ctx.attach_shader(&program, &vertex_shader); ctx.attach_shader(&program, &fragment_shader); ctx.link_program(&program); let link_status = ctx .get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS) .as_bool() .unwrap(); let link_msg = if link_status { "Linked successfully" } else { "Linking failed" }; console::log_1(&JsValue::from(link_msg)); ctx.use_program(Some(&program)); // create a vertex array and bind it to the graphics context let vertex_array = ctx.create_vertex_array().unwrap(); ctx.bind_vertex_array(Some(&vertex_array)); // set up a repainting routine create_effect(move || { const VERTEX_CNT: usize = 9; // set the vertex positions let tip_shift = 4.0/3.0 * tip.get() as f32; let positions: [f32; 3*VERTEX_CNT] = [ // triangle 1 1.0 - tip_shift, 1.0 - tip_shift, -5.0 - tip_shift, 1.0, -1.0, -7.0, -1.0, 1.0, -7.0, // triangle 2 1.0 - tip_shift, 1.0 - tip_shift, -5.0 - tip_shift, -1.0, 1.0, -7.0, -1.0, -1.0, -7.0, // triangle 3 1.0 - tip_shift, 1.0 - tip_shift, -5.0 - tip_shift, -1.0, -1.0, -7.0, 1.0, -1.0, -7.0 ]; bind_vertex_attrib(&ctx, &program, "position", 3, &positions); // set the vertex colors let colors: [f32; 3*VERTEX_CNT] = [ // triangle 1 1.0, 0.0, 0.5, 1.0, 0.0, 0.5, 1.0, 0.0, 0.5, // triangle 2 0.0, 0.5, 1.0, 0.0, 0.5, 1.0, 0.0, 0.5, 1.0, // triangle 3 0.5, 0.0, 1.0, 0.5, 0.0, 1.0, 0.5, 0.0, 1.0 ]; bind_vertex_attrib(&ctx, &program, "color", 3, &colors); // clear the screen and draw the scene ctx.clear_color(0.0, 0.0, 0.0, 1.0); ctx.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT); ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32); }); }); view! { div(id="app") { canvas(ref=display, width="600", height="600") input( type="range", max=1.0, step=0.01, bind:valueAsNumber=tip ) } } }); }