Shading by Cube Mapping

拡散反射光と鏡面反射光の計算結果をそれぞれテクスチャーに保存しておいてキューブマッピング + マルチテクスチャーでレンダリングしているだけ。キューブマッピングで使うテクスチャーは自作ツールで生成した。シェーダーは以下の通りすごく単純。特定の条件下では高速化の手法として結構有効かもしれない。

// vertex shader
#version 120

varying vec3 N;

void main()
{
	gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
	N = gl_NormalMatrix * gl_Normal;
}
// fragment shader
#version 120

uniform samplerCube DiffuseMap;
uniform samplerCube SpecularMap;
uniform vec3 DiffuseMaterial;
uniform vec3 SpecularMaterial;

varying vec3 N;

void main()
{
	vec3 kd = textureCube(DiffuseMap, N).rgb;
	vec3 ks = textureCube(SpecularMap, N).rgb;
	vec3 color = DiffuseMaterial*kd + SpecularMaterial*ks;
	gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);
}

TMP : Template Meta Programming

Template Metaprograms
前回に引き続いて再びC++Reportから。今度はテンプレートメタプログラミング(TMP)の記事を読んでみた。

TMPとは、簡単にいうと、テンプレートを特殊化するプロセスを応用してコンパイルと同時に実行結果を生成するテクニックといっていいのだろうか。コンパイラをあたかもインタプリタのように振る舞わせることで、通常のマクロ展開よりも遥かに強力なプリプロセッサ機能を利用できるようになる。
TMPでは条件分岐やループなどの制御構造をテンプレートの特殊化と再帰呼び出しで表現する。この意味においてTMPは関数型言語と良く似てるといえるかもしれない。例えば、ユークリッドの互除法をTMPで書くと次のようなプログラムになる。

template <unsigned int a, unsigned int b>
struct which_t {
  enum {
    greater = (a > b) ? a : b,
    less = (a < b) ? a : b
  };
};

template <unsigned int a, unsigned int b>
struct gcd_t {
  enum {
    p = which_t<a, b>::greater,
    q = which_t<a, b>::less,
    value = gcd_t<q, p % q>::value
  };
};

template <unsigned int a>
struct gcd_t<a, 0> {
  enum {
    value = a
  };
};

このプログラムでは、アルゴリズムの基本となるループをテンプレート(gcd_t)の再帰呼び出しによって記述し、終了条件をこのテンプレートを特殊化することで定義している。計算結果を取得するには同じく引数を渡してテンプレートを特殊化すればいい。

std::cout << gcd_t<51, 136>::value << std::endl;

テンプレートの特殊化はコンパイル時に行われるので上のコードは実際は次のコードと等価となる。

std::cout << 17 << std::endl;

このようにTMPを使うと本来実行時に発生する計算コストをコンパイル時に移動させることができるので、パフォーマンスチューニングのテクニックとして大変有効に思われる。しかし、その適用範囲は期待する程広くはなさそうだ。本記事ではバブルソートを例に関数版とTMP版の2種類の実装を用意して性能評価を行っていて、関数呼び出しのコストが発生しない分、TMP版は関数版よりも予想通り速い結果となっている。しかし、入力の大きさが極小さい場合を除くと両者の間にはさほど大きな性能差を認められない。結局のところ、TMPによるチューニングはあくまで奥の手にすべきであって肝心なのはやはりアルゴリズムの性能なんだということだろう。

ちなみにこの記事の初出は1995年。C++の標準仕様が初めてISOに承認されるちょうど3年前になる。こうしてブログを書くついでに昔の記事を掘り起こしてみると、標準化に向けて活発だった当時のC++コミュニティの勢いが伺い知れてなんだか感慨深い。

Traits : C++ template technique

今更ながらC++を学んでいる。単なるOOP機能を備えたCとしてではなくC++固有の機能をもっと活用しようと思ったのがきっかけ。巷ではリーナス・トーバルズ氏のようにC++をボロクソにけなす人達がいるけれど、Cは基本的なデータ構造を扱う標準ライブラリが存在しないのがやはり苦しい。なので、複雑な言語仕様や意味不明なコンパイルエラーに悩みつつも、自分は当分の間 C++を使い続けていくんだろうと思う。

§

最近になって Traits という便利なテクニックを知った。Traitsとは、C++のテンプレート機能を活用してデータ型に関する処理を実行時からコンパイル時に移行させるコーディングテクニック。Traits 自体の説明は C++ Report の過去記事「Traits: a new and useful template technique」が詳しいのでそちらを参照して欲しい。この記事では、STLで採用された様々な実装例を挙げて Traits の導入動機と活用の仕方が詳しく解説されている。

さて、新しく学んだテクニックを応用すべく「Beautiful Code」で紹介されていた正規表現マッチャをC++で書き直してみた。オリジナルとの大きな違いは、ポインタを直接操作する代わりにイテレータを使用している点とワイド文字列に対応した点。このワイド文字列の対応にあたって、次のような re_metachar_trait というクラスを用意した。マッチングの中では、‘*’ や ‘?’ などパターン文字列中のメタ文字を判別する際、文字リテラルを直接指定する代わりに re_metachar_trait のメソッドが返す値を利用している。

template <>
struct metachar_trait<char> {
  typedef char char_type;
  static char_type hat() { return '^'; }
  static char_type dollar() { return '$'; }
  static char_type star() { return '*'; }
  static char_type plus() { return '+'; }
  static char_type question() { return '?'; }
  static char_type dot() { return '.'; }
  static char_type eos() { return '\0'; }
};

template <>
struct metachar_trait<wchar_t> {
  typedef wchar_t char_type;
  static char_type hat() { return L'^'; }
  static char_type dollar() { return L'$'; }
  static char_type star() { return L'*'; }
  static char_type plus() { return L'+'; }
  static char_type question() { return L'?'; }
  static char_type dot() { return L'.'; }
  static char_type eos() { return L'\0'; }
};

このようにデータ型固有の情報を Trait クラスに委ねることで複数のデータ型に対応するための煩雑な条件分岐をマッチング処理から排除することができた。ただし、出来上がったソースコードを眺めてみると余計なキーワードだらけでとても”Beautiful”とは言えない始末。オリジナルコードが本当に美しいのかどうかはともかく、その特徴だった簡潔さが失われてしまってあんまり良い例ではないよね。

マルチスレッドプログラミング

最近、マルチスレッドを使ったプログラムを書くことがあって、これを機にオライリーの「Pthreadsプログラミング」を読んでみました。本書はタイトルの通りPOSIXスレッドAPIの解説書ですが、マルチスレッドプログラミング自体の概要を押さえるのにも役に立つ内容ではないでしょうか。おかげでこれまで曖昧にしていた知識の補完ができて大変参考になりました。

  • プロセスとスレッドの違い
    • スレッドコンテキストとは、すなわちスタックポインタとプログラムカウンタ等のレジスタセットのこと。
    • このコンテキストを切り替えることで、メモリを共有しながら同じプログラム内の異なる命令を並行して実行できる。
  • 並列処理と並行処理の違い
    • 並列処理とは、異なるタスクを同時に実行すること。
    • 並行処理とは、異なるタスクを任意の順番で実行すること。
    • したがって、並列ならば並行は成立するがその逆は成立しない。
  • マルチスレッドプログラムの設計モデル
    • ボス・ワーカーモデル、ピアモデル、パイプラインモデル。
  • Pthreadsの実装方法の種類
    • ユーザースレッドとカーネルスレッド、2レベルスケジューリングスレッド。

それでも、分かったのはせいぜい抽象的な概念のみ。実装レベルに踏み込むと途端に理解があやふやです。試しにid:naoyaさんのスケジューリング・スレッドに関する過去記事を読んでみましたが、頭が追いつかず途中で息切れしてしまいました。まずはマルチスレッドを利用した小さいサーバーでも書いてみましょうか。実際に手を動かしてみた方が理解が深まりそうですし。

ちなみに、MacOSX(というかDarwin)はというと、LinuxのNPTLと同じくユーザースレッドとカーネルスレッドが1対1で対応する1:1モデル。MacOSXは複数のスレッディングAPIを提供していて、各APIの関係は下記のような階層構造になっています。

※ADC Technical Note TN2028より。
この階層図を見て分かるとおり、Machスレッドの上位にPOSIXスレッドが位置し、そのPOSIXスレッドの上位にCoccoa・NSThreadやCarbon・MPタスク等が位置しています。このように、Machスレッドと上位APIの間にPOSIXスレッドを挟んだ設計のおかげで、UnixソフトウェアとMacアプリケーションが同じ環境下で動作可能な仕組みになっているのですね。なお、参考にした資料が、MacOSXがリリースされて間もない頃の内容で、現在のアーキテクチャがどうなっているかは分かりません。どなたかご存じの方がいらっしゃったらご指摘いただけると幸いです。


参考資料

Pthreadsプログラミング

Pthreadsプログラミング