OpenGL や GLSL の互換性

Direct3D の HLSL コンパイラは Microsoft が用意しており、共通のバイトコードを
生成しています。
実行時はさらにドライバが GPU 毎のネイティブコードに最適化&変換を行っています。

一見二度手間ですが、最初の最適化は共通バイトコードへの変換時に済んでいます。
時間のかかる巨大なシェーダーでも事前に変換しておくことが可能だし、コンパイラ部の
最適化の恩恵はすべての GPU で受けられます。
ドライバも共通コードからの変換だけ行えばよいので、互換性も比較的維持しやすいのでは
ないかと考えられます。

その代わり一度バイナリにしてしまうと、コンパイルした時点のコンパイラの能力である程度
固定されます。あとからコンパイラが強化される可能性もあるし、GPU が ShaderModel 5.0
対応になっても、3.0 向けにコンパイルしたシェーダーで能力を出し切れるとは限りません。

OpenGL の GLSL の場合 API が受け取るのは生のソースコードです。
コンパイルそのものが GPU ドライバの役目となっていて、中間の共通コードがありません。
動的にコンパイルするメリットは常にハードに最適なプロファイルを選べることです。

逆にデメリットとしては、コンパイル時間がかかるのではないかとの懸念もありますが
もっと別の問題もあるようです。
今まで GeForce で組み立ててきたコードを RADEON で走らせてみました。
環境は下記の通り。

GeForce 9800GT 190.62 (GL 3.1 GLSL 1.4) WHQL
GeForce GTX280 190.57 (GL 3.2 GLSL 1.5)
RADEON HD 4760 Catalyst 9.9 (GL 3.1 GLSL 1.4)

● Version 指定

RADEON の GLSL では「#version」はソースコードの先頭にないとエラーになります。

・#version の前にプリプロセッサ命令があるだけでもだめ。
・glShaderSource() に複数のソースコードを渡している場合、最初のコードの先頭でないとだめ。2番目以降のソースコードには記述できない。

この挙動は OpenGLES 2.0 の GLSL と同じです。また GLSLangSpec.Full.1.40.05.pdf
を見ても、#version の前に許されるのはコメントとホワイトスぺースのみと記載されています。
こちらの動作の方が正解でした。

ただプリプロセッサ命令も許されていないので、複数のターゲット間で互換性ととる場合に
version 指定を分岐できないのは不便です。C 言語側の Shader Loader 部分に手を加えて、
独自のプリプロセッサを追加するなどの対策が必要かもしれません。

● precision 指定

OpenGL ES の GLSL から取り入れた仕様として precision 指定があります。
宣言時に変数が必要とする精度のヒントを与えます。
highp, middlep, lowp の 3段階ありますが

・GeForce は in/out 宣言の前に必要
・RADEON は in/out 宣言の後ろ書かないとエラー

RADEON の場合必ず何らかの精度指定が必須で、個別指定かまたはデフォルトの
precision 行が必要です。GeForce は無くても通ります。
とりあえず最初にデフォルト宣言をしておけばどちらでも通ります。
細かく個別に宣言をしている場合は注意。

precision highp float;

ドキュメントを見る限り RADEON の方が正しいようです。
全体的に GeForce の方が判定が緩く RADEON の方が厳密になっています。

GPU のドライバ毎にコンパイラの仕様が異なっている可能性を考えると、
Direct3D + HLSL のように共通化されている方が楽だと思います。
慣れるまではこれらの互換性の維持に苦労しそうです。

●OpenGL 3.1

現段階で OpenGL 3.2/GLSL 1.5 に対応しているのは GeForce 190.57 だけです。
RADEON で試すにあたって OpenGL 3.1/GLSL 1.4 で動作するように修正しました。

GeForce の場合、最初に wglCreateContext() で作った Context がすでに
OpenGL 3.x に対応していました。
wglCreateContextAttribsARB() で作り直さなくても動作します。
RADEON の場合 OpenGL 2.1 だったので、wglCreateContextAttribsARB() が必要です。
でもシェーダーバージョンは同じ。

// GeForce 190.57
// wglCreateContext()
GL VERSION: 3.2.0
GL RENDERER: GeForce GTX 280/PCI/SSE2
GL VENDOR: NVIDIA Corporation
GL SHADING_LANGUAGE_VERSION: 1.50 NVIDIA via Cg compiler
 ↓
// wglCreateContextAttribsARB()
GL VERSION: 3.1.0
GL RENDERER: GeForce GTX 280/PCI/SSE2
GL VENDOR: NVIDIA Corporation
GL SHADING_LANGUAGE_VERSION: 1.40 NVIDIA via Cg compiler

// GeForce 190.62
// wglCreateContext()
GL_VERSION: 3.1.0
GL_RENDERER: GeForce 9800 GT/PCI/SSE2
GL_VENDOR: NVIDIA Corporation
GL_SHADING_LANGUAGE_VERSION: 1.40 NVIDIA via Cg compiler
 ↓
// wglCreateContextAttribsARB()
GL_VERSION: 3.1.0
GL_RENDERER: GeForce 9800 GT/PCI/SSE2
GL_VENDOR: NVIDIA Corporation
GL_SHADING_LANGUAGE_VERSION: 1.40 NVIDIA via Cg compiler
// RADEON 9.9
// wglCreateContext()
GL_VERSION: 2.1.8918
GL_RENDERER: ATI Radeon HD 4600 Series
GL_VENDOR: ATI Technologies Inc.
GL_SHADING_LANGUAGE_VERSION: 1.40
 ↓
// wglCreateContextAttribsARB()
GL_VERSION: 3.1.8918
GL_RENDERER: ATI Radeon HD 4600 Series
GL_VENDOR: ATI Technologies Inc.
GL_SHADING_LANGUAGE_VERSION: 1.40

● Extension の判定

GeForce 190.57 を Version 3.1 で作り直した場合のはまりです。
Extension の判定で glGetString( GL_EXTENSIONS ) を参照していましたが、
Context を作り直すとエラーになります。
ドキュメントをよく見ると glGetString( GL_EXTENSIONS ) は古い仕様で、
OpenGL 3.x の場合は

glGetIntegerv( GL_NUM_EXTENSIONS, .. )
glGetStringi( GL_EXTENSIONS, .. )

を用いるのが正しいやり方のようです。
WGL_CONTEXT_FLAGS_ARB を 0 にしても
WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB がセットされてしまうことが
原因のようです。
190.62 では WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB を指定しない限り
大丈夫でした。

互換性周りはまだ慣れてないせいか、または出来るだけ共通で動かそうと欲張っている
せいか苦労しています。
Direct3D の場合問題になる互換性はハードウエア能力の差でした。
その後 Direct3D 10 では完全に足並みが揃って、GeForce も RADEON もほとんど
区別せずに同じシェーダーが動くようになっています。
OpenGL ではハードウエア能力の違いよりも、まだまだドライバの差が表面化している感じです。
API 仕様が決まっても、安定するまでしばらく時間がかかるのかもしれません。

関連エントリ
OpenGL の同期と描画速度の測定
OpenGL のはじめ方 (2) wgl
OpenGL 3.2 GeometryShader をもう少し使ってみる
OpenGL GLSL のバージョン
OpenGL 3.2 の GeometryShader
OpenGL のはじめ方