From fd3cbae1b4fd0f40500a7a868196fdbd868ec0ad Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 21 Aug 2024 13:01:33 -0700 Subject: [PATCH] Get a WebGL canvas working in Sycamore --- app-proto/inversive-display/.gitignore | 3 + app-proto/inversive-display/Cargo.toml | 37 ++++++ app-proto/inversive-display/index.html | 9 ++ app-proto/inversive-display/main.css | 23 ++++ app-proto/inversive-display/src/main.rs | 152 ++++++++++++++++++++++++ 5 files changed, 224 insertions(+) create mode 100644 app-proto/inversive-display/.gitignore create mode 100644 app-proto/inversive-display/Cargo.toml create mode 100644 app-proto/inversive-display/index.html create mode 100644 app-proto/inversive-display/main.css create mode 100644 app-proto/inversive-display/src/main.rs diff --git a/app-proto/inversive-display/.gitignore b/app-proto/inversive-display/.gitignore new file mode 100644 index 0000000..238273d --- /dev/null +++ b/app-proto/inversive-display/.gitignore @@ -0,0 +1,3 @@ +target +dist +Cargo.lock \ No newline at end of file diff --git a/app-proto/inversive-display/Cargo.toml b/app-proto/inversive-display/Cargo.toml new file mode 100644 index 0000000..42d4e17 --- /dev/null +++ b/app-proto/inversive-display/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "inversive-display" +version = "0.1.0" +authors = ["Aaron"] +edition = "2021" + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +js-sys = "0.3.70" +sycamore = "0.9.0-beta.2" + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.7", optional = true } + +[dependencies.web-sys] +version = "0.3.69" +features = [ + 'HtmlCanvasElement', + 'WebGl2RenderingContext', + 'WebGlBuffer', + 'WebGlProgram', + 'WebGlShader', + 'WebGlVertexArrayObject', + 'Window' +] + +[dev-dependencies] +wasm-bindgen-test = "0.3.34" + +[profile.release] +opt-level = "s" # optimize for small code size +debug = true # include debug symbols diff --git a/app-proto/inversive-display/index.html b/app-proto/inversive-display/index.html new file mode 100644 index 0000000..9c45180 --- /dev/null +++ b/app-proto/inversive-display/index.html @@ -0,0 +1,9 @@ + + + + + Inversive display + + + + diff --git a/app-proto/inversive-display/main.css b/app-proto/inversive-display/main.css new file mode 100644 index 0000000..f79e62c --- /dev/null +++ b/app-proto/inversive-display/main.css @@ -0,0 +1,23 @@ +body { + margin-left: 20px; + margin-top: 20px; + color: #fcfcfc; + background-color: #202020; +} + +#app { + display: flex; + flex-direction: column; + width: 600px; +} + +canvas { + float: left; + background-color: #020202; + border-radius: 10px; + margin-top: 5px; +} + +input { + margin-top: 5px; +} \ No newline at end of file diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs new file mode 100644 index 0000000..6730a39 --- /dev/null +++ b/app-proto/inversive-display/src/main.rs @@ -0,0 +1,152 @@ +// based on the WebGL example in the `wasm-bindgen` guide +// +// https://rustwasm.github.io/wasm-bindgen/examples/webgl.html +// + +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 +} + +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(); + + // load the vertex and fragment shaders + let vertex_shader = compile_shader( + &ctx, + WebGl2RenderingContext::VERTEX_SHADER, + r##"#version 300 es + + in vec4 position; + + void main() { + gl_Position = position; + } + "##, + ); + let fragment_shader = compile_shader( + &ctx, + WebGl2RenderingContext::FRAGMENT_SHADER, + r##"#version 300 es + + precision highp float; + out vec4 outColor; + + void main() { + outColor = vec4(gl_FragCoord.xy / 600., 1., 1.); + } + "##, + ); + 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)); + + // set up a repainting routine + create_effect(move || { + // list the vertices + let vertices: [f32; 9] = [ + -0.9, 0.9, 0.0, + -0.9, -0.9, 0.0, + 0.9*(tip.get() as f32), 0.0, 0.0 + ]; + + // create a vertex buffer and bind it to ARRAY_BUFFER + let position_attribute_location = ctx.get_attrib_location(&program, "position"); + let buffer = ctx.create_buffer().unwrap(); + ctx.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&buffer)); + + // load the vertex list into the vertex 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 { + ctx.buffer_data_with_array_buffer_view( + WebGl2RenderingContext::ARRAY_BUFFER, + &js_sys::Float32Array::view(&vertices), + WebGl2RenderingContext::STATIC_DRAW, + ); + } + + // create a vertex array and bind it to the graphics context + let vtx_array_obj = ctx.create_vertex_array().unwrap(); + ctx.bind_vertex_array(Some(&vtx_array_obj)); + + // use whatever is bound to ARRAY_BUFFER---here, the vertex + // buffer created above---as the vertex attribute array + // + // https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer + // + ctx.vertex_attrib_pointer_with_i32( + position_attribute_location as u32, + 3, // dimension + WebGl2RenderingContext::FLOAT, // type + false, // normalized? + 0, // stride + 0, // offset + ); + ctx.enable_vertex_attrib_array(position_attribute_location as u32); + + // clear the screen and draw the scene + let vert_count = (vertices.len() / 3) as i32; + ctx.clear_color(0.0, 0.0, 0.0, 1.0); + ctx.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT); + ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, vert_count); + }); + }); + + view! { + div(id="app") { + canvas(ref=display, width="600", height="600") + input( + type="range", + max=1.0, + step=0.01, + bind:valueAsNumber=tip + ) + } + } + }); +} \ No newline at end of file