Skip to content

V-Sync broken with renderer-femtovg-wgpu #8693

Closed
@jturcotte

Description

@jturcotte

Bug Description

I'm trying to use the unstable-wgpu-24 feature as an underlay using some changes to Slint but the next issue I run into is that the timing between frames isn't synchronized with compositor frames.

I'm getting those frame times in release but I would expect ~16 ms on this 60Hz output:
With OpenGL it works as expected.

Rendering frame diff: 3 ms
Rendering frame diff: 3 ms
Rendering frame diff: 3 ms
...

This seems to be a different performance issue, but in debug it's really slow:

Rendering frame diff: 36 ms
Rendering frame diff: 39 ms
Rendering frame diff: 37 ms
...

Reproducible Code (if applicable)

Here is a patch on top of the opengl_underlay example:

From 501f7629ba27d3f39120ab8faaa488ba24c35c58 Mon Sep 17 00:00:00 2001
From: Jocelyn Turcotte <turcotte.j@gmail.com>
Date: Sat, 14 Jun 2025 10:56:33 +0200
Subject: [PATCH] vsync issue

---
 examples/opengl_underlay/Cargo.toml |  11 +-
 examples/opengl_underlay/main.rs    | 231 ++--------------------------
 2 files changed, 23 insertions(+), 219 deletions(-)

diff --git a/examples/opengl_underlay/Cargo.toml b/examples/opengl_underlay/Cargo.toml
index 4cd4186e7..cb0a197b5 100644
--- a/examples/opengl_underlay/Cargo.toml
+++ b/examples/opengl_underlay/Cargo.toml
@@ -15,9 +15,14 @@ path = "main.rs"
 name = "opengl_underlay"
 
 [dependencies]
-slint = { path = "../../api/rs/slint" }
-glow = { workspace = true }
-web-time = { version = "1.0" }
+# slint = {  }
+# glow = { workspace = true }
+# web-time = { version = "1.0" }
+
+[dependencies.slint]
+path = "../../api/rs/slint"
+default-features = false
+features = ["compat-1-2", "backend-winit", "renderer-femtovg-wgpu", "unstable-wgpu-24"]
 
 [build-dependencies]
 slint-build = { path = "../../api/rs/build" }
diff --git a/examples/opengl_underlay/main.rs b/examples/opengl_underlay/main.rs
index 73aed4976..ac6d016a0 100644
--- a/examples/opengl_underlay/main.rs
+++ b/examples/opengl_underlay/main.rs
@@ -6,169 +6,6 @@
 
 slint::include_modules!();
 
-use glow::HasContext;
-
-struct EGLUnderlay {
-    gl: glow::Context,
-    program: glow::Program,
-    effect_time_location: glow::UniformLocation,
-    rotation_time_location: glow::UniformLocation,
-    vbo: glow::Buffer,
-    vao: glow::VertexArray,
-    start_time: web_time::Instant,
-}
-
-impl EGLUnderlay {
-    fn new(gl: glow::Context) -> Self {
-        unsafe {
-            let program = gl.create_program().expect("Cannot create program");
-
-            let (vertex_shader_source, fragment_shader_source) = (
-                r#"#version 100
-            attribute vec2 position;
-            varying vec2 frag_position;
-            void main() {
-                frag_position = position;
-                gl_Position = vec4(position, 0.0, 1.0);
-            }"#,
-                r#"#version 100
-            precision mediump float;
-            varying vec2 frag_position;
-            uniform float effect_time;
-            uniform float rotation_time;
-
-            float roundRectDistance(vec2 pos, vec2 rect_size, float radius)
-            {
-                vec2 q = abs(pos) - rect_size + radius;
-                return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
-            }
-
-            void main() {
-                vec2 size = vec2(0.4, 0.5) + 0.2 * cos(effect_time / 500. + vec2(0.3, 0.2));
-                float radius = 0.5 * sin(effect_time / 300.);
-                float a = rotation_time / 800.0;
-                float d = roundRectDistance(mat2(cos(a), -sin(a), sin(a), cos(a)) * frag_position, size, radius);
-                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)));
-                col *= 0.8 + 0.5 * sin(50.0 * d);
-                col = mix(col, vec3(0.9), 1.0 - smoothstep(0.0, 0.03, abs(d) ));
-                gl_FragColor = vec4(col, 1.0);
-            }"#,
-            );
-
-            let shader_sources = [
-                (glow::VERTEX_SHADER, vertex_shader_source),
-                (glow::FRAGMENT_SHADER, fragment_shader_source),
-            ];
-
-            let mut shaders = Vec::with_capacity(shader_sources.len());
-
-            for (shader_type, shader_source) in shader_sources.iter() {
-                let shader = gl.create_shader(*shader_type).expect("Cannot create shader");
-                gl.shader_source(shader, shader_source);
-                gl.compile_shader(shader);
-                if !gl.get_shader_compile_status(shader) {
-                    panic!("{}", gl.get_shader_info_log(shader));
-                }
-                gl.attach_shader(program, shader);
-                shaders.push(shader);
-            }
-
-            gl.link_program(program);
-            if !gl.get_program_link_status(program) {
-                panic!("{}", gl.get_program_info_log(program));
-            }
-
-            for shader in shaders {
-                gl.detach_shader(program, shader);
-                gl.delete_shader(shader);
-            }
-
-            let effect_time_location = gl.get_uniform_location(program, "effect_time").unwrap();
-            let rotation_time_location = gl.get_uniform_location(program, "rotation_time").unwrap();
-            let position_location = gl.get_attrib_location(program, "position").unwrap();
-
-            let vbo = gl.create_buffer().expect("Cannot create buffer");
-            gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
-
-            let vertices = [-1.0f32, 1.0f32, -1.0f32, -1.0f32, 1.0f32, 1.0f32, 1.0f32, -1.0f32];
-
-            gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, vertices.align_to().1, glow::STATIC_DRAW);
-
-            let vao = gl.create_vertex_array().expect("Cannot create vertex array");
-            gl.bind_vertex_array(Some(vao));
-            gl.enable_vertex_attrib_array(position_location);
-            gl.vertex_attrib_pointer_f32(position_location, 2, glow::FLOAT, false, 8, 0);
-
-            gl.bind_buffer(glow::ARRAY_BUFFER, None);
-            gl.bind_vertex_array(None);
-
-            Self {
-                gl,
-                program,
-                effect_time_location,
-                rotation_time_location,
-                vbo,
-                vao,
-                start_time: web_time::Instant::now(),
-            }
-        }
-    }
-}
-
-impl Drop for EGLUnderlay {
-    fn drop(&mut self) {
-        unsafe {
-            self.gl.delete_program(self.program);
-            self.gl.delete_vertex_array(self.vao);
-            self.gl.delete_buffer(self.vbo);
-        }
-    }
-}
-
-impl EGLUnderlay {
-    fn render(&mut self, rotation_enabled: bool) {
-        unsafe {
-            let gl = &self.gl;
-
-            gl.use_program(Some(self.program));
-
-            // Retrieving the buffer with glow only works with native builds right now. For WASM this requires https://github.com/grovesNL/glow/pull/190
-            // 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
-            // these bindings to persist across frames.
-            #[cfg(not(target_arch = "wasm32"))]
-            let old_buffer =
-                std::num::NonZeroU32::new(gl.get_parameter_i32(glow::ARRAY_BUFFER_BINDING) as u32)
-                    .map(glow::NativeBuffer);
-            #[cfg(target_arch = "wasm32")]
-            let old_buffer = None;
-
-            gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
-
-            #[cfg(not(target_arch = "wasm32"))]
-            let old_vao =
-                std::num::NonZeroU32::new(gl.get_parameter_i32(glow::VERTEX_ARRAY_BINDING) as u32)
-                    .map(glow::NativeVertexArray);
-            #[cfg(target_arch = "wasm32")]
-            let old_vao = None;
-
-            gl.bind_vertex_array(Some(self.vao));
-
-            let elapsed = self.start_time.elapsed().as_millis() as f32;
-            gl.uniform_1_f32(Some(&self.effect_time_location), elapsed);
-            gl.uniform_1_f32(
-                Some(&self.rotation_time_location),
-                if rotation_enabled { elapsed } else { 0.0 },
-            );
-
-            gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4);
-
-            gl.bind_buffer(glow::ARRAY_BUFFER, old_buffer);
-            gl.bind_vertex_array(old_vao);
-            gl.use_program(None);
-        }
-    }
-}
-
 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
 pub fn main() {
     // This provides better error messages in debug mode.
@@ -176,66 +13,28 @@ pub fn main() {
     #[cfg(all(debug_assertions, target_arch = "wasm32"))]
     console_error_panic_hook::set_once();
 
-    slint::BackendSelector::new()
-        .require_opengl_es()
-        .select()
-        .expect("Unable to create Slint backend with OpenGL ES renderer");
+    // slint::BackendSelector::new()
+    //     .require_opengl_es()
+    //     .select()
+    //     .expect("Unable to create Slint backend with OpenGL ES renderer");
 
     let app = App::new().unwrap();
 
-    let mut underlay = None;
-
     let app_weak = app.as_weak();
 
-    app.window()
-        .set_rendering_notifier(move |state, graphics_api| {
-            // eprintln!("rendering state {:#?}", state);
-
-            match state {
-                slint::RenderingState::RenderingSetup => {
-                    let context = match graphics_api {
-                        #[cfg(not(target_arch = "wasm32"))]
-                        slint::GraphicsAPI::NativeOpenGL { get_proc_address } => unsafe {
-                            glow::Context::from_loader_function_cstr(|s| get_proc_address(s))
-                        },
-                        #[cfg(target_arch = "wasm32")]
-                        slint::GraphicsAPI::WebGL { canvas_element_id, context_type } => {
-                            use wasm_bindgen::JsCast;
-
-                            let canvas = web_sys::window()
-                                .unwrap()
-                                .document()
-                                .unwrap()
-                                .get_element_by_id(canvas_element_id)
-                                .unwrap()
-                                .dyn_into::<web_sys::HtmlCanvasElement>()
-                                .unwrap();
+    let mut last_frame_time = std::time::Instant::now();
 
-                            let webgl1_context = canvas
-                                .get_context(context_type)
-                                .unwrap()
-                                .unwrap()
-                                .dyn_into::<web_sys::WebGl2RenderingContext>()
-                                .unwrap();
-
-                            glow::Context::from_webgl2_context(webgl1_context)
-                        }
-                        _ => return,
-                    };
-                    underlay = Some(EGLUnderlay::new(context))
-                }
-                slint::RenderingState::BeforeRendering => {
-                    if let (Some(underlay), Some(app)) = (underlay.as_mut(), app_weak.upgrade()) {
-                        underlay.render(app.get_rotation_enabled());
-                        app.window().request_redraw();
-                    }
-                }
-                slint::RenderingState::AfterRendering => {}
-                slint::RenderingState::RenderingTeardown => {
-                    drop(underlay.take());
-                }
-                _ => {}
+    app.window()
+        .set_rendering_notifier(move |state, graphics_api| match state {
+            slint::RenderingState::BeforeRendering => {
+                let now = std::time::Instant::now();
+                let diff = now.duration_since(last_frame_time).as_millis();
+                println!("Rendering frame diff: {} ms", diff);
+                last_frame_time = now;
+
+                app_weak.upgrade().unwrap().window().request_redraw();
             }
+            _ => {}
         })
         .expect("Unable to set rendering notifier");
 
-- 
2.48.1

Environment Details

  • Slint Version: 1.12 (master)
  • Platform/OS: Linux with Wayland
  • Programming Language: Rust
  • Backend/Renderer: winit and femtovg

Product Impact

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    need triagingIssue that the owner of the area still need to triage

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions