💨

TauriでもRustで爆速描画したい

2025/03/05に公開1

はじめに

皆さんこの記事は読みましたでしょうか?
https://zenn.dev/1step621/articles/8daee458b5140b
この記事を書いたのは僕の友達で、レンダリングについて色々調べていたら知見が溜まってきたので記事にしました。

課題点

一応ここでも挙げておくと

  • Rustで画像を処理したとしてもフロントに送るのが遅い
  • wasmはnative Rustよりは遅い (ロマンが足りない)
  • +Linuxだとwasmは現状並列化ができない (SharedArrayBufferが有効にできないため)

じゃあRustで直接描画するしかない!!
となるわけですね。

他のライブラリを使えばよいのでは?

彼はRustでUIを作りたくないらしいです。
それにTauriは本当に使い心地がいいですしできることが多いのはいいことです。

実際できるのか

できます
青いやつはRustから描画しています。
Linux
linux_demo

Windows
windows_demo
ソースコードは以下
https://github.com/yadokani389/tauri-rust-render

前準備

説明を分かりやすくするためにいくつか先に説明しておくべきものがあります。

まずはwinit。これはクロスプラットフォームのウィンドウ作成とイベントループのライブラリです。これを使うことで、OSに依存しない形でウィンドウを作成し、イベントを処理することができます。
Tauriの使用しているTAOというライブラリはこのwinitのフォークです。
TauriはWRYというWebViewレンダリングライブラリも使っています。

次にraw-window-handleです。これはウィンドウハンドルを抽象化するためのライブラリです。これを使うことで、異なるプラットフォーム間でウィンドウハンドルを統一的に扱うことができます。
このライブラリのHasDisplayHandleHasWindowHandleというtraitがあればsoftbufferや、wgpuなどのライブラリを使うことで描画が可能になります。

ほとんどのRustのGUIライブラリがこれらrust-windowingのcrateに依存している気がします。

本題

AppHandleget_webview_windowというメソッドでWebviewWindowというものを得ることができます。(Appでもいいです)
このWebviewWindowHasDisplayHandleHasWindowHandleを実装しています。 (うれしい)

ですがWindows(とたぶんmacOS)の方はまだ描画できません。src-tauri/tauri.conf.jsonを編集してapp->windowsの要素に"transparent": trueを追加しましょう。
それからグローバルなcssで

* {
  background-color: transparent !important;
}

などと書いて背景を透明にしてください。

ではcargo add softbufferしてsoftbufferで描画してみましょう

tauri::Builder::default()
    .setup(|app| {
        let window = tauri::Manager::get_webview_window(app, "main").unwrap();
        let window = std::sync::Arc::new(window);
        tauri::async_runtime::spawn(async move {
            let context = softbuffer::Context::new(window.clone()).unwrap();
            let mut surface = softbuffer::Surface::new(&context, window.clone()).unwrap();
            let (width, height) = {
                let size = window.inner_size().unwrap();
                (size.width, size.height)
            };
            surface
                .resize(
                    std::num::NonZeroU32::new(width).unwrap(),
                    std::num::NonZeroU32::new(height).unwrap(),
                )
                .unwrap();

            loop {
                let mut buffer = surface.buffer_mut().unwrap();
                for index in 0..(width * height) {
                    let x = index % width;

                    let r = 0;
                    let g = 0;
                    let b = 255 * x / width;
                    let a = 255;

                    buffer[index as usize] = b | (g << 8) | (r << 16) | (a << 24);
                }
                buffer.present().unwrap();
            }
        });
        Ok(())
    })
    .run(tauri::generate_context!())
    .expect("error while running tauri application");

Windowsの方は上の画像みたいになったと思います。
ですがLinuxの方は透けずに画面いっぱいが青いグラデーションで埋まったのではないでしょうか。

問題点

動作が違う

Linuxではフロントを上書きして描画されていて僕の環境だとウィンドウに触れたりするとチカチカしていてたまにフロントの画面が見えます。
ですが、Windows環境だとそういったこともなく、しかも背景部分のみに描画されています、つまりフロントの下に描画されているのです。
羨しいです。

Waylandなのも関係あるかもしれない

普段ウィンドウマネージャにはHyprlandというWayland compositorを使っているのですが、gnome(xorg)だとチカチカはするものの、背景が透明になるだけでフロントの画面は見えなかったです。
とはいえ、フロントの上に描画されているのであまり影響はありません。

Linuxで綺麗に表示したい

普通にウィンドウを作ってしまうとWebViewが邪魔してきて綺麗に表示できないです。
なのでAppHandlecreate_tao_windowというメソッドでTAOだけのウィンドウを作ります。
cargo add taoでTAOを追加してください。

let window = handle
    .create_tao_window(|| {
        ("display".to_string(), {
            tao::window::WindowBuilder::new()
                .with_title("New Window")
                .with_inner_size(tao::dpi::LogicalSize::new(400, 320))
        })
    })
    .unwrap()
    .upgrade()
    .unwrap();

windowをこれに置き変えれば良いです。
型がWebviewWindowWindowで少し違うのでinner_sizeの戻り値がResultじゃなくなってますがそこだけ修正すればいいと思います。
こうすることでマルチウィンドウにはなってしまいますがLinuxでもRustから描画ができるようになりました。

おわり

本当にここらへんドキュメントもないしやってる人が少なすぎて全体的によくわかりません。
Rust力が足りなくて変なコードとかも書いてるかもしれないです。
ご意見あればどしどしお寄せください。

Discussion

カズマカズマ

RustのUI周り、とくに描画系ってなかなか安定しないですよね。
僕もDioxusでUI作ったときはtailwind使えてめっちゃ楽!って思ってたらリフレッシュ時のチラつきに悩まされました。