1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: MIT
3
4use std::num::NonZeroU32;
5use std::rc::Rc;
6
7slint::include_modules!();
8
9use glow::HasContext;
10
11macro_rules! define_scoped_binding {
12 (struct $binding_ty_name:ident => $obj_name:path, $param_name:path, $binding_fn:ident, $target_name:path) => {
13 struct $binding_ty_name {
14 saved_value: Option<$obj_name>,
15 gl: Rc<glow::Context>,
16 }
17
18 impl $binding_ty_name {
19 unsafe fn new(gl: &Rc<glow::Context>, new_binding: Option<$obj_name>) -> Self {
20 let saved_value =
21 NonZeroU32::new(gl.get_parameter_i32($param_name) as u32).map($obj_name);
22
23 gl.$binding_fn($target_name, new_binding);
24 Self { saved_value, gl: gl.clone() }
25 }
26 }
27
28 impl Drop for $binding_ty_name {
29 fn drop(&mut self) {
30 unsafe {
31 self.gl.$binding_fn($target_name, self.saved_value);
32 }
33 }
34 }
35 };
36 (struct $binding_ty_name:ident => $obj_name:path, $param_name:path, $binding_fn:ident) => {
37 struct $binding_ty_name {
38 saved_value: Option<$obj_name>,
39 gl: Rc<glow::Context>,
40 }
41
42 impl $binding_ty_name {
43 unsafe fn new(gl: &Rc<glow::Context>, new_binding: Option<$obj_name>) -> Self {
44 let saved_value =
45 NonZeroU32::new(gl.get_parameter_i32($param_name) as u32).map($obj_name);
46
47 gl.$binding_fn(new_binding);
48 Self { saved_value, gl: gl.clone() }
49 }
50 }
51
52 impl Drop for $binding_ty_name {
53 fn drop(&mut self) {
54 unsafe {
55 self.gl.$binding_fn(self.saved_value);
56 }
57 }
58 }
59 };
60}
61
62define_scoped_binding!(struct ScopedTextureBinding => glow::NativeTexture, glow::TEXTURE_BINDING_2D, bind_texture, glow::TEXTURE_2D);
63define_scoped_binding!(struct ScopedFrameBufferBinding => glow::NativeFramebuffer, glow::DRAW_FRAMEBUFFER_BINDING, bind_framebuffer, glow::DRAW_FRAMEBUFFER);
64define_scoped_binding!(struct ScopedVBOBinding => glow::NativeBuffer, glow::ARRAY_BUFFER_BINDING, bind_buffer, glow::ARRAY_BUFFER);
65define_scoped_binding!(struct ScopedVAOBinding => glow::NativeVertexArray, glow::VERTEX_ARRAY_BINDING, bind_vertex_array);
66
67struct DemoTexture {
68 texture: glow::Texture,
69 width: u32,
70 height: u32,
71 fbo: glow::Framebuffer,
72 gl: Rc<glow::Context>,
73}
74
75impl DemoTexture {
76 unsafe fn new(gl: &Rc<glow::Context>, width: u32, height: u32) -> Self {
77 let fbo = gl.create_framebuffer().expect("Unable to create framebuffer");
78
79 let texture = gl.create_texture().expect("Unable to allocate texture");
80
81 let _saved_texture_binding = ScopedTextureBinding::new(gl, Some(texture));
82
83 let old_unpack_alignment = gl.get_parameter_i32(glow::UNPACK_ALIGNMENT);
84 let old_unpack_row_length = gl.get_parameter_i32(glow::UNPACK_ROW_LENGTH);
85 let old_unpack_skip_pixels = gl.get_parameter_i32(glow::UNPACK_SKIP_PIXELS);
86 let old_unpack_skip_rows = gl.get_parameter_i32(glow::UNPACK_SKIP_ROWS);
87
88 gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1);
89 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32);
90 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32);
91 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32);
92 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32);
93 gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, width as i32);
94 gl.pixel_store_i32(glow::UNPACK_SKIP_PIXELS, 0);
95 gl.pixel_store_i32(glow::UNPACK_SKIP_ROWS, 0);
96
97 gl.tex_image_2d(
98 glow::TEXTURE_2D,
99 0,
100 glow::RGBA as _,
101 width as _,
102 height as _,
103 0,
104 glow::RGBA as _,
105 glow::UNSIGNED_BYTE as _,
106 None,
107 );
108
109 let _saved_fbo_binding = ScopedFrameBufferBinding::new(gl, Some(fbo));
110
111 gl.framebuffer_texture_2d(
112 glow::FRAMEBUFFER,
113 glow::COLOR_ATTACHMENT0,
114 glow::TEXTURE_2D,
115 Some(texture),
116 0,
117 );
118
119 debug_assert_eq!(
120 gl.check_framebuffer_status(glow::FRAMEBUFFER),
121 glow::FRAMEBUFFER_COMPLETE
122 );
123
124 gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, old_unpack_alignment);
125 gl.pixel_store_i32(glow::UNPACK_ROW_LENGTH, old_unpack_row_length);
126 gl.pixel_store_i32(glow::UNPACK_SKIP_PIXELS, old_unpack_skip_pixels);
127 gl.pixel_store_i32(glow::UNPACK_SKIP_ROWS, old_unpack_skip_rows);
128
129 Self { texture, width, height, fbo, gl: gl.clone() }
130 }
131
132 unsafe fn with_texture_as_active_fbo<R>(&self, callback: impl FnOnce() -> R) -> R {
133 let _saved_fbo = ScopedFrameBufferBinding::new(&self.gl, Some(self.fbo));
134 callback()
135 }
136}
137
138impl Drop for DemoTexture {
139 fn drop(&mut self) {
140 unsafe {
141 self.gl.delete_framebuffer(self.fbo);
142 self.gl.delete_texture(self.texture);
143 }
144 }
145}
146
147struct DemoRenderer {
148 gl: Rc<glow::Context>,
149 program: glow::Program,
150 vbo: glow::Buffer,
151 vao: glow::VertexArray,
152 effect_time_location: glow::UniformLocation,
153 selected_light_color_position: glow::UniformLocation,
154 start_time: web_time::Instant,
155 displayed_texture: DemoTexture,
156 next_texture: DemoTexture,
157}
158
159impl DemoRenderer {
160 fn new(gl: glow::Context) -> Self {
161 let gl = Rc::new(gl);
162 unsafe {
163 let program = gl.create_program().expect("Cannot create program");
164
165 let (vertex_shader_source, fragment_shader_source) = (
166 r#"#version 100
167 attribute vec2 position;
168 varying vec2 frag_position;
169 void main() {
170 frag_position = position;
171 gl_Position = vec4(position, 0.0, 1.0);
172 }"#,
173 r#"#version 100
174 precision highp float;
175 varying vec2 frag_position;
176 uniform vec3 selected_light_color;
177 uniform float iTime;
178
179 float sdRoundBox(vec3 p, vec3 b, float r)
180 {
181 vec3 q = abs(p) - b;
182 return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r;
183 }
184
185 vec3 rotateY(vec3 r, float angle)
186 {
187 mat3 rotation_matrix = mat3(cos(angle), 0, sin(angle), 0, 1, 0, -sin(angle), 0, cos(angle));
188 return rotation_matrix * r;
189 }
190
191 vec3 rotateZ(vec3 r, float angle) {
192 mat3 rotation_matrix = mat3(cos(angle), -sin(angle), 0, sin(angle), cos(angle), 0, 0, 0, 1);
193 return rotation_matrix * r;
194 }
195
196 // Distance from the scene
197 float scene(vec3 r)
198 {
199 vec3 pos = rotateZ(rotateY(r + vec3(-1.0, -1.0, 4.0), iTime), iTime);
200 vec3 cube = vec3(0.5, 0.5, 0.5);
201 float edge = 0.1;
202 return sdRoundBox(pos, cube, edge);
203 }
204
205 // https://iquilezles.org/articles/normalsSDF
206 vec3 normal( in vec3 pos )
207 {
208 vec2 e = vec2(1.0,-1.0)*0.5773;
209 const float eps = 0.0005;
210 return normalize( e.xyy*scene( pos + e.xyy*eps ) +
211 e.yyx*scene( pos + e.yyx*eps ) +
212 e.yxy*scene( pos + e.yxy*eps ) +
213 e.xxx*scene( pos + e.xxx*eps ) );
214 }
215
216 #define ITERATIONS 90
217 #define EPS 0.0001
218
219 vec4 render(vec2 fragCoord, vec3 light_color)
220 {
221 vec4 color = vec4(0, 0, 0, 1);
222
223 vec3 camera = vec3(1.0, 2.0, 1.0);
224 vec3 p = vec3(fragCoord.x, fragCoord.y + 1.0, -1.0);
225 vec3 dir = normalize(p - camera);
226
227 for(int i=0; i < ITERATIONS; i++)
228 {
229 float dist = scene(p);
230 if(dist < EPS) {
231 break;
232 }
233 p = p + dir * dist;
234 }
235
236 vec3 surf_normal = normal(p);
237
238 vec3 light_position = vec3(2.0, 4.0, -0.5);
239 float light = 7.0 + 2.0 * dot(surf_normal, light_position);
240 light /= 0.2 * pow(length(light_position - p), 3.5);
241
242 return vec4(light * light_color.x, light * light_color.y, light * light_color.z, 1.0) * 2.0;
243 }
244
245 /*
246 void mainImage(out vec4 fragColor, in vec2 fragCoord)
247 {
248 vec2 r = fragCoord.xy / iResolution.xy;
249 r.x *= (iResolution.x / iResolution.y);
250 fragColor = render(r, vec3(0.2, 0.5, 0.9));
251 }
252 */
253
254 void main() {
255 vec2 r = vec2(0.5 * frag_position.x + 1.0, 0.5 - 0.5 * frag_position.y);
256 gl_FragColor = render(r, selected_light_color);
257 }"#,
258 );
259
260 let shader_sources = [
261 (glow::VERTEX_SHADER, vertex_shader_source),
262 (glow::FRAGMENT_SHADER, fragment_shader_source),
263 ];
264
265 let mut shaders = Vec::with_capacity(shader_sources.len());
266
267 for (shader_type, shader_source) in shader_sources.iter() {
268 let shader = gl.create_shader(*shader_type).expect("Cannot create shader");
269 gl.shader_source(shader, shader_source);
270 gl.compile_shader(shader);
271 if !gl.get_shader_compile_status(shader) {
272 panic!("{}", gl.get_shader_info_log(shader));
273 }
274 gl.attach_shader(program, shader);
275 shaders.push(shader);
276 }
277
278 gl.link_program(program);
279 if !gl.get_program_link_status(program) {
280 panic!("{}", gl.get_program_info_log(program));
281 }
282
283 for shader in shaders {
284 gl.detach_shader(program, shader);
285 gl.delete_shader(shader);
286 }
287
288 let effect_time_location = gl.get_uniform_location(program, "iTime").unwrap();
289 let selected_light_color_position =
290 gl.get_uniform_location(program, "selected_light_color").unwrap();
291 let position_location = gl.get_attrib_location(program, "position").unwrap();
292
293 let vbo = gl.create_buffer().expect("Cannot create buffer");
294 gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
295
296 let vertices = [-1.0f32, 1.0f32, -1.0f32, -1.0f32, 1.0f32, 1.0f32, 1.0f32, -1.0f32];
297
298 gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, vertices.align_to().1, glow::STATIC_DRAW);
299
300 let vao = gl.create_vertex_array().expect("Cannot create vertex array");
301 gl.bind_vertex_array(Some(vao));
302 gl.enable_vertex_attrib_array(position_location);
303 gl.vertex_attrib_pointer_f32(position_location, 2, glow::FLOAT, false, 8, 0);
304
305 gl.bind_buffer(glow::ARRAY_BUFFER, None);
306 gl.bind_vertex_array(None);
307
308 let displayed_texture = DemoTexture::new(&gl, 320, 200);
309 let next_texture = DemoTexture::new(&gl, 320, 200);
310
311 Self {
312 gl,
313 program,
314 effect_time_location,
315 selected_light_color_position,
316 vbo,
317 vao,
318 start_time: web_time::Instant::now(),
319 displayed_texture,
320 next_texture,
321 }
322 }
323 }
324}
325
326impl Drop for DemoRenderer {
327 fn drop(&mut self) {
328 unsafe {
329 self.gl.delete_program(self.program);
330 self.gl.delete_vertex_array(self.vao);
331 self.gl.delete_buffer(self.vbo);
332 }
333 }
334}
335
336impl DemoRenderer {
337 fn render(
338 &mut self,
339 light_red: f32,
340 light_green: f32,
341 light_blue: f32,
342 width: u32,
343 height: u32,
344 ) -> slint::Image {
345 unsafe {
346 let gl = &self.gl;
347
348 gl.use_program(Some(self.program));
349
350 let _saved_vbo = ScopedVBOBinding::new(gl, Some(self.vbo));
351 let _saved_vao = ScopedVAOBinding::new(gl, Some(self.vao));
352
353 if self.next_texture.width != width || self.next_texture.height != height {
354 let mut new_texture = DemoTexture::new(gl, width, height);
355 std::mem::swap(&mut self.next_texture, &mut new_texture);
356 }
357
358 self.next_texture.with_texture_as_active_fbo(|| {
359 let mut saved_viewport: [i32; 4] = [0, 0, 0, 0];
360 gl.get_parameter_i32_slice(glow::VIEWPORT, &mut saved_viewport);
361
362 gl.viewport(0, 0, self.next_texture.width as _, self.next_texture.height as _);
363 let elapsed = self.start_time.elapsed().as_millis() as f32 / 500.;
364 gl.uniform_1_f32(Some(&self.effect_time_location), elapsed);
365
366 gl.uniform_3_f32(
367 Some(&self.selected_light_color_position),
368 light_red,
369 light_green,
370 light_blue,
371 );
372
373 gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4);
374
375 gl.viewport(
376 saved_viewport[0],
377 saved_viewport[1],
378 saved_viewport[2],
379 saved_viewport[3],
380 );
381 });
382
383 gl.use_program(None);
384 }
385
386 let result_texture = unsafe {
387 slint::BorrowedOpenGLTextureBuilder::new_gl_2d_rgba_texture(
388 self.next_texture.texture.0,
389 (self.next_texture.width, self.next_texture.height).into(),
390 )
391 .build()
392 };
393
394 std::mem::swap(&mut self.next_texture, &mut self.displayed_texture);
395
396 result_texture
397 }
398}
399
400fn main() {
401 let app = App::new().unwrap();
402
403 let mut underlay = None;
404
405 let app_weak = app.as_weak();
406
407 if let Err(error) = app.window().set_rendering_notifier(move |state, graphics_api| {
408 // eprintln!("rendering state {:#?}", state);
409
410 match state {
411 slint::RenderingState::RenderingSetup => {
412 let context = match graphics_api {
413 slint::GraphicsAPI::NativeOpenGL { get_proc_address } => unsafe {
414 glow::Context::from_loader_function_cstr(|s| get_proc_address(s))
415 },
416 _ => return,
417 };
418 underlay = Some(DemoRenderer::new(context))
419 }
420 slint::RenderingState::BeforeRendering => {
421 if let (Some(underlay), Some(app)) = (underlay.as_mut(), app_weak.upgrade()) {
422 let texture = underlay.render(
423 app.get_selected_red(),
424 app.get_selected_green(),
425 app.get_selected_blue(),
426 app.get_requested_texture_width() as u32,
427 app.get_requested_texture_height() as u32,
428 );
429 app.set_texture(slint::Image::from(texture));
430 app.window().request_redraw();
431 }
432 }
433 slint::RenderingState::AfterRendering => {}
434 slint::RenderingState::RenderingTeardown => {
435 drop(underlay.take());
436 }
437 _ => {}
438 }
439 }) {
440 match error {
441 slint::SetRenderingNotifierError::Unsupported => eprintln!("This example requires the use of the GL backend. Please run with the environment variable SLINT_BACKEND=GL set."),
442 _ => unreachable!()
443 }
444 std::process::exit(1);
445 }
446
447 app.run().unwrap();
448}
449