feature image

2026年1月1日 | ブログ記事

Suzuri | モダンGUIのためのRustテキストレンダラ | Rust text renderer for modern GUI.

🎉🎉🎉

SuzuriというRustテキストレンダリングクレートを作りました!
バグ直しもろもろあって現在v0.2.1です

GitHub - helgev-traP/Suzuri
Contribute to helgev-traP/Suzuri development by creating an account on GitHub.
Crates.io
Docs.rs

みんなつかってね!


自作動画編集ソフトのための自作GUIライブラリのための自作テキストレンダリングライブラリを作りました!

Rustテキスト界隈でよく使われているfontdbと、世界最速らしいfontdueの上に乗っかって作っています
テキストレイアウトロジックとキャッシュ、レンダリングロジックを実装しました

雑に使えるように、全ての機能を FontSystem にまとめて FontSystem のメソッドとしてテキストレイアウトからレンダリングまでできるようにしています
FontSystemSend + Sync かつすべてのメソッドが &self なので雑にスレッド間共有して色んなスレッドでレンダリングできます

できること!

必要十分な機能だけあります

機能感、レンダリング感はこんな感じ↓
1つのTextData構造体から1発で描画してます

なんで今更テキストレンダリングやってるの?

最初はモーショングラフィックやりたいだけでした

  1. モーショングラフィックやりたい
  2. 納得いく動画編集ソフトがなかった
  3. 動画編集ソフトを作ろう
  4. 納得いくGUIライブラリがなかった
  5. GUIライブラリを作ろう
  6. 納得いくテキストレンダリングライブラリがなかった
  7. テキストレンダリングライブラリを作ろう
  8. テキストレンダリングライブラリ作った!← いまここ

既存のテキストレンダリングライブラリの問題点

動画編集ソフトのためのGUIライブラリを自作していることは上記のとおりです

その中で、レイアウトロジックとして2パスレイアウトというものを採用しています

数行とは、、、?

Column、Rowなどのコンテナの実装やテキスト配置を柔軟に実装するためのほぼ必須のロジックです
いろんなGUIライブラリで広く使われています

この、2パスレイアウト中に既存のテキストレンダリングライブラリを利用したときに問題が発生しました

テキストレイアウトに(広義の)再現性が無い

狭義の再現性はもちろんあります。同じテキストに対して同じレイアウト空間を割り当てたらもちろん同じグリフレイアウトを返してくれます
しかし、

  1. ある幅を渡して、あるテキストレイアウト LayoutA が返ってきたとします
    LayoutA の最大幅を調べたところ、wだったとします
  2. 2パスレイアウトの後半で、前半と同じテキストに対してw幅を指定した上でレイアウトさせます
    普通に考えればレイアウトに十分な幅が用意されているので、前半と同じテキストレイアウトが返ってくるはずです
    しかし既存のテキストレンダリングライブラリでは、より早い段階で改行が入ってしまう、LayoutAとは異なったテキストレイアウトが出力されてしまうのでした
    (浮動小数点数の誤差も疑いましたが0.5px程度マージンしても変わりませんでした。僕のユースケースで浮動小数点誤差がここまで大きくなるのはあり得ないのでレイアウトロジックの根本的な問題です。)

つまり、レイアウトされたグリフの右端からレイアウト境界まで十分な余裕があるにも関わらず、レイアウト境界を縮小させて再レイアウトさせると異なる結果を出力してしまうということです

既存テキストレンダリングライブラリ

そういうわけで、テキストレンダリングライブラリを作りました

実装!

以下、(チャットアプリを含む)Geminiはすべてgemini-3.0-pro、Claudeはすべてclaude-opus-4.5です
それと、VSCodeではなくAntigravityを使っています

スーパーGemini駆動進捗

1. FontStorage

雑にfontdbとfontdueを一緒にしました

特に深いことはやってません

fontdbのようなクエリで、直接fontdue::Fontを取り出せるようにしました

2. TextData / TextLayout

文章の論理的表現を雑に作りました

グリフレイアウトロジックを自然言語で雑に用意
関数の入力と出力、ループ構造だけスケルトンを適当に書いたのち、丸ごと全部Geminiに投げました
この頃からGeminiにタスクを投げるようになっていきます

すると、僕が自然言語で書いたロジック仕様からそれなりに逸脱した(?)(少なくともぼくの想定したコードではない)コードが出てきました

読めない
まったく意図がわからない
めんどくさかったので5分ぐらいだけ解読しようとしてやめました

まあでもちゃんと動いていればヨシ!!!!!ということで動作を確認します

とりあえず「パフォーマンスを完全に無視し、可能の限り正しく丁寧なコードを書くように、デバッグ用レンダラを作ってください」みたいな感じで頼みます

何度かバグ直し対話を繰り返して、最後に出力のpngを見てみるとなんかちゃんと動いてそうです ヨシ!!!

入出力のインターフェースを揃えてるので後で直せますし、めんどくさいので次に進みます

3. Renderer

I. CPUレンダラ

レンダリングを行う部分は自&明なのでキャッシュ部分を頑張ります

連続メモリ空間を先に用意して、ブロック分けしてLRU管理します

アプリのGeminiと相談しながらLRUキャッシュクレートの選定をしたんですが、LRUの内部状態をみる必要があったので自作します
Geminiに連結リストの高速な実装を聞きます

雑に実装します 実装をGeminiにレビューしてもらいます それだけだと心配なのでテストをたくさん書いてもらいます

テストが大丈夫そうだったのでLRUエントリにキャッシュエントリの管理を紐づけていい感じに完成させます

最後にGeminiにパフォーマンスを詰めてもらいます

II. GPUレンダラ

キャッシュの実装を流用します
テクスチャアトラスの左上から右下までインデックス振るだけなので適当に移植します

ここでGPU特有の事情として、ドローコールの発行はとても時間がかかるのでドローコールはなるべくまとめておかないといけません
なので、一回のドローコールでなるべく多くの文字を描けるようにテクスチャアトラス全体を利用したいです
すると、ドローコールが来るまでに、次のドローコールで描画されるべきキャッシュを上書きされないように保護しないといけません
ここにLRUを自作する必要があったんです

最後に、ドローコールで描画されるべきインスタンスをバッファにプッシュしてしていって、これ以上一回の描画に詰められないなっていうタイミングでドローコールする部分を雑に書いて完成です

最後にGeminiにパフォーマンスを詰めてもらいます

毎ループ生成するVecを、ループ外で作成して毎ループclearする実装に修正されたりしました

たしかにメモリアロケーションコストを節約できそう

III. wgpuレンダラ

ここはほとんど自明な実装しかないし、めんどくさくなったので丸投げしました
90%以上はGeminiが書いてるはずです
ここまでの作業で、以外とLLMに丸投げできるぞって気づいてしまったのも理由です

実装の可読性や関数化、DRYなどレビューして直してもらうなどを何回か繰り返して満足するコードになったので完了

途中テクスチャへのデータアップロードをずっとEncoderではなくQueue経由でやっていて、そのため描画とのテクスチャキャッシュの同期が崩れていた問題に僕が気づいて指摘して直してもらったのが唯一1番引っかかったところでした

スーパーGemini駆動ドキュメント

リリースする前に、Docs.rsに登録するドキュメントを書かないといけません
やりたくないです
やりたくないのでGeminiに全部投げます
確認するのに英語読むのも面倒です
面倒なのでClaudeに全部チェックしてもらいます
Claudeにチェックと修正(あれば)してもらったら、まだ心配なのでもう一度Geminiに最終チェックしてもらいます

良さそうって言ってたので次に進みます

readmeだけ重点的に確認しながら思い通りになるように書かせました
readmeはたぶん両手で数え切れないぐらいには改稿指示したはずです

スーパーGemini駆動命名

名前決めないといけません
自分で頑張って考えました
頑張って考えてCrates.ioを見に行くと全部先客が居ます オヨ~(;o; )

語彙力が尽きたのでGeminiに量優先でたくさん出してもらいました

「Suzuri」テキストレンダリングライブラリに相応しい、渋くてお洒落な名前にしました!

バイブコーディング総括

バイブコーディングの現実

現状のAIに、質の良いコードを書く能力はありませんでした

ポン出し、丸投げで指示して作業を任せると素晴らしいスパゲッティを茹でてくれます(注: ぼくはCodexも使っています その上での感想です)

ただし、人間が適切に舵取りしてあげると爆速でプログラミングが進むアクセラレーターになってくれます

特に、関数やメソッドの入出力を決める、スケルトンを書く、関数化の指示、DRYの指示など、メンテナンス性や可読性に関わる部分をちゃんとアシストしてあげると中身の細々としたコードは意外とちゃんと書いてくれます
つまりAIの書いたMessyなコードをAPIの内側、ロジックの内側に閉じ込めて、アプリ全体に悪影響を与えないようにしてあげれば良いのです

人間に求められる作業

設計原則に気をつけましょう KISSとか、DRYとか

AIが書いたコードの「(良くない)匂い」に気をつけましょう
なんか嫌だなーって思ったらたぶんそれは良くないコードです

パッと見て理解できなかったらそれは高確率で可読性とメンテナンス性に乏しい良くないコードです
修正を依頼するかプロンプトの指示を増やしてやり直しましょう

AIが書いたコードを読むのと、半年/1年前に自分が書いたコードを読むのはほとんど一緒です
読みにくさを感じたらすぐに修正を依頼すべきです
ただ、決めたインターフェースに合っていて、テストしても正常に動いてそうならリファクタを後回しにして先に進んでもいいかも、、、? 進捗状況・時間と相談です
こういう後回しを柔軟にできるようにするためにもアーキテクチャ・APIを先に固めておくのが大事です

Suzuri | 今後の予定

以上数点直したら、完成と見て以降基本的には大きく手を加えずに枯れさせていこうかなって思ってます

helgev icon
この記事を書いた人
helgev

へるげふです 原義クリエイターです いろんなものを作っています

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2024年8月21日
【最新版 / 入門】JUCEを使ってVSTプラグインを作ろう!!!!【WebView UI】
kashiwade icon kashiwade
2023年4月27日
Vulkanのデバイスドライバを自作してみた
kegra icon kegra
2022年9月26日
競プロしかシラン人間が web アプリ QK Judge を作った話
tqk icon tqk
2024年8月29日
クロスコンパイルRust
H1rono_K icon H1rono_K
2023年12月25日
オレオレRustプロジェクトテンプレート
H1rono_K icon H1rono_K
2024年9月10日
Tauriでtodolist作ってみた
mehm8128 icon mehm8128
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記