/
lib.rs
135 lines (109 loc) · 4.88 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#![cfg(feature = "webgl_stdweb")]
#![recursion_limit = "512"]
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,
node_ref: NodeRef::default(),
render_loop: None,
}
}
fn rendered(&mut self, first_render: bool) {
// Once rendered, 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.cast().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.
if first_render {
// 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(Msg::Render);
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));
}
}
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 change(&mut self, _: Self::Properties) -> ShouldRender {
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(Msg::Render);
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));
}
}