feature image

2023年12月18日 | ブログ記事

GPU側でレイヤーブレンドする絵描きソフトの同期方法

アドベントカレンダー2023 18日目の記事です. 外国人なので日本語で読みにくいところが多いに申し訳ないです.

はじめに

最近, 私のPCは(OpenBSD以外)全部Waylandに移行したが, 絵描きソフトが全部waylandで使えなく/バッグが多くようになりました. さらに, 以前からオープンソース絵描きソフトの重さに不満だった. 例えば: プラグインは埋め込み言語で開発(GIMPのscript-fu)に限られる. ライブラリとして再利用することは基本的に無理. つまり, どこでもすぐ起動して使える絵描きソフトを作りたい.

現在の絵描きソフトはほぼCPUで処理する, そのため細かいパフォーマンス最適化が必要なところが多い. しかしCPU性能の限界があるのため, ブラシなどの操作はdamage areaから追跡できるが, 一部の操作はスムーズに実行することは不可能です. 例えば, レイヤーの回転,移動操作はCPUで実行すれば リアルタイムでプレビューする時はかなり重くになる. というわけで, vulkanで高速化するソフトを自作することを決めた.

定義

. undo . undo @ redo . redo . redo .
<ユーザーredo>
. undo . undo . undo @ redo . redo .

仕組み

deps

vwdraw(この絵描きソフト)はC言語でlinux+waylandを使用環境として開発している. ライブラリ依頼(現在)はvulkan, wayland-client, cglm, stb_imageだけ. 主なライブラリ(赤色)を簡単に説明します:

まだ開発中ですが, コードは全部githubで同期している (この記事の下の自己紹介のホームページに). 機能が少ないが一応それを使ってこの記事の説明グラフを描いてみた.

同期モデル

今の絵描きソフトはほとんどCPU側でレイヤーブレンドを実行する. 例えば, kritaのGPUで高速化した部分はviewportだけ. Vwdrawにはundo/ブラシrasterizer以外全ての処理がGPU側で実行されている. レイヤーデータも, RAMではなくVRAMで保存する. 今回紹介するのは, CPU-GPU同期必要の3つの部分:

CPU-GPUの同期は必要とはいえ, それほど複雑な問題ではない. なぜなら, 3Dゲームと違う絵描きソフトは compute-boundではなく, input latency bound問題です. input latencyを最小限にしたければ, vulkanと言えばframe-in-flightなどの同期技術はいらないです.

まずはメインループ(フレームの処理):

preview

1つフレームの時間は計算フェーズと同期フェーズを構成される.

ユーザーの入力から次のpresentは前のフレームsubmitしたimageから, 入力の遅延は1~2=平均1.5フレームです. さらに遅延を短縮したい場合,内部FPSはモニター出力FPSの2倍に設定すれば平均遅延は1フレームぐらいになる (最も速い場合にも0~1=平均0.5フレームの遅延がある).

GPUデータが必要時の処理

Vwdrawのレイヤーデータは全てGPUで保存する一方, undo patchesは全てCPUで保存する. その理由は, undo patchのメモリ使用率はよく非常に大きくになる (大きい画像を編集する時). CPUで保存すればメモリ不足の時データはDiskに移行できる.

同期モデルは簡単ですが,計算フェーズの中GPUデータは取得できない. 例えば, save/load操作は全てのレイヤーをCPUに同期してファイルにexportする. Save/load操作は次のフレームまで待ってもいいが, undo listに関する操作, 例えばsubmitとundo list walkingは次のフレームを待つことができない. 次の同期フェーズに待っていれば, edit bufferは"同期待ち"状態のままになったら, その後に起こった全ての操作(例えばブラシエンジン)は処理できなくようになる.

Undo/submitは頻繁な操作ですが, ブラシと同じ遅延レベルまで必要はない. だから, 今の解決方法はGPU操作を完成するまでCPUをブロックする.

undo-1

submitの場合, GPU操作が完成する時, 次のコマンドをレコードする:

  1. edit bufferを同期する.

  2. 元のレイヤーからdamage areaをCPUに保存する (今はcommand recordから, まだCPUに保存されていない).

  3. 編集中のレイヤーをブレンドする.

  4. GPUレイヤー(ブレンドされたpreviewから, 同じdamage area)の更新.

  5. edit buffer(CPU側)をクリアする.

このコマンドbufferはもう一度submitしてCPUをブロックする. これでsubmitが完成した, CPU側のレイヤーから同じdamage areaの画像をクリップしてundo patchを作成する.

Undo list walkingの場合もほぼ同じ: まずは編集中のデータをGPUに同期, そしてレイヤーを更新+ブレンドして, ブレンドしたレイヤーでレイヤーを上書きして, コマンドをsubmitして,完成までCPUをブロックする. 最後はundo/repo patchを切り替える.

まとめに

絵描きソフトについて, 入力遅延を最小限に抑えることは最も重要なので, 基本的には全部同期するだけでいい.

vwdraw(と他のライブラリ)は自分の初めて書いた(大型)Cプロジェクトです. 以前使っているRustのいろんなvulkan binding(vulkano/ash/wgpu-rs)より, 全てのライブラリはdynamic linkingのおかげで, コンパイル速度(Rust+sccache, w/o shaderc-rsに比べて)は10倍以上, binaryファイルサイズは(Rust静的リンクされたexecutableに比べて)10%以下に改善した.


明日の担当は @dogwood_flo です, お楽しみに!

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

Code trivially.

この記事をシェア

このエントリーをはてなブックマークに追加
共有
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記