mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Add WebGL example (#908)
This commit is contained in:
parent
92aab6c97c
commit
90a7cdb9b5
@ -24,4 +24,5 @@ members = [
|
|||||||
"todomvc",
|
"todomvc",
|
||||||
"two_apps",
|
"two_apps",
|
||||||
"pub_sub",
|
"pub_sub",
|
||||||
|
"webgl"
|
||||||
]
|
]
|
||||||
|
|||||||
10
examples/webgl/Cargo.toml
Normal file
10
examples/webgl/Cargo.toml
Normal 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
5
examples/webgl/README.md
Normal 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.
|
||||||
11
examples/webgl/src/basic.frag
Normal file
11
examples/webgl/src/basic.frag
Normal 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);
|
||||||
|
}
|
||||||
7
examples/webgl/src/basic.vert
Normal file
7
examples/webgl/src/basic.vert
Normal 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
134
examples/webgl/src/lib.rs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
3
examples/webgl/src/main.rs
Normal file
3
examples/webgl/src/main.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
yew::start_app::<webgl::Model>();
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user