Skip to content

Instantly share code, notes, and snippets.

@shibukawa
Last active November 22, 2017 10:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shibukawa/583d7be277c5be7fe034 to your computer and use it in GitHub Desktop.
Save shibukawa/583d7be277c5be7fe034 to your computer and use it in GitHub Desktop.
NanoVGo実装メモ

NanoVGo実装メモ

オリジナルNanoVGの構造

  • NanoVG のソースコードを読んでみた が参考になる
  • 画像読み込み周りはstb_image、フォント読み込み周りはstb_truetype(オプションでFreeTypeも使える)を使用している。どちらも1ヘッダでやるエクストリームなライブラリ。
  • バックエンドの実装と切り離しやすい構造になっている。
    • NVGcontextが、ユーザのインタフェースとなる関数の第一パラメータで渡される(Pythonのself的)構造体。
    • NVGpamamが、バックエンドで使うコールバックを登録する。
    • nvgCreateGL2() みたいな関数で初期化している。この中でバックエンドで使う関数と接続している。
    • OpenGLバックエンドのコンテキスト保持の構造体はGLNVGcontextで、nanovg_gl.hにすべてのOpenGL固有の実装が入ってる
    • DirectXを使うフォークとかもあるっぽい

実装方針

  • 基本的にはオリジナル版NanoVGのベタ移植
    • ただし、1ファイルのヘッダでやっているのはいくつかに分ける
    • Golangユーザからみて不自然に感じないAPIにする
      • StateのセッターにはSetを付ける(SetFontSize)。ゲッターは何も付けない(FontSize)
      • 文字列関連は文字のポインタを扱うものはruneのスライスにする
      • Goの命名規則にしたがって、Runeを受け取るものは末尾にRune(TextTextRune)を、最大数指定はN(TextBreakLinesTextBreakLinesN)をつける。
    • 関数のプリフィックスのnvgは外す。
    • ライブラリのパッケージ名もnvgの方が良い?nanovgoは長い。
    • 定数だろうとなんだろうと、外部公開しないものはすべて小文字名にする。コード補完したときに必要なものしか出ないようにする。
  • 周辺のコードの対応
    • OpenGL周りは、gopher.jsやモバイル対応も考えているgithub.com/goxjs/glを使う。
    • WebGL 1.0のスペックに合わせて、モバイルもES2、デスクトップもOpenGL 2でちょっと控えめなところに水準を揃える。
    • WebGL 2.0が普及したらES3、OpenGL 3系にしていくのがいいかもしれないけど数年は期待できないので今は無視
    • TrueTypeフォント周りは fontstash.go のtruetypeモジュールを使った。
    • が、↑はTTFだけしか処理できないし、github.com/golang/freetype/truetypeに乗り換えて、TTCも読めるようにしたい
    • FontStashはfontstash.goを使うのではなくて、オリジナルのfontstash.hの中から、使う関数だけを移植した。
    • 画像処理周りはstb_imageを移植するのではなく、Goの標準ライブラリのimageを使う
  • フラットな関数をGoの構造体をつかって階層化する
    • NanoVGの元ネタのHTML5のCanvasと同じように、ctx.BeginPath()みたいにできるようにする。
    • APIとして公開するインタフェースはnanovgo.goのContext構造体 + NewContext()関数
    • オリジナルはNVGParamsが関数ポインタを束ねる構造体だが、Paramsをインタフェースにして、GLParamsに実体を書くことでバックエンドと連係するようにする
    • 基本的には関数の先頭にくる構造体のメソッドにしていくが、NVGContextがでかくなりすぎなので、一部PathCache系に寄せている

      そのため、NVGContextのパラメータを参照していた一部関数で引数が増えている

ムダなメモリ操作を撲滅するという意思表明

  • 構造体の配列はポインタの配列にしない
    • golangがデフォルトでC言語のreallocを使ってよくやる挙動(2^nサイズにしてコピー)。配列を縮めても一度確保したメモリは保存してくれて、一度確保したメモリを最大限に利用してくれる
    • ↑再描画ごとに大量にパスオブジェクトができたりするので、なるべく同じところに何度も入れる方が良い
    • forループの記述はシンプルには書けないけど我慢
  • 不便だけどOpenGLに合わせてプリミティブで実装する個所があるよ
    • OpenGLに送るバーテックスのデータだが、glBufferDataでバイトで送る。今はvertexesを[]byteで宣言してencoding/binaryで真面目に実装している。
    • まぁリトルエンディアン以外グラフィックスを使う環境は絶滅しているから[]floatにしてunsafeでキャストでもいいかも。方法がわかれば。
    • GLFragUniformsも、オリジナルはunion使ってうまく構造体としてコードを書いて、float配列で取り出して・・・をしている
    • 今はデータの流れが一方向で済んでいるので、データは配列にして、セッターをいっぱい用意して誤魔化すことにした。

将来なおしたいけど・・・

  • 今はC言語版/OpenGLに合わせて、32ビット演算(float32, int32)を積極的に使っているが、Gopher.jsではパフォーマンスのために64ビットを使えとのお達し。
  • WebAssemblyがすぐ来るなら必要ないけど、そうでなければビルド環境によってfloatを32/64ビットで扱うように条件ビルドで切り替えたいところ。WebAssembly早く来い。

現状のpublicな項目

  • NewContext()関数(エントリポイント)
  • Context構造体(NewContextが返す)
  • Paint, Color, TransformMatrixとそれらを作る関数などなど

今のNanoVGにはないけど足したい機能

  • TransformMatrixを取得できるけど、設定できる関数がないっておかしくね?
  • オフスクリーンレンダリング
    • 描画命令状態でバッファリングしておく機能。カーブや太いストロークをポリゴンなどに分解してバックエンドに送る前の状態をとっておくと良いかも。
    • ↑返還前の状態で最終的な描画範囲を計算して返せると良い
    • ↑globalAlphaや座標変換はスキップしておいて後で変更できる機能もあると良さそう。
    • 完全なオフスクリーンレンダリング。テクスチャとして生成してテクスチャIDを返す。もしくはimageオブジェクトにもできる。
  • カーブを直線にサンプリングする時の粒度の調整。まさに動きまくっているものとかは精度を落として速度を得るというトレードオフができてもいい。

NanoVGのコードを読んでいて気になった点

あとでPull Request送る

  • nvg__tesselateBezier(): 2つ目のreturnで抜けるなら上で計算しているものが不要
  • nvg__flattenPath(): NVG_CCWとNVG_CWは排他なので後者はelse ifにできるのでは?
  • nvg__expandStroke()の中でif loop == 0がなぜか2つにわかれている。lineCapで同じ項目を参照している
  • solidity, windingの定数のコメント
  • nvgFill()の中のstrokeの統計をfillに入れている?
  • デフォルトのfontIdが0なのだけど、FONS_INVALIDじゃなくていいの?
  • nvgTextBox、halignがスペルミス
  • nvgTextBoxBoundsでfonsSetSizeとか呼んでいるけど、nvgTextMetrics内でもやっているから不要では?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment