1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: MIT |
3 | |
4 | #[cfg (target_arch = "wasm32" )] |
5 | use wasm_bindgen::prelude::*; |
6 | |
7 | slint::include_modules!(); |
8 | |
9 | use glow::HasContext; |
10 | |
11 | struct 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 | |
21 | impl 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 | |
118 | impl 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 | |
128 | impl 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))] |
173 | pub 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 | |