tauri-apps / tauri

Build smaller, faster, and more secure desktop and mobile applications with a web frontend.
https://tauri.app
Apache License 2.0
84.63k stars 2.54k forks source link

[bug] Resizing on Windows is much slower in Tauri than in Wry #6322

Open FabianLars opened 1 year ago

FabianLars commented 1 year ago

Describe the bug

We all know that Chromium is super slow to resize its content, it's present in all Chromium browsers too, but while trying out a few workarounds to make it less noticeable, like setting the window background color via win32 apis (changing the webview2 background doesn't change anything btw because the webview itself is not resized in time), i noticed that this is pretty much no problem in Wry but it got even worse in Tauri than the last time i really paid attention to it. Here's a recording. Its resolution and fps makes it look a bit worse than it actually is.

https://user-images.githubusercontent.com/30730186/210389507-8367d374-c65f-4238-9e76-7aea3d65ffb4.mp4

Note that i already disabled Tauri's window event handlers in its source code for that recording - no noticeable difference. The 2 apps in the video are release builds pointing to my local dev server. debug builds, and release builds with bundled artifacts have the same behavior.

I could not reproduce this issue on Linux.

Reproduction

Build literally any app on Windows. It's present even in the most lightweight vanillajs apps.

Expected behavior

i don't expect Tauri to be as fast as Wry but it shouldn't feel like my computer is crashing.

Platform and versions

Environment › OS: Windows 10.0.19045 X64 › Webview2: 108.0.1462.54 › MSVC: › Node.js: 18.12.1 › npm: 8.19.2 › pnpm: 7.21.0 › yarn: 1.22.19 › rustup: 1.25.1 › rustc: 1.66.0 › cargo: 1.66.0 › Rust toolchain: stable-x86_64-pc-windows-msvc

Packages › @tauri-apps/cli [NPM]: 1.2.2 › @tauri-apps/api [NPM]: 1.2.0 › tauri [RUST]: 1.2.3, (overwritten with local path for testing but released version has the same problem) › tauri-build [RUST]: 1.2.1, › tao [RUST]: 0.15.8, › wry [RUST]: 0.23.4,

App › build-type: bundle › CSP: unset › distDir: ../dist › devPath: http://localhost:5173/ › framework: React › bundler: Vite

App directory structure ├─ dist ├─ node_modules ├─ src └─ src-tauri

Stack trace

No response

Additional context

I also added some window event logging in Wry's source code for testing and that looks jittery too when used in Tauri.

metkm commented 1 year ago

I tried both wry and tauri but the problem is I get the same resizing performance. But your Tauri app looks worse than mine, like crashing and freezing. I don't have that.

Wry on the left, tauri on the right - Release builds

https://user-images.githubusercontent.com/54271295/219012481-fe3da920-30e7-49dd-b375-f6404e7cac0a.mp4

FabianLars commented 1 year ago

Yeah, it used to be the same for me a while back. but some months ago it changed to whatever the hell is going on in the video i posted (maybe it's something specific to my app idk). Note that it's not that bad, but OBS didn't like converting 144hz to a 30fps video i guess.

i guess on top of that we could use this issue as an continuation/extension of https://github.com/tauri-apps/tauri/issues/4012 🤷

metkm commented 1 year ago

also https://github.com/MicrosoftEdge/WebView2Feedback/issues/2715

metkm commented 1 year ago

I also tried with this sample code sample.rs. the issue is still there, and the resizing performance is bad. Probably an issue with Webview2 itself

my webview 2 version is 110.0.1587.46

FabianLars commented 1 year ago

Probably an issue with Webview2 itself

Don't get me wrong, resizing webview2, or chromium in general is incredibly slow, at least if hardware acceleration is enabled. There are just 2 issues on top of. 1) the one in my video, where it's much slower in tauri than in wry (though as we know it's not as must of a difference for everyone) 2) the webview2 background color setting we thought would help us here does not work for us. We unfortunately have to change the color of the window itself which is also a bit tricky. - i think i documented my thoughts and experiments somewhere else, at least on discord 🤔

metkm commented 1 year ago

@FabianLars yeah, To my knowledge microsoft edge also uses webview2 but It feels like It performs way better than wry, tauri, and the code I posted which uses only the windows API nothing else.

FabianLars commented 1 year ago

To my knowledge microsoft edge also uses webview2

More like the other way around, webview2 is a modified edge browser, but same thing.

but It feels like It performs way better than wry, tauri, and the code I posted which uses only the windows API nothing else.

yep, something is wrong somewhere. But it's a really weird thing. like, we basically use the same apis like the ms employee in your issue said, etc. And then another weird thing is that Wails (a similar project to tauri but written in Go) was just as laggy as tauri last time i tested it (tho less obvious because they change the window color) - well, maybe not that weird if they make the same mistake we do but whatever.

metkm commented 1 year ago

I tried adding delay to updating size of webview. I think It works a little better. What do you think? the delay I show in the video is 10ms, I just tried with 2ms and both works pretty good.

Without Delay

https://user-images.githubusercontent.com/54271295/219348123-c4b8b110-ece5-4018-90d4-8b08e57bb7cc.mp4

With Delay

https://user-images.githubusercontent.com/54271295/219348135-882ba0a9-90b3-48a8-a8b0-580b3f80162c.mp4

FabianLars commented 1 year ago

Yeah i guess that's a bit better indeed. Though i'm not aware of the possible consequences the change could have, maybe @amrbashir does, and/or maybe we need some more testing to be sure that wouldn't break anything (or maybe we find the actual cause&solution along the way :D )

metkm commented 1 year ago

@FabianLars Yeah, It feels like this will break something. I just wanted to know if it feels better or if I'm going crazy

Maybe Windows triggers resize event with the same values twice or something? I don't know. or maybe because we try to resize webview way too quickly.

metkm commented 1 year ago

This is a project of mine. The difference in performance is easier to see here. Both projects are in release mode, I reduced the delay to 2ms.

WindowsAndMessaging::WM_SIZE => {
    std::thread::sleep(Duration::from_millis(2));
    let size = get_window_size(hwnd);
    unsafe {
        webview
            .controller
            .0
            .SetBounds(RECT {
                left: 0,
                top: 0,
                right: size.cx,
                bottom: size.cy,
            })
            .unwrap();
    }
    *frame.size.lock().expect("lock size") = size;
    LRESULT::default()
}

With Delay

https://user-images.githubusercontent.com/54271295/219353579-e033b325-27d4-4609-94a5-eacb999fc37d.mp4

Without Delay

https://user-images.githubusercontent.com/54271295/219353820-f08b21b4-062c-4512-a04e-b6731748ef02.mp4

amrbashir commented 1 year ago

Good findings and a strange one I must say. I just tested and can confirm it works and it even works with just 1ms delay too, However, I don't think we should have this by default at all because the thread could sleep longer than expected and have unknown side-effects in some apps, see std::tread::sleep:

The thread may sleep longer than the duration specified due to scheduling specifics or platform-dependent functionality. It will never sleep less.

That said, I am fine with adding it behind an option or a feature-flag but not the default

cogscides commented 1 year ago

I'm facing with the same problem. @metkm Can you please help me with applying the same edit? I'm newbie to the Tauri and Rust and wondering where I should put this code to make it work 😓

metkm commented 1 year ago

@cogscides what I did was just for now is

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let Some(window) = app.get_window("main") else {
                return Ok(())
            };

            window.on_window_event(|event| {
                match event {
                    WindowEvent::Resized(..) => {
                        std::thread::sleep(std::time::Duration::from_millis(1))
                    }
                    _ => {}
                }
            });

            Ok(())
        })
        .invoke_handler(tauri::generate_handler![])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

But be careful you are blocking the thread

amrbashir commented 1 year ago

Moving it back here since the original issue is about tauri vs wry performance and still not fixed.

@metkm feel free to open an issue or a PR to wry for the hack you found.

FabianLars commented 1 year ago

Am i crazy or does 1 micro second have the same effect as 1 milisecond? Like, from_micros(1)) "fixes" it in the same way from_millis does.

Edit: Same for from_nanos(1) actually

metkm commented 1 year ago

@FabianLars yeah I feel like they have the same effect. I don't know what the default sleep duration should be, or maybe we can leave that to the developer.

Maybe I should reactive this issue again in the webview2 repo

FabianLars commented 1 year ago

well, i mean at least in rust there's no shorter duration than a nano second right? so imo we should just go with that because it has the least influence on the other components compared to not sleeping if that makes sense?

wusyong commented 1 year ago

I saw tauri-apps/wry#892 and I feel like this better to put in tauri-runtime-wry instead? Also it's better to add some comment about why we need it.

amrbashir commented 1 year ago

@wusyong seems like we don't need to add it in wry or tauri after-all, users could add this in their app logic

 tauri::Builder::default()
        .on_window_event(|e| {
            if let WindowEvent::Resized(_) = e.event() {
                std::thread::sleep(std::time::Duration::from_nanos(1));
            }
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
FabianLars commented 1 year ago

Honestly not the biggest fan of not including it in tauri in some way since virtually all windows users complain about the laggy resizing.

amrbashir commented 1 year ago

std::thread::sleep could sleep longer than requested so I'd rather let users to choose to opt-in rather than opt-out

Ciantic commented 1 year ago

I'm using Windows 11, I don't experience the same lag in resizing at the moment. I know that others are encountering it in Windows 11... but here is what it looks like without any hacks:

https://user-images.githubusercontent.com/64731/224395458-9a883066-b166-4489-bdbd-463d066d25f7.mp4

It has low frame counts but that's only because of the video. It's smooth for me.

The current app code is here: https://github.com/Ciantic/winvd-monitoring (works only on Windows 11 because of my desktop library)

simonhyll commented 1 year ago

Just wanna chime in here with that it's very weird that at least on Windows 11 if you put the window to the side so that it covers half the screen or just maximize / unmaximize it resizes perfectly fine, even more fluid than with the 1 nano second sleep workaround, which suggests that there is in fact a solution to this, we just don't know what it is.

Also, on my machine I have 2 graphics cards, one crappy and one good, and when I move the window to the monitor with the crappy card it's very clearly a difference in how effective the workaround is, suggesting part of this is just related to hardware, and that there is in fact a better solution since e.g. maximize / unmaximize performs much better even on the crappy card.

Reach out to Microsoft possibly?

mempler commented 1 year ago

I remember having this issue with a normal Win32 window, this happens if the window receives too many event calls, I fixed this by render while the resize event was called and not after the event loop (which you would usually do).

allthough i don't know if this applies here aswell. just giving a piece of what i've experienced.

So, to fix this you would either have to render everything during the resize event or have some sort of "deadline" which if has been hit, will enqueue all future events to a new frame (and should cancel/ignore all non important events) and continue with the rendering

(a theory) the sleep causes a context switch and cancels all the Win32 resize events (except the very last one sent) This is also why https://github.com/tauri-apps/tauri/issues/6322#issuecomment-1437047841 works

7flash commented 1 year ago

Similar issue I noticed on linux build, - resizing the window reduces React app performance significantly

Tnze commented 10 months ago

So, to fix this you would either have to render everything during the resize event or have some sort of "deadline" which if has been hit, will enqueue all future events to a new frame (and should cancel/ignore all non important events) and continue with the rendering

A test of limiting the rate of resizing webview:

[package]
name = "wry-test"
version = "0.1.0"
edition = "2021"

[dependencies]
winit = { version = "0.29.7", features = ["rwh_05"] }
wry = "0.35.1"
use std::time::{Duration, Instant};
use winit::event::{Event, WindowEvent};
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
use wry::{Rect, WebViewBuilder};

fn main() -> wry::Result<()> {
    let event_loop = EventLoop::new().unwrap();
    let window = WindowBuilder::new()
        .with_title("Hello World")
        .build(&event_loop)
        .unwrap();
    let webview = WebViewBuilder::new_as_child(&window)
        .with_url("https://tauri.app")?
        .build()?;

    let mut resize_request = None;
    let mut last_resize_time = Instant::now();
    event_loop
        .run(move |event, elwt| {
            match event {
                Event::WindowEvent {
                    event: WindowEvent::CloseRequested,
                    ..
                } => {
                    println!("The close button was pressed; stopping");
                    elwt.exit();
                }
                Event::AboutToWait => {
                    if let Some(bound) = resize_request.take() {
                        webview.set_bounds(bound);
                    }
                    window.request_redraw();
                }
                Event::WindowEvent {
                    event: WindowEvent::Resized(size),
                    ..
                } => {
                    let bound = Rect {
                        x: 0,
                        y: 0,
                        width: size.width,
                        height: size.height,
                    };
                    // My monitor refresh rate: 144
                    if last_resize_time.elapsed() > Duration::from_millis(1000 / 144) {
                        webview.set_bounds(bound);
                        resize_request = None;
                        last_resize_time = Instant::now();
                    } else {
                        resize_request = Some(bound);
                    }
                }
                Event::WindowEvent {
                    event: WindowEvent::RedrawRequested,
                    ..
                } => {}
                _ => (),
            }
        })
        .expect("event loop run failed");
    Ok(())
}

And it is fast as the Edge browser.

Videos

Without rate limitation:

https://github.com/tauri-apps/tauri/assets/19628575/1d68c2fa-1d6f-4b3a-92f0-50d609ff49d8

With rate limitation:

https://github.com/tauri-apps/tauri/assets/19628575/e27d08aa-6670-4193-8543-fa20f5024b9a