読者です 読者をやめる 読者になる 読者になる

C++ ときどき ごはん、わりとてぃーぶれいく☆

Wonder Rabbit Projectのなかのひとのブログ。主にC++。

C++ tips: Boost で demangling

C++ Boost demangling RTTI

RTTIで型情報を対話的に可視化したいと思うと、C++ではデマングルしないと割と読めません。extern "C"しておくとか邪悪臭いですし。

ところがデマングリングは処理系依存に触れる事になるので、ユーザーコードで扱いたいものではありません。そこで処理系依存のデマングルをラップしたライブラリーレベルでユーザーコードからは使いたいと思うのが一般的なプログラマーの思考かと思います。

Boost.Units にあるよ・w・

例えば、

#include <boost/units/detail/utility.hpp>
using boost::units::detail::demangle;
auto demangled_name = demangle(typeid(0).name());

こんな感じで使うと、翻訳時に0の型はintになるのでdemangled_nameには"int"なるstd::stringが入る事になります。

もうちょっとゴチャゴチャ見てみましょう・w・

魔黒は便利なC++機能です。死者が出ない程度に有効に使いましょう。と、それはさておき、この例の実行結果は著者環境では次の通りです。

i --> int
Ss --> std::string
St6vectorIhSaIhEE --> std::vector<unsigned char, std::allocator<unsigned char> >
1XIiIfdjbDnEE --> X<int, float, double, unsigned int, bool, decltype(nullptr)>
PFivE --> int (*)()

著者環境は g++-4.7.1 / openSUSE-12.2 です。テンプレートが絡まない型はマングリングされた状態でも読めなくも…いやどうなんだ…程度な事もありますが、テンプレートが絡んでるともう型名読めません、デマングル必須です・w・

と、言う訳で今回は「処理系依存のdemangleとか直接書きたくないでござる」という場合にBoostの一角に含まれたデマングリング機能を使っちゃう方法の紹介でした。

Color programming tips with C++ samples: RGB24(16777216色)に対しHSL24は十分な分解能を持っているか?

色空間 RGB24 HSL24 R8G8B8 H8S8L8 conversion resolution C++ sample tips

今回は色の話。PCでは長らく16777216色を誇るRGB24がディスプレイはじめ色を取り扱う際の標準として利用されてきた。RGBなのかBGRなのかなどは今回の本題では無いのでスルーする。また、制限された環境におけるRGB16やRGB15であるとか65536色中同時256色であるとかシステムカラー固定8色であるとか、或いは一部特別な状況やデータ形式でRGB各14bitだとかHDRが…とかの話も今回は本題では無いのでスルーで(;´∀`)

『16777216色を誇るRGB24に対してHSL24は十分な分解能を持っているか?』

この問いに即答できるだろうか?ちょっと考えれば直ぐに答えが出るかもしれない。曰く、

YES

と。

確信を持ってYESと答えられたならどうぞ続きを読んで行って下さい。NOと答えられたなら読まなくても良いかも。でも本当かな?少しでも自信が無い方はどうぞ(´ω`)

RGB24 = R8G8B8 の情報量

色をR,G,Bの各色に分解し各8bitsの分解能で標本化して表す。通常はR,G,Bの各標本には単純に整数を用いる。よってRGB24が表す色の情報量は当然24bitsで、これは組み合わせの総数にすると pow(2,24) = 16,777,216 通り(≈色数)。

HSL の情報量

HSLを考える時、幾つかのパターンが考えられる。

H8S8L8 の情報量

RGB24に倣って最も単純に考え得る色のデータ構造はH8S8L8で各要素が整数のパターン。さて、これは本当に24bitsの色情報を持っているだろうか?

少なくともH8S8L8としては24bitsの情報量を持っている事は確かだ。でも、実際のPC上ではHSLをネイティブに扱うディスプレイデバイスは恐らく無くて、色が実際にディスプレイから発色されるまでのどこかでRGB24に変換されていると考えられる。これは本当に24bitsの、つまり16,777,216通りの色を扱えているだろうか?

この答えを求める為に HSL → RGB の色空間の変換アルゴリズムを知る必要がある。

ENのWikipediaのHSL色空間の記事は詳細で丁寧で、 HSL → RGB の変換アルゴリズムも掲載されている。これを基にC++で実装すると、

RGB24とHSL24は、

struct RGB24 { uint8_t r, g, b; };
struct HSL24 { uint8_t h, s, l; };

こんな具合にしておいて、変換アルゴリズムの実装は、

こんな具合に。

入力のHSL24では各8bitのフルレンジ[0-256)にH(色相≈色角度)、S(彩度)、L(明度)を割り当てる。変換アルゴリズムの参考としたENのWikipediaの記事ではHを弧度法で扱っているので [0-360)° を扱っているけれど、これは平面角の次元を持っていれば単位は何だって構わないので、 ラジアンを採用すれば [0-2π) rad でも構わない。

でも弧度法で0°から360°を1°の分解能として整数でカバーするには勿論8bitでは足りない(0..359の360通りの状態を表す為には log(360,2)≈8.492 bits の枠が必要)ので、弧度法を採用する意味は無い。また、ラジアンは少数を扱う必要があるので8bitで一般的な少数を扱う仕組みを組み込むのはちょっと現実的じゃない。それで例示した実装では H は 0..255 の整数に unorm (符号無し正規化値: 0.0-1.0)をマップする形で円周"度"みたいに扱っている。

また、この実装では変換アルゴリズムの内部で用いる浮動小数点数の分解能の都合でも変換による情報損失が発生する可能性があるから、とりあえず内部型の影響を変化させて観察しやすい様にテンプレート関数にしてみた。(実際にこれはIEEE754的に言うところのBinary32, Binary64, Binary128の3つのセットを使って実験してみたよ。)

と、言う訳で実験的に HSL24 → RGB24 が16777216色を出力できるのかやってみる。

実験ソース

先ほどの変換アルゴリズムに尾びれ背びれ胸びれなどを付けてみた。

最初に簡単な実装で…と思って書き始めてから、後でちょっとだけゴテゴテ付け加えてしまったのでちょっと設計的にどうなのと言う残念な気持ちになったであろう事は分かる。申し訳ない・w・;

内部型の浮動小数点数について

Makefileから3通りの実行ファイルが生成されます。 binary32 binary64 binary128 はそれぞれ変換アルゴリズムの内部型に float double __float128 を用いてビルドする様に仕込まれています。__float128はGCC4.6以降で使用できる拡張仕様で、気になった方は以下の日本語記事が参考になると思います。

.cxx にて ADOPT_BINARY32 ADOPT_BINARY64 ADOPT_BINARY128 の定義状態によりCPPで簡単なソースコードの特殊化が行われる様にしています。

Boost.GIL について

ちなみに Boost.GIL には HSL を扱うコードが今のところ含まれて居ません。また、このサンプルでは Boost.GIL はPNG形式の画像として結果を保存する為の libpng のラッパーとしてしか使っていません。(libpngとかCスタイル過ぎて使うだけでワヒャヒャヒャヒャヒャとかむず痒くなるよ!)。この程度の簡単な使い方であればLet's Boostの以下の記事を参考にすると良いです。

OpenMP について

ソフトウェア浮動小数点数となる __float128 を用いる場合、CPU組み込みで扱える float double に比べて著者環境でも凡そ 7 倍の計算時間が必要でした。具体的には float double が6秒弱で全体の処理を終えるのに対し __float128 は40秒少々を要しました。そこで、単純な処理ですから処理を平行化しておこう、となるわけです。

なるわけですが、C++11 threadを使うまでもない本当にちょこっと、の平行化なので気まぐれにOpenMPでプラグマ1行書いて済ませてみる事にしました。尾びれ背びれ胸びれと付けてしまったのでOpenMP採用に伴うコード量の増加は数行になってしまいましたが、OpenMPの基本はソースコード中で「①このコードブロックで並行処理させたいぞ!」と「②このforを適宜に分割して平行化してくれ!」を指示するだけでよしなに並行処理するバイナリーを生成してくれます。

-fopenmp を付けなければ通常はエラーや警告も出ずにOpenMPを使わないバイナリーも生成できますし、今回程度の緩やかな平行化のニーズにはぴったりです。ちょっとここ平行化しといたら嬉しいかな〜程度ではなく、やらなければいろいろと死んでしまうのだ!みたいな状況ではOpenMPでは平行処理の最適化の粒度やプログラマビリティが悪いけど、このお手軽さは良い・w・

OpenMPの入門の参考としては下記の日本語の記事が分かりやすく整理されていて良いかもしれません。

実験環境

  • Softwares
  • Hardwares
    • AMD Phenom2 X4 (3.0GHz x 4 cores)
    • DDR2-800 8GB
    • intel SSD 330 Series 128GB

実験結果

duplicated

HSL24→RGB24の全単射の結果、重複したRGB値が得られていました。変換アルゴリズム内部の分解能に応じても結果が異なりました。重複数と失われた情報量は、

重複数実質情報量[bits]色数[colors]%損失
基準RGB2402416,777,2160
binary32(387)537,89923.952 98816,239,3173.206 1
binary32(SSE)577,78923.949 43916,199,4273.443 9
binary32(SSE+387)835,35823.926 31615,941,8584.979 1
binary64(387)581,87923.949 07516,195,3373.468 3
binary64(SSE)676,79523.940 59516,100,4214.034 0
binary64(SSE+387)646,11523.943 34216,131,1013.851 1
binary128(387)717,94523.936 90316,059,2714.279 3
binary128(SSE)717,94523.936 90316,059,2714.279 3
binary128(SSE+387)717,94523.936 90316,059,2714.279 3

こんな結果に。実行バイナリーの 387 SSE SSE+387 はGCCのオプション -mfpmath に対応します。こまけぇ〜事はいいんだよ!という方は(SSE)だけで見比べても構いません。これには計算精度の良し悪しの他に計算内容による偶発的な要素も関わりますので(´・ω・`)

ところでそもそも HSL24 は何色か?

3つのパラメーターを256通りに組み合わせられるのでHSL24のパラメーターの組み合わせの総数はRGB24のそれと同じで当然16777216通りです。RGB24では3つのパラメーターのうち1つでも異なる色同士は別の発色となるので16777216色です。HSL24ではどうでしょう?

色相 H の値を 0 rad = 0 ° → uint8_t(0) から 2π rad = 360 ° → uint8_t(255) とマッピングすれば 0 と 255 が同じ色相となってしまい、実装による損失となります。この点は 0 rad → uint8_t(0) から 2π → uint8_t(256) = uint8_t(0) とマッピングされる様に正しく実装すれば問題になりません。実装上は uint8_t では 256 は扱えないので、256という値を扱う部分が必要であれば、そこはuint_fast16_tを使うとかdoubleを使うなどして慎重に実装しましょう。

彩度 S と 明度 L は範囲の上下は文字通り両極端でループしませんので、こちらは 0 .. 255 に正規化してマッピングすれば良いでしょう。

さて、問題はこの S と L です。

彩度 S は 色相 H の発色の良さ、即ち彩度ですから、S=0の時はあらゆるHに対して同じ色を呈します。S=0の世界は灰色(グレースケール)の世界になります。よってHSL24ではS=0の時、HとLによる呈色の組み合わせはLのみに依存しL=0..255の256通りしかありません。(8+8=16)bits=65536通りの値の組み合わせの内、256通りの他は全て重複した同じ色を呈します。よってこの時点でHSL24はRGB24に対して65,280色少ない事になります。

明度 L は文字通り明るさで、L=0だと真っ暗で物体表面から光がカメラなり目なりへ届かない状態です。また、L=255だとカメラの光センサー素子なり目の受容体なりが完全に飽和してしまい色が分からない極めて眩しい状態です。つまり。L=0またはL=255の時は、あらゆるHとSの組み合わせに対して「真っ黒」または「真っ白」しか呈色しません。よってHSL24はRGB24に対して、 L=0 と L=255 の時、HとSによる各 65536 通りは各 1 通りしか呈色せず、Lを要因としても RGB24 に対し 65,535*2=131,070 色少ない事になります。

と、言う訳でHSL24は理論上RGB24に対し 65,280+131,070=196,350色少ない事になり、HSL24が呈色可能な色数は16,580,866色となります(HSL24=H8S8L8とした場合)。これはRGB24の持つ色空間よりHSL24の持つ色空間は 1.170 3 % 小さい事になります。

実際には今回のHSL24→RGB24の変換結果は3〜5%の損失が生じて居て、理論上の損失に加えて計算上の損失も大きく影響していた事が分かります。

記事が長くなって来たのでぷちまとめ

HSL24はRGB24に対して理論的に重複した呈色を含むため 1.1703% も色空間が狭い。加えてHSL24→RGB24の変換計算でも呈色の損失が生じ、全体としてRGB24に対してHSL24で呈色可能な色空間は3〜5%程度狭くなってしまう。

必要に応じて、対応策としてはHSL24のH、S、Lの各情報量をヘテロに構成(例えばH12S6L6とか)する方法、そもそもRGBより多い情報量をHSLの色情報の保持に割り当てる(例えばHSL32とかHSL96とか)が考えられる。それはまたおいおい記事にするかも。

GCC4.7.1: C++11 と OpenMP のちょっと惜しい関係

C++11 OpenMP Concurrency OpenMP 3.1 GCC 4.7.1 C++ tips

C++11から言語仕様のスレッディング周りが強化され、thread、atomic、mutexなどなど言語標準機能として扱える様になり、ちょっとやそっとやそれなりの用途でもintel TBBやBoost.Threadに頼らずにマルチスレッディングソフトウェアを開発し易くなりました。

なりました、が、ありがちなごく単純なforのちょっとやそっとの並行化如きであれば OpenMP で十分です。ところがC++11とOpenMPの組み合わせではちょっと惜しい部分があります。

OpenMP による for 並行化

とりあえず OpenMP による for の並行化ついて簡単に。

↑こんなソースを

g++ omp.cxx -fopenmp

とかビルドして実行すると、OpenMPにより0..9の出力が並行化されあばばばばばします。ちなみに-fopenmpを付けずにコンパイルすればプラグマは活きずにOpenMPによる並行化は行われず、0..9を順序良く出力します。

C++11 for

さて、C++11では区間全域(いわゆるforeachタイプの)に対するforとして range-based for なる構文が実装されました。

std::vector<unsigned> v(10);
std::iota(v.begin(), v.end(), 0);
for (auto a : v)
  cout << a;

こんなのですな。既に多くのC++erがこの構文に慣れて来た頃かもしれません。ところがこの構文は便利な OpenMP と組み合わせられません。

↑こんなソースを

g++ omp.cxx -fopenmp

とかビルドしようとすると、

omp.cxx: In function ‘int main()’:
omp.cxx:6:14: error: expected ‘=’ before ‘:’ token
omp.cxx:6:37: error: expected ‘;’ before ‘)’ token
omp.cxx:6:37: error: expected primary-expression before ‘)’ token
omp.cxx:9:1: error: expected primary-expression before ‘}’ token
omp.cxx:9:1: error: expected ‘)’ before ‘}’ token
omp.cxx:9:1: error: expected primary-expression before ‘}’ token
omp.cxx:9:1: error: expected ‘;’ before ‘}’ token
omp.cxx:6:3: error: invalid type for iteration variable ‘a’

とかなんとか激しく構文エラーを指摘されてしょんぼりする事になるでしょう(´・ω・`)

(´・ω・`)

今回は GCC 4.7.1 で試しました。

によればGCC 4.7系はOpenMP 3.1をサポートしているのだそうだけれど、

とか見てより新しい次のシリーズのOpenMP規格、OpenMP 4.0の仕様を見ても、C++C++98を基準にしたまま。 range-based forをOpenMPでちょちょいと並行化できる日は遠いのかも(´・ω・`)

openSUSE: eyefi-config 011.20120712 package released

openSUSE Build Service Eye-Fi eyefi-config package

Link to OBS

なに?

OBSにパッケージがあった eyefi-config が随分古かったのでbranchして現行最新版のgit-reposより 2012-07-12 版に差し替えました。

で?

  • --transfer-mode
  • --wigi-radio
  • --endless

などが使える様になってたり、 -p で渡すパスフレーズがバグる場合があるのが治ってたりします。

Qt5 / Qt Quick 2.0 第3回 続々・はじめたよ : Canvas

Qt5 Qt Quick 2.0 Canvas HTML5 QML

続々ですが今回はあっさりと終わっちゃいます。

教材

32:15くらいから。

概要

Qt5/Qt Quick 2.0でHTML5Canvasを使うらしい。HTML5Canvasはもう今更だから解説はしないけど、必要ならこちらとか参考にしながら進めたら良いと思うよ。

雛形

ビデオの34:30頃に今回の最初のソースが登場する。

f:id:USAGI-WRP:20130104021340p:plain

解説

Canvas要素の前半のid、width、heightはただのRectangleにもあるQML部品の基本要素。描画タイミングのコールバック関数として onPaint プロパティにECMAScriptHTML5canvasそのものなコードを書いている。

HTML5canvasやgetContext、それからコンテキストへの描画などについて知識が無ければ先にも紹介したこちらを見れば良いと思う。

Canvas

御馴染みHTML5canvasのQML実装。

「あらゆるHTML5の2Dコンテキストピクセル操作をサポートしている」と明記されている。

Context2D

HTML5canvasの2DコンテキストもQMLのオブジェクトとしてきちんと定義されている。

ECMAScriptでgetContextして取得するオブジェクト。HTML5のそれ互換。

余談

プログラマーならHTML5がどうとかこうとかって話からそのcanvasについてあーのこーのと勉強するよりも、寧ろこのQt5/Qt Quick 2.0のCanvasでそれを学んだ方がドキュメンテーションやプログラマビリティについてよく整理しながらcanvasの本質をしっかりと学べるのではないかな、とか思うなど(次回のECMAScript講座からはいきなりこれで教えようかな、HTML講座じゃないしね。学生も楽しいだろうし)。

\(^o^)/

もうちょっと何かしら紹介するかと思ったんだけど、どうやらこのセッションではCanvasについてはこれだけ、そのあとはPathAnimationとTextのレンダリングとエフェクトの改善について軽くなったよって述べて、後はまとめ、質疑応答へ。

と、言う訳で私もいまさらCanvasにさほど興味無いので今回は短いけれどこれで終わりに。次回はQML要素をC++で作る紹介なんかを適当にやろうかな。

Qt5 / Qt Quick 2.0 第2回 続・はじめたよ : ShaderEffect

Qt5 Qt Quick 2.0 ShaderEffect QML GLSL

前回に続き、例の紹介ビデオを教材に、25分あたりからのシェーダーエフェクトをざっくり遊んでみる事にしましょう。今回は冗長な解説はせずに初出の事項についててきぱき解説するよ。

教材

対象

  • QMLについてなんとなく雰囲気くらい分かった程度のさむばでぃ
  • プログラミング一般の基礎知識はありそうなさむばでぃ
  • GLSLとか触った事あるか基礎的な概要くらい知ってるさむばでぃ

雛形

教材にしたビデオの25:50付近で最初の雛形が登場します。

f:id:USAGI-WRP:20130103165416p:plain

「しれっとロゴ画像ちゃうやんけー」などは気にしなくて良い事です。

解説

Row

内包する要素群をカラム要素として1つの行(=row)を成して表示する為のGUIコンテナー。今回はImageとShaderEffectの2つの要素を内包して使われている。

Image

画像をロードして表示するGUI部品。sourceはURLに対応。smoothはboolでデフォルトがtrueなので現在は明示的に指定している意味は無い。

ShaderEffect

他の何らかの要素(RectangleでもImageでも他適当な)にOpenGLにより頂点シェーダーとフラグメントシェーダーを適用して表示するエフェクター。シェーダープログラムの定義方法はGLSL。

頂点とフラグメントしか無いのが残念だけど、QMLで手軽にGLSLエフェクターも使えるのは嬉しいですね。今回のメイン。

property

これはC++による実装も含め、QMLを含むQtのプロパティシステムについて触れる必要が生じる。

とは言え、現段階で全貌を把握する必要はないので、今回登場するQMLでの property キーワードは要素にプロパティをQMLから追加またはオーバーライドする宣言であるという事だけ分かれば十分です。

variant

property宣言に続いて登場している variant は型名です。QMLではintやrealやstringなどの型システムがあり、QML要素のプロパティはC++バインディングされていますから当然内部的には型があります。property宣言でプロパティを追加またはオーバーライドする為にはそのプロパティの型も指示する必要があり、property宣言とプロパティ名の間に今回はvariantと型名が入って居ます。

variant型は一般的なプログラミングにおける動的型付けでお馴染みのバリアント型だと思って置けば良いのですが、仕様には

The variant type is a generic property type. It is obsolete and exists only to support old applications; new applications should use var type properties instead.

曰く、「variant型はジェネリックなプロパティの型。これは廃止されており、古いアプリケーションらの為だけにサポートされている。新しいアプリケーションでは代わりにvar型をプロパティらに使ってね。」

と、いうわけで既に時代は variant ではなく var にすべきなのでありました。

var

あらゆるデータの型に対する参照が可能なQt新時代のバリアント型にしてECMAScriptとも互換な var 型です。

これからはvariantじゃなくてvarを使いましょう。

雛形・改

GLSL を書きますよ

教材にしているビデオの26:30辺りでShaderEffectへしれっとGLSLがコピペされて登場します^^;

GLSLを知っているか、或いはHLSLでもCgでも良いのでシェーダー言語による基礎的なGPUプログラミングの知識があれば、なんという事の無い頂点シェーダーとフラグメントシェーダーです。

ここ数年の一般的なシェーダプログラムの実行方法ではC++レベルの通常のアプリケーションがOSで実行されるとOpenGLなどを介しGPUドライバーにシェーダープログラムを実行直前にコンパイルさせます。よってQML要素レベルでもGLSLプログラムソースをstringで定義する形式になっています。

f:id:USAGI-WRP:20130103211510p:plain

まだ、実行しても特に先ほどの雛形から見た目の変化はありません。

GPUプログラミング

GPUプログラミングに詳しくない方の為に少しだけ解説すると、プログラマブルな世代の現代的なGPUでは基本要素として2つのプログラム構成部分が必ず用意されています。1つは頂点シェーダー、もう1つはフラグメントシェーダー(≈Microsoft系の世界ではピクセルシェーダーとも呼ばれます)。

GPUは元々CPUだけでは描画しきれない膨大な3Dグラフィックスを描画する為に進化した計算機の部品です。3Dグラフィックスの描画と云うのは、3Dのデータ(≈3Dモデルデータ)を特定のパラメーターで2Dの絵に変換して、それをディスプレイに接続されたフレームバッファーへ書き出す仕事が基本です。

通常一般のディスプレイは2Dですから、2Dの画像データを単に描画するのであれば、座標を指定して画素情報をコピーするだけです。しかし、3Dのデータと云うのは一般に画素ではなく形状を構成する点群(≈頂点群)とその点群をどの様に接続した面で物体が構成されるのかという約束事で構成されており、3Dの世界を2Dのディスプレイに描画する為にはこの様な3Dモデルデータを、どこから見ているのか(≈カメラ)を基にまるで撮影のシミュレーションそのものの様な処理を計算で行わなければなりません。綺麗な計算機画像を得る為には3Dモデルデータも数万個やそれ以上の頂点で構成されていたり、複雑な光線に関する計算が必要となり、とてもとても重たい処理で、CPUだけでやっていてはとてもとても計算時間が必要なものです。

そこで、CPUの様に汎用的に何でもプログラマブルに計算できる訳じゃないけれど、代わりに3Dグラフィックスに関する大量の計算には滅法強いGPUなる部品を作ったのです。

その流れは今でも勿論GPUプログラミングに影響、というかそのものとして受け継がれて居て、大量の3Dモデルデータの頂点群を特定のパラメーター(視点だとか)ではスクリーンの何処に映るのかを計算する頂点シェーダー、スクリーン上に転写された点群からそれら点群により構成される面に色塗りをするフラグメントシェーダーがGPUプログラミングの基本となっています。

現代的なGPUプログラミングでは、今回も用いられるGLSL(≈OpenGL)の他、HLSL(≈Direct3D)、Cg(NVIDIA言語)などC風の文法の専用言語を用います。ちなみにGPGPUGPUの変態的な応用技術であってGPUは本来GPGPUを行う為に設計された訳ではありませんから、GPUで工夫した計算をさせようと、特にDirectComputeやOpenCLやCUDAなどそれ用に更に特殊化されたド変態言語やAPIを用いない限りはいろいろとGPUの特徴(というか癖というか生の仕様が透けて見えるというか)なプログラミングのスキルや知識が少なからず必要です。

です、が、GPUプログラミングの仕組みや概要が分かってしまえばなかなか楽しいものではありますので、他の部分はさておいて突拍子もなくGLSLで割と純粋にGPUプログラミングを楽しめるプラットフォームとしてQt5/Qt Quick 2.0が目の前に現れてくれているのですから、之を期にでも是非少しは遊んで見る事をお勧めします。(実際生のOpenGLDirect3DではGLSLなどのシェーダー言語を書く他にもGPUを使う為のアレコレの初期化やシェーダープログラムのコンパイルなどの制御やそもそものGUIの制御などそれなりに煩わしいものです。)

余談

ちなみに、頂点シェーダーとフラグメントシェーダーに加えて現代のGPUプログラミングでは超ド変態な進化を遂げていて数年前から頂点データ群から別の頂点データ群を生成するジオメトリーシェーダーとかストリームアウトとか、サブディビジョンサーフェイス用にと実装が進んだテッセレーターがどうのとか、まあいろいろともっと進化した機能を使い熟す事も可能な、まあ兎に角ド変態デバイスとその取り扱いへと進化しています。著者もテッセレーターがどうした辺りからGPUをいじらなくなって久しいです。

解説

vertexShader

  1. qt_TexCoord0 に qt_MultiTexCood0 を代入しています。
    1. qt_MultiTexCood0 は attribute highp vec2 で頂点シェーダーに渡されて来る頂点属性の2つ目の値。
  2. gl_Position に qt_Matrix * qt_Vertex を代入しています。
    1. qt_Matrix は uniform highp mat4 でシェーダーに渡されている値。
    2. qt_Vertex は attribute highp vec4 で頂点シェーダーに渡されて来る頂点属性の1つ目の値。

attribute 各種はQMLのバックエンドの仕様に基づいて渡されてくる(はず)。少なくともQMLのShaderEffectを使う分には頂点属性は固定だと思って良い(はず)で、QMLのShaderEffectの頂点属性は { vec4 頂点座標, vec2 テクスチャー座標 } と推論できる。

と、いうわけで、日本語で読み下すと、

  1. テクスチャー座標をフラグメントシェーダーへパイプ。
    1. テクスチャー座標は頂点属性で渡されて居る値を引っ張り出す。
  2. スクリーン座標系へ変換した頂点座標をフラグメントシェーダーへパイプ。
    1. スクリーン座標系への入力頂点座標の変換はシェーダーに共通で渡されて居るバッファーから引っ張り出した4x4行列(≈ここでは3DCG用語で云うならば(ワールド×ビュー×スクリーン)変換行列)。
    2. 頂点の入力座標は頂点属性で渡されて居る値を引っ張りだす。

と、いうわけで、推測ですがQMLの元要素を4頂点の四角の座標+元要素を画像としてテキスチャーで転送されたデータがシェーダーにやってきて居て、変換行列の処理については2D以外等への応用を視野にしていて少なくともこの推測の元では単位行列でも入ってて、名目上の頂点座標処理を記述しつつテクスチャー座標と共に頂点座標をフラグメントシェーダーに出力(パイプ)している、のだと思います。

※実際ビデオを見続けると、そんな様な事の一部始終を解説してくれている。

GLSL tips 1

attribute は頂点シェーダーに渡って来る1つ1つの生の頂点データを変数にバインドする旨のGLSLのキーワード。HLSLで言ったらセマンティクスとかに近い辺り。

uniform は頂点毎などではなくシェーダープログラム全体に共通して渡されるデータを変数にバインドする旨のキーワード。HLSLで言ったらSM5の定数バッファー(HLSLの、だよ。GLSLのconstとかじゃないよ)の取り扱い的な感じ。

varying は頂点シェーダー→フラグメントシェーダーとパイプされるGPU内部での変数を宣言するキーワード。HLSLで言ったらシェーダー間のoutとinネ。

highp は C++風に言ったら long double 的な感じ。GPUネイティブな小数点数型(≠浮動小数点数型、ハードウェアによっては浮動とは限らない)にも精度の異なる型がサポートされていて、C++の float-double-long double みたいに一定の精度を区切りにGPUのハードウェアに依存した精度が lowp-midp-highp に割り当てられる。詳しはこちらを見ると分り易いと思うよ。

mat4 は 4x4 の行列型で、その前に highp とか付けて highp mat4 とか宣言して highp で構成された 4x4 行列の型という事になる。vec4 や vec2 も同様でこちらは4次元と2次元のベクター(≈要素次元N個を表すデータ配列)。

void main() { ... } は関数の定義で、mainは各シェーダーのエントリーポイント的な関数と思って差し支え無い。

fragmentShader

  1. 描画対象領域について2Dテクスチャーサンプラーでシェーダーに渡されたテクスチャー(GPU組み込みのテクスチャーサンプラー機能で要素の値を補完して取得できる)、シェーダーに共通で渡された不透明度を基に、サンプリングされた色に不透明度を掛け算して描画点の色を決定、出力している。
GLSL tips 2

sampler2D はGPUの内部機能の1つテクスチャーサンプラーを扱う型の宣言。テクスチャーサンプラーはC++風に言えば { return texture.get_pixel_rgba(texture_coordinate); } 的なアルゴリズムの関数でテクスチャーから要素を得るためのGPU組み込みの仕掛け、機能、と思えばOK。実際には一般に補完機能付きのアルゴリズムを用いる。

float は vec4 や vec2 風に言えば vec1 、つまり単一要素、スカラー値の型名です。

QMLからGLSLのuniformへ値を渡す

ビデオの29:50頃のコードでQMLからGLSLのuniformへと値を渡す手法についてデモを行なっています。

f:id:USAGI-WRP:20130103225431p:plain

oh... わたしのアイコン使うんじゃなかったかしら…orz

解説

ShaderEffect::vertexShader はすっぱり無くなって居ますが、先程の内容がデフォルトなので無くても同様だったという訳。

さて、肝心のこの状態の内容ですが、先ずShaderEffect要素にQMLでユーザー独自のプロパティを追加して値をセットしてあります。

次に、ShaderEffect::fragmentShader のフラグメントシェーダーの定義中で uniform highp で先ほど定義したプロパティ名と同じ名前の変数を宣言しています。

これだけで QML → フラグメントシェーダー の値の受け渡しが出来ています。簡単で良いですね!

GLSL tips 3

GLSL(HLSLでもそうですが)では、基礎的な数学関数が言語組み込みのAPIで利用できます。 sin は正弦を求める御馴染みの三角関数ですね。今回は引数が float ではなく vec2 ですが、ベクターの要素毎に sin を求めたベクターが出力されるオーバーロードがあると思って差し支えありません。

アニメーションの適用

ぎゅうううんぎゅうううん…私のアイコンがー…orz

解説

ShaderEffectにQMLでtimeプロパティを追加しています。フラグメントシェーダーではこのtimeに依存する様に先ほどのsin関数の中を書き換えてあげたのみ。

レンダリングはQt5がよしなに回してくれて居るので再描画毎にシェーダーに渡るプロパティtimeの値が取得され、その値は NumberAnimation によって動的に生成されており、アニメーション(=時刻に応じた描画内容の変化)が実現できて居ます。

NumberAnimation on time

NumberAnimation on time は NumberAnimation なる関数オブジェクト的なQML要素をtime変数にバインドして居ます。

私のソースの書き方だとちょっとややこしいかもしれませんが、直前の property real time からの続きとしての定義ではなくて、 property real time はそれだけで値の定義は無いが変数の宣言としては完結しています(C++で言えば int value = 123; と定義を伴わずに int value; と宣言のみと言う事)。

NumberAnimation は ShaderEffect の単に子要素としてQMLで定義し、 on time と要素宣言に追加してある事で親要素の time の内容としてバインドさせて居ます。

NumberAnimation

QML要素のプロパティをアニメーションさせる事に特化した要素で、値を from から to へと評価時刻により変えて出力します。厳密な範囲としてはfromもtoも共に生成される値に含まれます(C++のrangeのbegin-endとは異なる)。

プロパティは from と to だけの様に仕様をチラ見しただけでは思えますが、きちんと SmoothedAnimation と SpringAnimation からの継承についても触れられて居ます。

duration は to から from への変化をマッピングする時間間隔を指定します。単位は ms (ミリ秒)。

loops は アニメーションの繰り返し方の指定で、デフォルトは 1 、つまり1回だけアニメーションしてループはしない。Animation.Infiniteについては後述。

from と to が含まれるのか確認したい?

こんな具合で小細工すれば、from の 0 も to の 1 も含まれてコンソールに出力される事を確認できます。

Animation.Infinite

Animation.Infinite は NumberAnimation の基底型でもあるAnimationに定義された定数。

loops に Animation.Infinite がセットされると stop() が明示的に呼び出されるまでアニメーションを繰り返してくれる。

非表示のShaderEffectの元要素を定義できる ShaderEffectSource

ビデオを続けると31:30頃にShaderEffect専用にそれ自体は表示しないQML要素の定義方法が紹介されます。

解説

ShaderEffectSource

ShaderEffect の source とする要素を定義するQML要素。動作はソースを見たまま予想通りで分り易いですね。

今回の結び

ShaderEffectを使うとQMLだけでもリッチなエフェクトを簡単に実装できてしまいますね。GUI ToolkitとしてもUI定義としてもECMAScriptベースのアプリケーションエンジンとしても強力なQMLにGPUパワーも少なくともエフェクターとしては、驚くべき事にバックエンドのC++コードに必要無ければ全く触れずとも多彩な表現力を発揮できる、と。

ビデオをもう少しだけ進めると次は "Drawing with JavaScript, based on HTML5 Canvas API" だそうで。明日も楽しみですね。…とは言え、そろそろC++コード書かないとなかのひとがうぐぅ…って感じではありますけれど・x・;