1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: MIT
3
4#[cfg(target_arch = "wasm32")]
5use wasm_bindgen::prelude::*;
6
7slint::include_modules!();
8
9use glow::HasContext;
10
11struct EGLUnderlay {
12 gl: glow::Context,
13 program: glow::Program,
14 effect_time_location: glow::UniformLocation,
15 rotation_time_location: glow::UniformLocation,
16 vbo: glow::Buffer,
17 vao: glow::VertexArray,
18 start_time: web_time::Instant,
19}
20
21impl EGLUnderlay {
22 fn new(gl: glow::Context) -> Self {
23 unsafe {
24 let program = gl.create_program().expect("Cannot create program");
25
26 let (vertex_shader_source, fragment_shader_source) = (
27 r#"#version 100
28 attribute vec2 position;
29 varying vec2 frag_position;
30 void main() {
31 frag_position = position;
32 gl_Position = vec4(position, 0.0, 1.0);
33 }"#,
34 r#"#version 100
35 precision mediump float;
36 varying vec2 frag_position;
37 uniform float effect_time;
38 uniform float rotation_time;
39
40 float roundRectDistance(vec2 pos, vec2 rect_size, float radius)
41 {
42 vec2 q = abs(pos) - rect_size + radius;
43 return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
44 }
45
46 void main() {
47 vec2 size = vec2(0.4, 0.5) + 0.2 * cos(effect_time / 500. + vec2(0.3, 0.2));
48 float radius = 0.5 * sin(effect_time / 300.);
49 float a = rotation_time / 800.0;
50 float d = roundRectDistance(mat2(cos(a), -sin(a), sin(a), cos(a)) * frag_position, size, radius);
51 vec3 col = (d > 0.0) ? vec3(sin(d * 0.2), 0.4 * cos(effect_time / 1000.0 + d * 0.8), sin(d * 1.2)) : vec3(0.2 * cos(d * 0.1), 0.17 * sin(d * 0.4), 0.96 * abs(sin(effect_time / 500. - d * 0.9)));
52 col *= 0.8 + 0.5 * sin(50.0 * d);
53 col = mix(col, vec3(0.9), 1.0 - smoothstep(0.0, 0.03, abs(d) ));
54 gl_FragColor = vec4(col, 1.0);
55 }"#,
56 );
57
58 let shader_sources = [
59 (glow::VERTEX_SHADER, vertex_shader_source),
60 (glow::FRAGMENT_SHADER, fragment_shader_source),
61 ];
62
63 let mut shaders = Vec::with_capacity(shader_sources.len());
64
65 for (shader_type, shader_source) in shader_sources.iter() {
66 let shader = gl.create_shader(*shader_type).expect("Cannot create shader");
67 gl.shader_source(shader, shader_source);
68 gl.compile_shader(shader);
69 if !gl.get_shader_compile_status(shader) {
70 panic!("{}", gl.get_shader_info_log(shader));
71 }
72 gl.attach_shader(program, shader);
73 shaders.push(shader);
74 }
75
76 gl.link_program(program);
77 if !gl.get_program_link_status(program) {
78 panic!("{}", gl.get_program_info_log(program));
79 }
80
81 for shader in shaders {
82 gl.detach_shader(program, shader);
83 gl.delete_shader(shader);
84 }
85
86 let effect_time_location = gl.get_uniform_location(program, "effect_time").unwrap();
87 let rotation_time_location = gl.get_uniform_location(program, "rotation_time").unwrap();
88 let position_location = gl.get_attrib_location(program, "position").unwrap();
89
90 let vbo = gl.create_buffer().expect("Cannot create buffer");
91 gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
92
93 let vertices = [-1.0f32, 1.0f32, -1.0f32, -1.0f32, 1.0f32, 1.0f32, 1.0f32, -1.0f32];
94
95 gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, vertices.align_to().1, glow::STATIC_DRAW);
96
97 let vao = gl.create_vertex_array().expect("Cannot create vertex array");
98 gl.bind_vertex_array(Some(vao));
99 gl.enable_vertex_attrib_array(position_location);
100 gl.vertex_attrib_pointer_f32(position_location, 2, glow::FLOAT, false, 8, 0);
101
102 gl.bind_buffer(glow::ARRAY_BUFFER, None);
103 gl.bind_vertex_array(None);
104
105 Self {
106 gl,
107 program,
108 effect_time_location,
109 rotation_time_location,
110 vbo,
111 vao,
112 start_time: web_time::Instant::now(),
113 }
114 }
115 }
116}
117
118impl Drop for EGLUnderlay {
119 fn drop(&mut self) {
120 unsafe {
121 self.gl.delete_program(self.program);
122 self.gl.delete_vertex_array(self.vao);
123 self.gl.delete_buffer(self.vbo);
124 }
125 }
126}
127
128impl EGLUnderlay {
129 fn render(&mut self, rotation_enabled: bool) {
130 unsafe {
131 let gl = &self.gl;
132
133 gl.use_program(Some(self.program));
134
135 // Retrieving the buffer with glow only works with native builds right now. For WASM this requires https://github.com/grovesNL/glow/pull/190
136 // That means we can't properly restore the vao/vbo, but this is okay for now as this only works with femtovg, which doesn't rely on
137 // these bindings to persist across frames.
138 #[cfg(not(target_arch = "wasm32"))]
139 let old_buffer =
140 std::num::NonZeroU32::new(gl.get_parameter_i32(glow::ARRAY_BUFFER_BINDING) as u32)
141 .map(glow::NativeBuffer);
142 #[cfg(target_arch = "wasm32")]
143 let old_buffer = None;
144
145 gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
146
147 #[cfg(not(target_arch = "wasm32"))]
148 let old_vao =
149 std::num::NonZeroU32::new(gl.get_parameter_i32(glow::VERTEX_ARRAY_BINDING) as u32)
150 .map(glow::NativeVertexArray);
151 #[cfg(target_arch = "wasm32")]
152 let old_vao = None;
153
154 gl.bind_vertex_array(Some(self.vao));
155
156 let elapsed = self.start_time.elapsed().as_millis() as f32;
157 gl.uniform_1_f32(Some(&self.effect_time_location), elapsed);
158 gl.uniform_1_f32(
159 Some(&self.rotation_time_location),
160 if rotation_enabled { elapsed } else { 0.0 },
161 );
162
163 gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4);
164
165 gl.bind_buffer(glow::ARRAY_BUFFER, old_buffer);
166 gl.bind_vertex_array(old_vao);
167 gl.use_program(None);
168 }
169 }
170}
171
172#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
173pub fn main() {
174 // This provides better error messages in debug mode.
175 // It's disabled in release mode so it doesn't bloat up the file size.
176 #[cfg(all(debug_assertions, target_arch = "wasm32"))]
177 console_error_panic_hook::set_once();
178
179 let app = App::new().unwrap();
180
181 let mut underlay = None;
182
183 let app_weak = app.as_weak();
184
185 if let Err(error) = app.window().set_rendering_notifier(move |state, graphics_api| {
186 // eprintln!("rendering state {:#?}", state);
187
188 match state {
189 slint::RenderingState::RenderingSetup => {
190 let context = match graphics_api {
191 #[cfg(not(target_arch = "wasm32"))]
192 slint::GraphicsAPI::NativeOpenGL { get_proc_address } => unsafe {
193 glow::Context::from_loader_function_cstr(|s| get_proc_address(s))
194 },
195 #[cfg(target_arch = "wasm32")]
196 slint::GraphicsAPI::WebGL { canvas_element_id, context_type } => {
197 use wasm_bindgen::JsCast;
198
199 let canvas = web_sys::window()
200 .unwrap()
201 .document()
202 .unwrap()
203 .get_element_by_id(canvas_element_id)
204 .unwrap()
205 .dyn_into::<web_sys::HtmlCanvasElement>()
206 .unwrap();
207
208 let webgl1_context = canvas
209 .get_context(context_type)
210 .unwrap()
211 .unwrap()
212 .dyn_into::<web_sys::WebGlRenderingContext>()
213 .unwrap();
214
215 glow::Context::from_webgl1_context(webgl1_context)
216 }
217 _ => return,
218 };
219 underlay = Some(EGLUnderlay::new(context))
220 }
221 slint::RenderingState::BeforeRendering => {
222 if let (Some(underlay), Some(app)) = (underlay.as_mut(), app_weak.upgrade()) {
223 underlay.render(app.get_rotation_enabled());
224 app.window().request_redraw();
225 }
226 }
227 slint::RenderingState::AfterRendering => {}
228 slint::RenderingState::RenderingTeardown => {
229 drop(underlay.take());
230 }
231 _ => {}
232 }
233 }) {
234 match error {
235 slint::SetRenderingNotifierError::Unsupported => eprintln!("This example requires the use of the GL backend. Please run with the environment variable SLINT_BACKEND=GL set."),
236 _ => unreachable!()
237 }
238 std::process::exit(1);
239 }
240
241 app.run().unwrap();
242}
243