最後の数週間の間、少し自由時間があったので、楽しいプログラミングの練習として、CPU ベースのマルチスレッド ベクター グラフィック ラスタライザーを書き留めることにしました。 このフェアはかつて、公的にアクセス可能なすべてのベクター グラフィックス ライブラリのラスタライザーよりも優れたパフォーマンスを発揮することを目的としていました。 そして今日、私はここで得たものを共有しています。
私はプロジェクトを Blaze と名付けました。そのソースは ここにあります 。 Web ベースの基本的なデモ がここ にあります。 デモは WebAssembly 上で実行され、WebGL2 を利用して最終的な画像を作成します。 GPU は、どのようなアプローチでもピクセルを生成することにあまり関心がありません。 効率を最も簡単にするために、必ず まともな Web ブラウザを使用してください。
はじめに。
)歴史的スキャンライン ラスタライザーは、ルート アウトラインをワード化し、すべてのスキャンラインのエッジ リストの値を変更し、このエッジ リストをスパンの配列に変換して、スケダドル マップ表面上の最終構成に使用します。 このタスクをかろうじて適切に最適化する絶対的な最上位の設計はありますが、単一の CPU コアに確実に依存するという欠点があります。 悲しみの原因となった最後の言葉は、ルート内のどのセクションも、想定されるピクセルをどのようにカバーするかに影響を与える可能性があるという事実です。 つまり、ルート全体のいくつかのセグメントを探して、ピクセル X に屋根があるかどうかを仮定する必要はありません。説明するために、三角形のメッシュを使用することが好ましいです。 そのため、複数のスレッドでワークロードを均等に分散する公式を見つけることが訓練となります。次の数段落で、私がこの悲しみを解決しようとしている方法について簡単に説明します。
Enter.
ラスタライザーに入力すると、基本的なベジェ パスのリストが表示されます。 すべてのルートは、セグメントの形式を示すルート コマンドの配列と、これらのセグメントを記述するファセットの配列としてエンコードされます。 最も簡単なカラー蓄積とソースオーバーミキシングモードが現在サポートされており、これは既存のアイデアに十分です。
セクションリストを増やします。
表示隠蔽、またはスケダドル マップ イメージは、まずマウントされた高さの間隔に分割されます。 次に、ラスタライザーは、すべてのルートが交差する間隔ごとにセクション リストを作成します。 すべての入力ルートは、インターバルと交差するのと同じ数のセクション リストを取得します。 次に、ルート セグメントが変換マトリックスによって再加工され、スケダドル マップ画像の境界にクリップされ、トレースに平坦化されます。 不正確な間隔境界を重視しないように、ひずみはさらに細分化されます。 これはすべてのルートで並行して発生します。 最後に、線分が属するセクションリストに線分が挿入されます。以下は、2 つの形状を含む画像のセクション リストの準備の結果を示す図です。 影付きの境界線は線分を指します。
行セクション圧縮。
線分はセクション リストに挿入するときに圧縮されます。 すべての場合において、Y 値は 8.8 マウント ポイント番号としてエンコードされます。 X 値は 2 つのさまざまなプログラムでエンコードされます。 ルート幅が 256 ピクセルより小さい場合、X 値は同じ 8.8 マウント ポイント エンコーディングを所有します。 それ以外の場合は、より大きな 24.8 の数値としてエンコードされます。 狭いエンコードでは 1 行あたり 8 バイトのセクションが使用され、巨大なエンコードでは 12 バイトが使用されます。ハイテールの点では、これは Apple M1 Max のような途方もなく速いメモリ帯域幅を備えたプログラムに適度な機能拡張を提供しますが、さらに最大 2 バイトまで拡張できる可能性があります。 Enter で検索する、遅いプログラムに対する全体の 10% の強化。
行リストの構築。
見世物小屋の間隔ごとに、交差するパスのリストを取得する必要があります。 第 2 段階の原因は次のとおりです。 さらに、ラスタライザーには入力ジオメトリのより詳細な記録があるため、おそらく行の推定に寄与しないパスをスキップする可能性があります。このステージの出力は、空ではないセクション リストを含むショー ハウス間隔ごとのパスの配列です。以下の画像は、行リストを灰色の背景の四角形として示し、間隔ごとにどの形状のどのビットが含まれるかを示しています。
ラスタライズとミキシング。
ここで put ピクセルが生成されます。 ショーハウスの間隔ごとに、スレッドが生成され、全体のラスタライズが行われます。 ラスタライザーは、フロント通知をサポートするために、最近の間隔で作成されたパスのリストを反復処理し、すべてのルートがそれに割り当てられているセグメントをラスタライズします。セグメントのラスタライズは、かなり簡単なスキャンライン コンバーターです。 ただし、ラスタライズはコンパイル時に高さが特定され、一度に 1 つの間隔で完全に実行されるため、いくつかの最適化の機会があります。
高密度のキルトおよびハウス テーブル。 このラスタライザーは、交差するすべてのピクセル セクションのライト ハウス値を計算します。 ラスタライザのこのモデルは、ピクセルごとに 2 つの整数を使用します。 重要なのはハウスであり、そのピクセルと交差するセクションによってどのくらいのピクセルが並んでいたかを示します。 2 つ目はキルトで、セクションが交差するピクセルの許可されたすべてのピクセルにセクションがどの程度影響を与えるかを示します。 これは明確に識別されたアプローチですが、多くの多様な実装があります。 一部のラスタライザーは、ピクセル セクションの交差が発生するとすぐに新しいセルと合わせて、リンクされたセルのリストを保護します。 これは、メモリ構造のアプローチによって、ほとんど遅くなり、信頼性がなくなる可能性があります。このラスタライザーは非常に小さな画像ハウスで動作するため、総間隔でハウス テーブルとキルト テーブルにメモリを割り当てることは、それほど大きな問題ではありません。 このメモリは、ラスタライザによる最近のメモリの処理が完了するとすぐに、次の間隔で再利用されます。 どのピクセルがセグメントによって交差されているかを知るには、非常にベクトルが必要です。 終了内では、どのピクセルを蓄積するかを計算するために許可されたビット スキャンに任せるのは簡単です。 最も簡単なビット ベクトルは、ルートをラスタライズする前にゼロが詰め込まれます。 キルトとハウスのテーブルには、流行のつぶやき資料が残されており、貴重なメモリへの書き込みがさらに回避されます。浮動小数点数の回避。
さらに、予備セクションでは、カーブクリッピングが発生し、浮動小数点数は通用しません。 曲線の平坦化も、
マウントされたポイント番号 が常に整数として識別されることで完全に行われます。 一部の演算で浮動小数点数を使用することが全体的にもっと早くなるとしても、私にとっては全くのショックではありませんが、それでも、整数がいかに簡単で予測可能であるかには感心しています。結果。
効率の数値は常に楽しいものです。 ラスタライザーを CoreGraphics、Skia で評価することにしました。 および Blend2D。 Blend2D には、内部で並列ラスタライズを行うマルチスレッド コンテキストがあります。 それは間違いなく比較の正しい候補のように見えます。 一方、CoreGraphics と Skia は、基本的に最もきちんと評価されているレンダラーの 2 つです。最終的なかなりのミリ秒を測定するために、すべての画像が連続して 500 回レンダリングされます。 次に、結果が並べ替えられます。 最も単純な 5 つの結果と最悪の 5 つの結果が消去され、490 回の実行の緩和にかかる標準時間が計算されます。レンダリング前にスケダドル マップのピクセル バッファーをクリアすることは、時間計算にあまり組み込まれていません。どのような場合でも、レンダラーはすでにゼロが詰め込まれたスケダドル マップを取得します。 ルート オブジェクトは事前にきちんと作成され、タイミングは統合されていません。 画像のすべてのストロークは塗りつぶしに事前変換されています。 すべてのライブラリには、完全にカラー塗りつぶしを含む同じ正しいレコードが供給されます。 他のシステムではなく、ラスタライズの効率を確認することが目的であるため、最も簡単なカラー塗りつぶしやソースオーバーミキシングは過去のものです。すべての画像は 200% のスケールでレンダリングされます。以下の数値は、10 コアの Apple M1 Max CPU を搭載したマシンで測定されています。 時間はミリ秒単位です。 減少の方が大きい。
タイガー、960×1014ピクセル、301レイヤー。 SVG | ブレイズ | 0.6ms |
---|---|
Blend2D | 0.8ms | スキア | 5.9ms |
コアグラフィックス | 11ms |
ブレイズ | Blend2D | スキア 10.5ms | コアグラフィックス |
ボストン |
---|
コアグラフィックス |
18ms
段落 、1650×26158 ピクセル、11253 レイヤー。 SVG | ||||||
---|---|---|---|---|---|---|
ブレイズ | 6ms | Blend2D | ||||
コアグラフィックス |
このラスタライザーは、M1 Max を搭載した MacBook Professional のエレファンタイン ショーで、あらゆるスケールでほぼ一定の 60 FPS で Paris-30k をレンダリングします。 または、次の場所にある大量のテキストのつぶやき資料 120FPS。 今はツールラスタライザーとしては腐っていません。
うまくいかなかったもの。
立ち上げ当初の私のアイデアは、最終段階ではさまざまなアプローチを採用することでした。 セグメント自体は水平方向の間隔に分割されていませんが、8 × 16 タイルと小さいです。 また、左から許可されたものにしたい場合、スキャンは上から下に実行されていました。ラスタライズ段階では、タスクは次のようにクロールします –
- 垂直間隔内の最も先端のタイルで開きます。 確かなキルト/ハウステーブル。 8×16 タイルの場合、おそらく 8×16×2 の量の 32 ビット整数になる可能性があります。 そのタイルのすべてのセグメントをラスタライズします。 すべてのピクセルのアルファ値を計算しながら、そのタイルの上から下にキルト値アキュムレーターを実行します。
- )その下の次のタイルに進みます。
タイルにセグメントがない場合は、基本的には最後のタイル以降のキルト値の最終累積に基づいて累積します。