Add WebGL example (#908)

This commit is contained in:
Miklós Tusz 2020-02-22 01:32:42 -05:00 committed by GitHub
parent 92aab6c97c
commit 90a7cdb9b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 171 additions and 0 deletions

View File

@ -24,4 +24,5 @@ members = [
"todomvc",
"two_apps",
"pub_sub",
"webgl"
]

10
examples/webgl/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "webgl"
version = "0.1.0"
authors = ["Miklós Tusz <mdtusz@gmail.com>"]
edition = "2018"
[dependencies]
stdweb = "0.4.20"
yew = { path = "../.." }
webgl_stdweb = "0.3.0"

5
examples/webgl/README.md Normal file
View File

@ -0,0 +1,5 @@
## Yew WebGL Demo
This is a simple demo using WebGL with Yew to initialize the GL context, create a render loop, and draw to the canvas with basic shaders.
The `stdweb` and `webgl_stdweb` crates are required.

View File

@ -0,0 +1,11 @@
precision mediump float;
uniform float u_time;
void main() {
float r = sin(u_time * 0.0003);
float g = sin(u_time * 0.0005);
float b = sin(u_time * 0.0007);
gl_FragColor = vec4(r, g, b, 1.0);
}

View File

@ -0,0 +1,7 @@
precision mediump float;
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
}

134
examples/webgl/src/lib.rs Normal file
View File

@ -0,0 +1,134 @@
#![recursion_limit = "512"]
extern crate stdweb;
use stdweb::unstable::TryInto;
use stdweb::web::html_element::CanvasElement;
use stdweb::web::TypedArray;
use webgl_stdweb::{GLfloat, GLuint, WebGLRenderingContext as GL};
use yew::services::{RenderService, Task};
use yew::{html, Component, ComponentLink, Html, NodeRef, ShouldRender};
pub struct Model {
canvas: Option<CanvasElement>,
gl: Option<GL>,
link: ComponentLink<Self>,
node_ref: NodeRef,
render_loop: Option<Box<dyn Task>>,
}
pub enum Msg {
Render(f64),
}
impl Component for Model {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Model {
canvas: None,
gl: None,
link: link,
node_ref: NodeRef::default(),
render_loop: None,
}
}
fn mounted(&mut self) -> ShouldRender {
// Once mounted, store references for the canvas and GL context. These can be used for
// resizing the rendering area when the window or canvas element are resized, as well as
// for making GL calls.
let c: CanvasElement = self.node_ref.get().unwrap().try_into().unwrap();
let gl: GL = c.get_context().expect("WebGL not supported!");
self.canvas = Some(c);
self.gl = Some(gl);
// In a more complex use-case, there will be additional WebGL initialization that should be
// done here, such as enabling or disabling depth testing, depth functions, face
// culling etc.
// The callback to request animation frame is passed a time value which can be used for
// rendering motion independent of the framerate which may vary.
let render_frame = self.link.callback(|time: f64| Msg::Render(time));
let handle = RenderService::new().request_animation_frame(render_frame);
// A reference to the handle must be stored, otherwise it is dropped and the render won't
// occur.
self.render_loop = Some(Box::new(handle));
// Since WebGL is rendered to the canvas "separate" from the DOM, there is no need to
// render the DOM element(s) again.
false
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::Render(timestamp) => {
// Render functions are likely to get quite large, so it is good practice to split
// it into it's own function rather than keeping it inline in the update match
// case. This also allows for updating other UI elements that may be rendered in
// the DOM like a framerate counter, or other overlaid textual elements.
self.render_gl(timestamp);
}
}
false
}
fn view(&self) -> Html {
html! {
<canvas ref={self.node_ref.clone()} />
}
}
}
impl Model {
fn render_gl(&mut self, timestamp: f64) {
let gl = self.gl.as_ref().expect("GL Context not initialized!");
let vert_code = include_str!("./basic.vert");
let frag_code = include_str!("./basic.frag");
// This list of vertices will draw two triangles to cover the entire canvas.
let vertices: Vec<f32> = vec![
-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 vertex_buffer = gl.create_buffer().unwrap();
let verts = TypedArray::<f32>::from(vertices.as_slice());
gl.bind_buffer(GL::ARRAY_BUFFER, Some(&vertex_buffer));
gl.buffer_data_1(GL::ARRAY_BUFFER, Some(&verts.buffer()), GL::STATIC_DRAW);
let vert_shader = gl.create_shader(GL::VERTEX_SHADER).unwrap();
gl.shader_source(&vert_shader, &vert_code);
gl.compile_shader(&vert_shader);
let frag_shader = gl.create_shader(GL::FRAGMENT_SHADER).unwrap();
gl.shader_source(&frag_shader, &frag_code);
gl.compile_shader(&frag_shader);
let shader_program = gl.create_program().unwrap();
gl.attach_shader(&shader_program, &vert_shader);
gl.attach_shader(&shader_program, &frag_shader);
gl.link_program(&shader_program);
gl.use_program(Some(&shader_program));
// Attach the position vector as an attribute for the GL context.
let position = gl.get_attrib_location(&shader_program, "a_position") as GLuint;
gl.vertex_attrib_pointer(position, 2, GL::FLOAT, false, 0, 0);
gl.enable_vertex_attrib_array(position);
// Attach the time as a uniform for the GL context.
let time = gl.get_uniform_location(&shader_program, "u_time");
gl.uniform1f(time.as_ref(), timestamp as GLfloat);
gl.draw_arrays(GL::TRIANGLES, 0, 6);
let render_frame = self.link.callback(|time: f64| Msg::Render(time));
let handle = RenderService::new().request_animation_frame(render_frame);
// A reference to the new handle must be retained for the next render to run.
self.render_loop = Some(Box::new(handle));
}
}

View File

@ -0,0 +1,3 @@
fn main() {
yew::start_app::<webgl::Model>();
}