15. その他

15.1. glFinish() と glFlush()

3DS では、glFinish()glFlush() に違いはなく、どちらも glFlush() を呼び出したときの処理を行います。

コード 15-1. glFinish()、glFlush() の定義
void glFinish(void);
void glFlush(void);

15.2. OpenGL ES との違い

ほとんどの機能と関数は OpenGL ES の仕様と同じですが、一部の機能や関数には実装されていない、もしくは機能が制限されているなどの違いがあります。

表 15-1. 機能ごとの相違点

機能

相違点

GL_ALPHA_TEST

アルファテストの制御には予約ユニフォームを使用します。

GL_CLIP_PLANEi

クリッピングの制御には予約ユニフォームを使用します。

GL_COLOR_LOGIC_OP

論理演算は OpenGL ES 2.0 では廃止されていますが、3DS では OpenGL ES 1.1 相当の機能を実装しています。

GL_DITHER

ディザリングには対応していません。

GL_FOG

フォグの制御には予約ユニフォームを使用します。フォグ係数は視点からの距離ではなく、ウィンドウ座標系でのデプス値を入力にとり、参照テーブルで設定します。

GL_INDEX_LOGIC_OP

インデックスカラーには対応していません。

GL_LIGHTING

ライティングの制御には予約ユニフォームを使用します。3DS のライティングはフラグメント単位で行われます。

GL_POLYGON_OFFSET_FILL
GL_POLYGON_OFFSET_LINE
GL_POLYGON_OFFSET_POINT

ポリゴンオフセットの制御には GL_POLYGON_OFFSET_FILL のみが対応しています。

GL_SAMPLE_COVERAGE
GL_SAMPLE_ALPHA_TO_COVERAGE

マルチサンプリングには対応していません。

GL_SCISSOR_TEST

シザーテストはラスタライズのサブプロセスとして実行されます。

GL_TEXTURE_2D
GL_TEXTURE_CUBE_MAP

テクスチャユニットの制御には予約ユニフォームを使用します。

表 15-2. 関数ごとの相違点

関数

相違点

シェーダ関連

glBufferData()

usage には GL_STATIC_DRAW のみが指定可能です。

glCompileShader()

実装されていません。

glCreateProgram()

シェーダオブジェクトとは独立した名前空間(13 bit)を使用します。

glCreateShader()

プログラムオブジェクトとは独立した名前空間を使用します。

glDrawArrays()
glDrawElements()

GL_POINTSGL_LINESGL_LINE_STRIPGL_LINE_LOOP には対応していません。ポイントおよびラインの描画にはジオメトリシェーダを使用する必要があります。

glGetProgramInfoLog()

実装されていません。

glGetShaderInfoLog()

実装されていません。

glGetShaderSource()

実装されていません。

glGetShaderPrecisionFormat() 実装されていません。

glLineWidth()

実装されていません。

glReleaseShaderCompiler()

実装されていません。

glShaderBinary()

binaryformat には GL_PLATFORM_BINARY_DMP のみが指定可能です。

glShaderSource()

実装されていません。

glValidateProgram()

呼び出しても何も行われません。

glVertexAttribPointer()

type GL_FIXEDGL_UNSIGNED_SHORT を指定することができません。データポインタは、typeGL_FLOAT のときは 4 Byte、GL_SHORT のときは 2 Byte のアライメントになっていなければなりません。引数指定による正規化には対応していません。

ビューポート関連

glViewport()

x , y に負の値を指定することができません。

テクスチャ関連

glActiveTexture()

GL_TEXTURE3 を指定するとエラーを生成します。

glCompressedTexImage2D()

幅と高さは 2 のべき乗(16 ~ 1024)で指定しなければなりません。

glCompressedTexSubImage2D()

実装されていません。

glGenerateMipmap()

実装されていません。

glTexImage1D()
glTexSubImage1D()

参照テーブルのロードに使用します。1 次元テクスチャには対応していません。

glTexImage2D()

幅と高さは 2 のべき乗(8 ~ 1024)で指定しなければなりません。

glTexSubImage2D()

実装されていません。

レンダーバッファ関連

glClear()

インデックスカラーには対応していません。ステンシルバッファはデプスバッファと同時にクリアする必要があります。シザーテストとマスキング処理の影響を受けません。

glCopyPixels()

実装されていません。

glDrawBuffer()

実装されていません。

glDrawPixels()

実装されていません。

glFramebufferRenderbuffer()

ステンシルバッファがデプスバッファと複合しているため、ステンシルバッファのアタッチメントには GL_DEPTH_STENCIL_ATTACHMENT を指定しなければなりません。

glFramebufferTexture2D()

アタッチメントに対応しているのはカラーのみです。

glPixelStorei()

実装されていません。

glReadBuffer()

実装されていません。

glRenderbufferStorage()

指定可能なフォーマットが制限されています。ステンシルバッファを利用することができるフォーマットは GL_DEPTH24_STENCIL8_EXT のみです。

glSampleCoverage()

実装されていません。

glStencilFuncSeparate()
glStencilOpSeparate()
glStencilMaskSeparate()

実装されていません。

ブレンディング関連

glBlendEquation()
glBlendEquationSeparate()

OpenGL ES 2.0 では指定可能となっている GL_LOGIC_OP には対応していません。

glBlendFunc()
glBlendFuncSeparate()

ディスティネーションに対しても GL_SRC_ALPHA_SATURATE を指定することができます。

その他

glFinish()

glFlush() と同じ処理を行います。

glFlush()

glFinish() との間に処理の違いはありません。

glHint()

実装されていません。

glPolygonOffset()

factor の設定は無視されます。

15.3. バッファアクセスのパフォーマンス改善方法

カラーバッファ、デプスバッファ、ステンシルバッファのいずれかを使用しない場合は、各バッファの機能を明示的に無効にして不要な処理を減らすことができます。ただし、3DS のデプスバッファとステンシルバッファは同一のバッファを使用するため、デプスバッファまたはステンシルバッファのどちらかにアクセスするような設定は両方にアクセスする設定と同じパフォーマンスになります。

各バッファにアクセスする条件は以下のようになっています。無駄なアクセスが発生しないように、以下の条件が成立しないように注意してください。

15.3.1. カラーバッファ への Write アクセス

glColorMask() によりいずれかの成分が GL_TRUE に設定されている場合はアクセスが発生します。

15.3.2. カラーバッファへの Read アクセス

カラーバッファへの Write アクセスが発生し、かつ以下の条件のいずれかを満たしている場合はアクセスが発生します。

  • GL_BLENDglEnable() によって有効に設定されている。
  • glColorMask() による設定が、すべての成分に対して同じ設定になっていない。
  • GL_COLOR_LOGIC_OPglEnable() によって有効に設定されている。

 

15.3.3. デプスバッファへの Write アクセス

GL_DEPTH_TESTglEnable() によって有効に設定されている、かつ glDepthMask()GL_TRUE が設定されている場合はアクセスが発生します。

15.3.4. デプスバッファへの Read アクセス

GL_DEPTH_TESTglEnable() によって有効に設定されている場合はアクセスが発生します。

15.3.5. ステンシルバッファへの Write アクセス

GL_STENCIL_TESTglEnable() によって有効に設定されている、かつ glStencilMask() でのマスキング設定が 0 でない場合はアクセスが発生します。

15.3.6. ステンシルバッファへの Read アクセス

GL_STENCIL_TESTglEnable() によって有効に設定されている場合はアクセスが発生します。

15.4. CPU パフォーマンスの改善方法

以下の点に注意してアプリケーションを実装することで、処理速度が向上する場合があります。

  • なるべく多くの頂点シェーダアセンブラをリンクして使用してください。複数のシェーダアセンブラをリンクして 1 つのシェーダバイナリとして生成することができます。シェーダオブジェクトを切り替える際、異なるシェーダバイナリにリンクされたシェーダオブジェクトよりも、同じシェーダバイナリにリンクされたシェーダオブジェクトに切り替える方が処理は軽くなります。
  • glGetUniformLocation() で取得したユニフォームのロケーション値をアプリケーションで保持し、繰り返し使用してください。ロケーション値は glLinkProgram() の呼び出し後に決定され、再度 glLinkProgram() を呼び出すまで変更されることはありません。
  • 呼び出されるごとに区切りコマンドを生成する nngxSplitDrawCmdlist() を不要なタイミングで呼び出さないでください。例えば、nngxTransferRenderImage() を呼び出すと関数内で区切りコマンドが生成されますので、直後に nngxSplitDrawCmdlist() を呼び出すのは無駄なコマンドを生成することになります。
  • 頂点属性情報をシェーダに送るときには、なるべく頂点バッファを使用してください。頂点バッファを使用しない場合は CPU が 3D コマンドバッファに頂点データを蓄積していくため、頂点バッファを使用する場合に比べて CPU での処理が著しく増加します。
  • 特定の描画パスなどで同じテクスチャや頂点バッファを繰り返し使用する場合は、テクスチャコレクションや頂点ステートコレクションを使用してください。テクスチャのバインドや頂点アレイの設定を一括して行うことで、関数の呼び出しコストを減らすことができます。
  • 同じシェーダオブジェクトを異なるユニフォームの設定で実行する場合は、同じシェーダオブジェクトをアタッチさせた複数のプログラムオブジェクトに異なるユニフォームの設定をしておき、プログラムオブジェクトを切り替えて実行する方が、1 つのプログラムオブジェクトで多数のユニフォームの設定切り替えを行うよりも処理が軽くなる場合があります。これは、ユニフォームの値がプログラムオブジェクトごとに保存されているためです。
  • 参照テーブルのオブジェクトは頻繁に削除・再生成を行わないでください。これは、glTexImage1D() で参照テーブルのデータがロードされると、参照テーブルのオブジェクトの使用時にハードウェア内部フォーマットへの変換が行われるためです。

 

15.5. 頂点バッファの使用について

頂点バッファを使用した場合は GPU のジオメトリ処理パイプラインが頂点データをロードしますが、使用しない場合は CPU が頂点インデックスアレイに従って頂点アレイの並び替えを行い、すべての頂点データを 24 ビットの浮動小数点数値に変換してからコマンドバッファに詰め込みます。この処理は CPU にとって著しく負荷が高い上に、GPU のジオメトリ処理パイプラインによるデータロードの効率も低下してしまいます。また、より大きなコマンドバッファのサイズが必要となります。頂点データをコマンドバッファに詰め込む場合、すべての頂点データは xyzw の 4 コンポーネント×24 ビット浮動小数点数に変換されますので、頂点数×頂点属性数×12 バイトのサイズが必要となります。

頂点バッファを VRAM に配置した場合は、メインメモリ(デバイスメモリ)に配置した場合に比べて高速に処理することができます。VRAM とメインメモリに分けて配置した場合の速度は、メインメモリに配置した場合の速度と同じです。

15.5.1. 頂点アレイのデータ構成

頂点アレイには、複数の頂点属性を含んだ構造体の配列として頂点データを配置するインターリーブドアレイと、1 つの頂点属性の配列として頂点データを配置する独立アレイの 2 種類のデータ構成があります。

頂点バッファを使用する場合、頂点アレイは独立アレイよりもインターリーブドアレイにした方が頂点データを読み込む効率が良くなります。頂点データの読み込み処理時間自体は、そのあとに行われる頂点シェーダやラスタライズなどの処理時間により隠蔽されることが多いのですが、頂点バッファをメインメモリに配置している場合は、データの読み込み効率を良くすることでアクセス負荷を減らすことができるため、結果として処理の高速化に貢献できる場合があります。

15.6. 各種バッファの先頭アドレスの取得

テクスチャオブジェクト、頂点バッファオブジェクト、レンダーバッファオブジェクトの各オブジェクトのために確保されているデータ領域のアドレスを取得することができます。

取得したアドレスはすべて、GPU が直接アクセスを行う領域のアドレスです。ドライバが生成するコピー領域のアドレスを取得することはできません。

コード 15-2. 各種バッファの先頭アドレスの取得
void glGetTexParameteriv(GLenum target, GLenum pname, GLint* params);
void glGetBufferParameteriv(GLenum target, GLenum pname, GLint* params);
void glGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params);

テクスチャのアドレスは、glGetTexParameteriv()pnameGL_TEXTURE_DATA_ADDR_DMP を指定して呼び出すことで取得することができます。

頂点バッファのアドレスは、glGetBufferParameteriv()pnameGL_BUFFER_DATA_ADDR_DMP を指定して呼び出すことで取得することができます。

レンダーバッファのアドレスは、glGetRenderbufferParameteriv()pnameGL_RENDERBUFFER_DATA_ADDR_DMP を指定して呼び出すことで取得することができます。

これらの関数は、pname に渡す値によってさまざまな情報を取得することができます。取得することのできる情報については、関数リファレンスを参照してください。

15.7. 各種データのロードサイズ

GPU が、頂点バッファやテクスチャ、コマンドバッファからデータをロードするときの、ロード単位のバイトサイズについて説明します。

15.7.1. 頂点バッファ

頂点インデックスの並びによって、頂点バッファからのロード単位のバイトサイズが変化します。

glDrawElements() の場合、インデックスアレイから 16 個単位でロードされた頂点インデックスをソートし、ソートされた頂点インデックスの順に頂点アレイからデータをロードします。このとき、頂点インデックスが連続する場合は頂点アレイからデータが連続して読み出されます。

glEnableVertexAttribArray() で有効となっている頂点属性が格納されている頂点アレイからデータをロードするとき、glVertexAttribPointer() で指定された情報により、ドライバがインターリーブドアレイ(複数の頂点属性を 1 つにまとめた頂点アレイ)からのロードであると判断した場合はインターリーブドアレイ単位で複数の頂点属性をまとめてロードします。

頂点アレイから連続してデータをロードする場合は、最大 256 バイトのバースト読み出しが行われます。256 バイトを超えた場合はそこで一度区切ってから、続きのデータをロードします。不連続なデータをロードする場合でも、最低でも 16 バイト単位でデータが読み出されます。

glDrawArrays() の場合は、0 からの連番で作成されたインデックスアレイを使用する場合と同等の処理を行います。

15.7.2. テクスチャ

テクスチャのフォーマットによって、ロード単位のバイトサイズが変化します。下記の情報は、テクスチャキャッシュにヒットせずに VRAM から転送される際のバイトサイズです。

表 15-3. テクスチャフォーマットとロード単位の対応

format

type

バイトサイズ

GL_RGBA
GL_RGBA_NATIVE_DMP

GL_UNSIGNED_BYTE

128

GL_UNSIGNED_SHORT_5_5_5_1

64

GL_UNSIGNED_SHORT_4_4_4_4

64

GL_RGB
GL_RGB_NATIVE_DMP

GL_UNSIGNED_BYTE

96

GL_UNSIGNED_SHORT_5_6_5

64

GL_LUMINANCE_ALPHA
GL_LUMINANCE_ALPHA_NATIVE_DMP

GL_UNSIGNED_BYTE

64

GL_UNSIGNED_BYTE_4_4_DMP

32

GL_LUMINANCE
GL_LUMINANCE_NATIVE_DMP

GL_UNSIGNED_BYTE

32

GL_UNSIGNED_4BITS_DMP

16

GL_ALPHA
GL_ALPHA_NATIVE_DMP

GL_UNSIGNED_BYTE

32

GL_UNSIGNED_4BITS_DMP

16

GL_HILO8_DMP
GL_HILO8_DMP_NATIVE_DMP

GL_UNSIGNED_BYTE

64

GL_ETC1_RGB8_NATIVE_DMP

-

128

GL_ETC1_ALPHA_RGB8_A4_NATIVE_DMP

-

32

15.7.3. コマンドバッファ

コマンドバッファは 128 バイト単位でロードされます。

15.8. 特定のピクセルにブロック状のノイズが描画される

3DS のフレームバッファでは、ピクセルデータはブロックアドレスと呼ばれる、4×4 ピクセルを 1 ブロックとするブロック単位で処理されています。また、フレームバッファのキャッシュもブロックアドレスで管理されています。glFinish()glFlush()glClear() を呼び出したときや、フレームバッファに関する GPU ステート(NN_GX_STATE_ FRAMEBUFFERNN_GX_STATE_FBACCESS)をバリデーションしたとき、nngxSplitDrawCmdlist() でコマンドリストが区切られたときなどにキャッシュのタグ情報がクリアされます。キャッシュのタグ情報がクリアされると、キャッシュタグは初期値 0x3FFF で初期化されます。そのため、キャッシュタグをクリアした直後の描画で特定のブロックアドレス(0x3FFF)にあたるピクセルを描画しようとしても、キャッシュタグの初期値とピクセルのブロックアドレスが等しいためにキャッシュにヒットしたと誤判定されてしまい、その結果、正しくない色がピクセルに適用されてしまいます。

ブロックアドレスは、フレームバッファ(カラーバッファ、デプスバッファ、およびステンシルバッファ)の先頭アドレスから 16 ピクセル単位で、0 から連番で割り当てられます。その際、GPU の描画フォーマットで配置されたデータに対してアドレスが割り当てられるため、描画イメージのピクセルの位置とブロックアドレスの対応は、ブロックモードがブロック 8 モードの場合とブロック 32 モードの場合で異なります。

この問題はブロックアドレス 0x3FFF に割り当てられたピクセルに起こるため、フレームバッファの総ブロック数が 0x3FFF に満たない場合、つまり、フレームバッファの総ピクセル数が 0x3FFF×16 ピクセル(262128 ピクセル。正方形ならば 512×512 ピクセル)以下の場合は発生しません。また、カラーバッファ、デプスバッファ、ステンシルバッファのいずれに対してもリードアクセスが発生しない場合にも、この問題は発生しません。

補足:

キャッシュのタグ情報のクリアは、GPU のレジスタ 0x0110 に 1 を書き込むことでも行われます。

GPU のレジスタに直接アクセスする方法や GPU ステート、ブロックモード、フレームバッファへのリードアクセスの制御については、「3DS プログラミングマニュアル - グラフィックス応用編」を参照してください。

15.8.1. ピクセルとブロックアドレスの対応

先に述べたように、ブロックアドレスは GPU の描画フォーマットで配置された、カラーバッファおよびデプスステンシルバッファの先頭アドレスから 16 ピクセルごとに 0 から昇順に割り当てられます。glViewport() の原点とは異なり、描画イメージの左上端のピクセルがバッファアドレスの先頭にあたります。また、描画イメージの水平方向(幅)が LCD の幅が短い辺に対応していることに注意してください。

アドレスの割り当て方が異なるため、ブロックモードによって描画イメージ上のピクセルに対応するキャッシュのブロックアドレスは異なります。

15.8.1.1. ブロック 8 モード

描画イメージの左上端にある 4×4 ピクセルがブロックアドレス 0、そのすぐ右の 4×4 ピクセルのブロックアドレスが 1、0 の下の 4×4 ピクセルが 2、1 の下の 4×4 ピクセルが 3 です。8×8 ピクセル単位で右方向にブロックアドレスが大きくなり、右端まで行くと、すぐ下の行の左端にある 8×8 ピクセルから続けてカウントします。

図 15-1. ブロック 8 モードでのブロックアドレスの割り当て

00 01 04 02 03 06 05 07 08 0A 09 0B N-4 N-2 N-3 N-1 N N+2 N+1 N+3 4ピクセル 4ピクセル

図内の N は、フレームバッファの横のピクセル数を W とすると、N=W÷4×2 で計算することができます。

15.8.1.2. ブロック 32 モード

ブロック 32 モードでは、32×32 ピクセルのメタブロックという単位でアドレスを割り当てます。描画イメージの左上端にある 32×32 ピクセルがメタブロックアドレス 0、そのすぐ右の 32×32 ピクセルがメタブロックアドレス 1 です。32×32 ピクセル単位で右方向にメタブロックアドレスが大きくなり、右端まで行くと、すぐ下の行の左端にある 32×32 ピクセルから続けてカウントします。

下図のように、ピクセルのブロックアドレスは、メタブロック内の左上端の 4×4 ピクセルからジグザグに配置されます。描画イメージ全体から見た各ピクセルのブロックアドレスは、メタブロックアドレス×0x40+メタブロック内のブロックアドレスで計算することができます。

図 15-2. ブロック 32 モードでのブロックアドレスの割り当て

0 1 2 3 N-1 N N+1 32ピクセル 00 01 32ピクセル 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 4ピクセル 4ピクセル

図内の左側はメタブロック内のピクセルのブロックアドレス(16 進数表記)です。右側は描画イメージ全体から見たメタブロックのアドレスです。

15.8.2. 回避方法 1

フレームバッファの縦方向のピクセル数×横方向のピクセル数が、262128 ピクセル以下であれば問題は発生しません。つまり、不必要に大きいサイズのフレームバッファを使用しないことで問題を回避することができます。

LCD と同じサイズとなる、240×400(総ピクセル数 96000)または 240×320(総ピクセル数 76800)のフレームバッファならば、総ピクセル数が 262128 以下になりますので、問題は発生しません。

フレームバッファのサイズは glRenderbufferStorage() で指定しますが、大きめに確保したフレームバッファの一部(240×400)のみ使用する場合は、フレームバッファの確保領域を必要最低限のサイズに留めることで問題を回避することができます。

15.8.3. 回避方法 2

ブロックアドレス 0x3FFF にあたる問題のピクセルが描画領域の外に配置されるように、フレームバッファのサイズを調整することで問題を回避することができます。

例えば、ブロック 8 モードで 2x2 アンチエイリアスをかけるときのように 480×800 のフレームバッファを確保すると、ピクセル座標(124, 548)を左上端とする 4×4 ピクセルがブロックアドレス 0x3FFF に割り当てられます。この場合、フレームバッファのサイズを横方向に 32 ピクセル拡張して 512×800 とすると、ブロックアドレス 0x3FFF に割り当てられるピクセルは(508, 508)を左上端とする 4×4 ピクセルとなります。そこで、ビューポートの設定でフレームバッファの左側 480×800 のみを表示すれば、問題のピクセルを避けることができます。

この方法では、フレームバッファを必要なサイズよりも余分に確保しなければならないため、VRAM を無駄に消費してしまうデメリットがありますが、サイズを調整するだけで簡単に回避することができます。

ブロックモードやフレームバッファのサイズの違いにより、ブロックアドレス 0x3FFF に割り当てられるピクセルがどの座標になるのかは、前述の「15.8.1. ピクセルとブロックアドレスの対応」を参照してください。

15.8.4. 回避方法 3

キャッシュタグのクリア直後に、ブロックアドレス 0x3FFF に割り当てられているピクセルを避け、数ピクセル描画してキャッシュタグの内容を変化させることで問題を回避することができます。

キャッシュタグの内容を変化させるには、特定のブロックアドレスのピクセルを 4 ピクセル描画しなければなりません。カラーバッファとデプスステンシルバッファの両方をリードする設定のときは、ブロックアドレスの下位 3 ビットがすべて 1(つまり 0x7)であり、かつブロックアドレスが異なるピクセルが 4 ピクセル必要です。カラーバッファとデプスステンシルバッファのどちらか一方のみをリードする設定のときは、ブロックアドレスの下位 4 ビットがすべて 1(つまり 0xF)であり、かつブロックアドレスが異なるピクセルが 4 ピクセル必要です。

例えば、キャッシュタグのクリア直後に描画されるピクセルのブロックアドレスが、

0x00、0x01、0x0F、0x02、0x1F、0x03、0x0F、0x2F、0x3F、・・・

というような場合、0x00 や 0x01 などは下位 4 ビットが 0xF ではないためカウントされません。0x0F は 2 回描画されましたが、同じブロックアドレスのピクセルは 1 つとカウントします。0x0F、0x1F、0x2F、0x3F が描画された時点で問題を回避することができ、0x3F より先に 0x3FFF のピクセルが描画された場合には問題が発生します。

キャッシュタグのクリア直後に、このような条件を満たすピクセルを含むダミーポリゴンを描画することで、問題を回避することができます。ただし、以下の点に注意して描画しなければなりません。

  • デプステスト、ステンシルテスト、アルファテストでフェイルするピクセルでもダミー描画として有効です。ただし、ダミー描画が必ずフェイルするような設定をした場合(例えばデプステスト関数に GL_NEVER を設定するような場合)は、ダミー描画のあとに続けて、通常の設定で描画するときにデプステスト関数を元に戻す必要があります。その際に、キャッシュをフラッシュするコマンド(レジスタ 0x0111 に書き込むコマンド)が必要となることに注意してください。
  • アルファブレンドの設定により、描画結果がカラーバッファに影響しないようなピクセルでもダミー描画として有効です。
  • ビューボリュームおよびユーザー定義のクリップ平面によりクリップされたピクセルはダミー描画として無効です。
  • シザーテストにより落とされたピクセルはダミー描画として無効です。
  • アーリーデプステストにより落とされたピクセルはダミー描画として無効です。

15.8.4.1. ブロック 8 モード

問題を回避するためにダミーポリゴンとして描画するピクセルは、ブロックアドレスの下位 4 ビット、または下位 3 ビットがすべて 1 であるピクセルです。ブロック 8 モードのブロックアドレスの並びを見ると、下位 4 ビットについては 32×8 ピクセル単位、下位 3 ビットについては 16×8 ピクセル単位で同じパターンが横に並ぶことになります。ただし、フレームバッファの横方向のピクセル数によっては、縦方向の 8 ピクセルごとに、横方向に 8 ピクセル単位でずれる可能性があります。

図 15-3. ブロック 8 モードでのブロックアドレスの並び

0x0 0x1 0x4 0x2 0x3 0x6 0x5 0x7 0x8 0xA 0x9 0xB 0xC 0xE 0xD 0xF 0x0 0x1 0x2 0x3 0x4 0x6 0x5 0x7 0x8 0xA 0x9 0xB 0xC 0xE 0xD 0xF 下位4ビット単位 0x0 0x1 0x4 0x2 0x3 0x6 0x5 0x7 0x0 0x1 0x2 0x3 0x4 0x6 0x5 0x7 下位3ビット単位

以下のサイズの矩形であればダミーポリゴンとしての条件を満たします。最小面積の矩形については、四隅のピクセルが条件となるブロックアドレスにかかっていなければならないことに注意してください。

表 15-4. ブロック 8 モードでダミーポリゴンとして描画する矩形

矩形の形状・条件

下位 4 ビットがすべて 1

下位 3 ビットがすべて 1

最小面積の矩形(配置場所は限定されます)

94×1

46×1

配置場所を選ばない矩形

125×5
29×29

61×5
13×29

15.8.4.2. ブロック 32 モード

問題を回避するためにダミーポリゴンとして描画するピクセルは、ブロックアドレスの下位 4 ビット、または下位 3 ビットがすべて 1 であるピクセルです。ブロック 32 モードのブロックアドレスの並びを見ると、下位 4 ビットについては 32×32 ピクセル単位、下位 3 ビットについては 32×16 ピクセル単位で同じパターンが縦横に並ぶことになります。

図 15-4. ブロック 32 モードでのブロックアドレスの並び

0x0 0x1 0x4 0x2 0x3 0x6 0x5 0x7 0x8 0xA 0x9 0xB 0xC 0xE 0xD 0xF 下位4ビット単位 0x0 0x1 0x4 0x2 0x3 0x6 0x5 0x7 下位3ビット単位

以下のサイズの矩形であればダミーポリゴンとしての条件を満たします。最小面積の矩形については、四隅のピクセルが条件となるブロックアドレスにかかっていなければならないことに注意してください。

表 15-5. ブロック 32 モードでダミーポリゴンとして描画する矩形

矩形の形状・条件

下位 4 ビットがすべて 1

下位 3 ビットがすべて 1

最小面積の矩形(配置場所は限定されます)

46×1
14×14

46×1
14×6

配置場所を選ばない矩形

61×13
29×29

61×5
29×13

15.9. 意図しない線が描画され、フレームバッファ直後の領域が破壊される

ウィンドウ座標で x=0 の付近に右側のエッジがある非常に小さなポリゴンを描画すると、意図しない線が描画されてしまう場合があります。この現象は、ポリゴンのピクセル生成時の計算誤差によってピクセルの x 座標が負の値になってしまうことでラップアラウンドを起こし、x 座標の値が非常に大きくなるために x の正の方向に長く伸びたポリゴンが描画されてしまうことが原因となっています。

この現象が発生すると、描画領域以外のメモリの内容が壊されてしまう可能性があります。ラップアラウンドした x 座標は 1023 になり、(0, y) から (1023, y) までのピクセルが描画領域のサイズに関わらず生成されます。つまり、描画領域のサイズが 256×256 のように幅が 1024 より小さく設定されている場合でも、x 座標が 0 から 1023 までのピクセルが生成されてしまいます。フレームバッファへのアクセスはピクセルの x、y 座標と描画領域の幅から計算されたアドレスに対して行われますが、ピクセルの x 座標が描画領域の幅より大きい場合でも x 座標はそのままアドレス計算に使用されます。そのため、y 座標によっては、描画領域よりも後方のメモリにピクセルカラーのデータを書き込んでしまう可能性があります。

補足:

シザーテストによって描画領域を切り取っている場合はメモリ内容の破壊は起きません。メモリ内容の破壊を回避するためにもシザーテストの設定を行うことを推奨します。なお、シザーテストを行うことで GPU のパフォーマンスへのペナルティはありません。

この現象の発生条件はポリゴンのウィンドウ座標にのみ依存します。同じウィンドウ座標で現象が発生したり発生しなかったりすることはありません。また、ビューボリュームやクリッピングされた結果生成されるポリゴンについて発生するため、元のポリゴン自体が大きい場合でも、ウィンドウ座標 x=0 側の画面端からポリゴンがはみ出して、ビューボリューム内に含まれる面積が非常に小さい場合でも発生します。

この現象の回避方法として、頂点シェーダで x 座標を調整する方法があります。頂点シェーダで計算するクリップ座標 x は –w から w でクリップされますので、x の値が –w 付近の値であれば、その頂点はウィンドウ座標で x=0 側の画面端付近にあるということになります。このような x=0 側の画面端付近に配置される頂点を画面端上に移動させる(x の値を –w に修正する)という処理で現象の発生を回避することができます。この回避方法は、1 ピクセル以下の頂点座標について処理するため、描画結果にはほとんど影響を与えません。

頂点シェーダで行う処理は、射影変換を行ったあとの x の値(頂点座標 x として出力レジスタに書き込む値)に対し下記のような処理をします。

コード 15-3. 現象の発生を回避するための処理
if ( -w < x && x < -w * (1-epsilon) ) x = -w;

上記の xw は射影変換後の頂点座標での x と w の値です。epsilon は調整用の変数として、描画するシーンに応じて最適な値を設定します。

以下に頂点シェーダの実装例を示します。mul 命令以降が現象の発生を回避するための処理です。

コード 15-4. 回避方法の実装例
// v0    : position attribute
// o0    : output for position
// c0-c3 : modelview matrix
// c4-c7 : projection matrix
// c8    : (1 - epsilon, 1, any, any)
m4x4    r0, v0, c0              // modelview transformation
m4x4    r1, r0, c4              // projection transformation
mul     r2.xy, -r1.w, c8.xy     // r2.x = -w * (1-epsilon), r2.y = -w
cmp     2, 4, r1.xx, r2.xy      //
ifc     1, 1, 1                 // if ((x < -w * (1-epsilon)) && (x > -w))
    mov     r1.x, -r1.w         // x = -w;
endif
mov     o0, r1

15.10. ドライバの内部動作の優先度変更

3DS の CPU には複数のコアがあり、1 つはアプリケーションが占有していますが、ほかのコアはシステムが各種デバイスの処理を制御するために占有しています。

システム側のコアで制御されるデバイスは複数あり、その中でも GPU(グラフィックス)の処理は高い優先度で行われるため、負荷の高いグラフィックス処理を行うと、ほかのデバイスの処理に影響を与えることがあります。このような場合、nngxSetInternalDriverPrioMode() で GPU の優先度を低くすることにより、ほかのデバイスへの影響を解決できる可能性があります。

コード 15-5. GPU ドライバの内部動作の優先度変更
void nngxSetInternalDriverPrioMode(nngxInternalDriverPrioMode mode);

mode には、以下の値から選択して指定します。

表 15-6. 内部動作の優先度

定義

説明

NN_GX_INTERNAL_DRIVER_PRIO_MODE_HIGH

高い優先度で動作(デフォルト)

NN_GX_INTERNAL_DRIVER_PRIO_MODE_LOW

低い優先度で動作

優先度を低くすることで、ほかのデバイスの処理への影響は抑えられますが、グラフィックス性能が低下します。

15.11. ライブラリ内部で管理領域を確保する関数

gl 関数や nngx 関数の中には、ライブラリが暗黙的に管理領域を確保するものがあります。

15.11.1. nngxValidateState()、glDraw*()

参照テーブル(LUT)が利用される場合に、ライブラリは管理領域を確保します。

glTexture1D() が呼ばれ、参照テーブルが利用されることがライブラリに通知されると、次に nngxValidateState()glDraw*() が実行されるときに参照テーブルロード用の中間バッファが確保されます。ただし、3D コマンドを直接生成し、参照テーブルを使うバッファを指定した場合は確保されません。

なお、確保した領域は glDeleteTexture() または nngxFinalize() で解放されます。

15.11.2. コマンドリスト、ディスプレイリスト、テクスチャなど

コマンドリスト、ディスプレイリスト、テクスチャなどを確保するための関数はオブジェクトごとに管理領域を確保し、確保された管理領域は nngxDelete*()glDelete*() で対応するオブジェクトが破棄されるまで保持されます。

上記に該当する関数は以下のとおりです。

nngxGenCmdlists()nngxGenDisplaybuffers()glCreateProgram()glCreateShader()glGenBuffers()glGenRenderbuffers()glGenTextures() など

15.12. GPU ハングアップの原因の解析

nngxGetCmdlistParameteri()pnameNN_GX_CMDLIST_HW_STATE を渡して取得したデータのいくつかのビットはハードウェアがビジー状態であることを示します。つまり、GPU がハングアップした場合など、ハードウェアの動作に問題が発生した場合のこのデータが原因の解析に役立つ可能性があります。

ハードウェアが不正動作をしたときは、基本的にビジー状態のままになっているモジュールのいずれかが原因である可能性が高いと考えられます。ただし、トライアングルセットアップ⇒ラスタライゼーションモジュール⇒テクスチャユニットなどのように連続するモジュールでは、後段のモジュールから前段のモジュールへビジー信号が伝搬するため、連続するモジュールがビジー状態になっている場合、その最後段のモジュールが原因である可能性が考えられます。これとは逆に、ビット 6 のパーフラグメントオペレーションモジュールは前段からのデータが不正だった場合にもビジー状態となるため、問題の原因は前段にある可能性もあります。

ビジー信号の伝搬は大きく分けて、ビット 0 ~ 7 のラスタライズ/ピクセル処理部と、ビット 8 ~ 16 のジオメトリ処理部に分けられます。

ラスタライズ/ピクセル処理部は、トライアングルセットアップ⇒ラスタライゼーションモジュール⇒テクスチャユニット⇒フラグメントライティング⇒テクスチャコンバイナ⇒パーフラグメントオペレーションモジュールの順に連続するモジュールであり、後段のモジュールのビジー信号が前段のモジュールへ伝搬します。つまり、ビット 5 ⇒ビット 4 ⇒ビット 3 ⇒ビット 2 ⇒ビット 1 ⇒ビット 0 へと伝搬します。

ビット 6 もパーフラグメントオペレーションモジュールのビジー信号です。ただし、ビット 0 とビット 1 には伝搬しますが、ビット 2、ビット 3、ビット 4 には伝搬しません。

ビット 7 のアーリーデプステストモジュールはアーリーデプスバッファ(GPU の内蔵メモリ)のクリア待ちのときにビジー状態となり、ほかのモジュールにビジー信号が伝搬しません。

トライアングルセットアップからその前段モジュール(頂点キャッシュまたはジオメトリ生成)へはビジー信号が伝搬しません。つまり、ラスタライズ/ピクセル処理部とジオメトリ処理部の間ではビジー信号が伝搬しません。

続いてジオメトリ処理部について説明します。頂点入力プロセス(コマンドバッファおよび頂点アレイをロードするモジュール)⇒頂点処理プロセッサ⇒ポスト頂点キャッシュの順に連続するモジュールであり、後段のモジュールのビジー信号が前段のモジュールへ伝搬します。つまり、ビット 16 ⇒(ビット 11、ビット 12、ビット 13、ビット 14)⇒ビット 8 ⇒ビット 9 へと伝搬します。ビット 11、ビット 12、ビット 13、ビット 14 は頂点プロセッサ 0、1、2、3 のビジー状態に対応しますが、各頂点プロセッサは頂点ロードモジュールとポスト頂点キャッシュの間に並列に配置されているため、ポスト頂点キャッシュのビジー信号は 4 つの頂点プロセッサのうちの 1 つまたは複数に伝搬します。4 つあるすべての頂点プロセッサに必ず伝搬するわけではありません。

上記はジオメトリシェーダが無効の場合です。ジオメトリシェーダが有効である場合、ジオメトリシェーダプロセッサである頂点プロセッサ 0 はポスト頂点キャッシュの後段に位置します。このとき、ジオメトリシェーダプロセッサのビジー信号はポスト頂点キャッシュへと伝搬します。ジオメトリシェーダプロセッサを起因とするビジー信号はポスト頂点キャッシュへは伝搬しますが、それより前段へは伝搬しません。ポスト頂点キャッシュを起因とするビジー信号は、前段のモジュール(頂点プロセッサ 1、2、3)へと伝搬します。つまり、ビット 11 ⇒ビット 16、およびビット 16 ⇒(ビット 12、ビット 13、ビット 14)⇒ビット 8 ⇒ビット 9 へとビジー信号が伝搬します。

ビット 16 に対応するポスト頂点キャッシュは、キャッシュ内に頂点データが満杯まで入力されたときにビジー信号を出力します。何らかの理由により、ポスト頂点キャッシュが後段のモジュールに対してデータが出力できない場合、例えば後段のモジュールがハングアップしたような場合ではポスト頂点キャッシュに頂点データが溜まり、ポスト頂点キャッシュがビジー信号を出力します。ポスト頂点キャッシュの後段のモジュールは、ジオメトリシェーダが無効な場合はトライアングルセットアップであり、ジオメトリシェーダが有効な場合はジオメトリシェーダプロセッサ(頂点プロセッサ 0)です。

ビット 2 のテクスチャユニットがビジー状態で GPU がハングアップする原因には、4 ビットフォーマットと非 4 ビットフォーマットのテクスチャを同時にマルチテクスチャとして使用するときに発生するハードウェアの不具合が挙げられます。

ロードアレイ(頂点属性データの GPU へのロード単位)の設定が正しくないことが原因で GPU がハングアップした場合、ビット 8 がビジー状態になります。

頂点シェーダから NaN が出力された(ジオメトリシェーダが使用されている場合はジオメトリシェーダから NaN が出力された)ことが原因で GPU がハングアップした場合、ラスタライゼーションモジュールおよびトライアングルセットアップ(ビット 0 およびビット 1)がビジー状態となります。

補足:

nngxGetCmdlistParamerteri() および NN_GX_CMDLIST_HW_STATE で取得するデータのビットについては、「4.1.10. パラメータ取得」を参照してください。

15.12.1. GPU ハングアップ時のハードウェア状態の事例

参考までに、GPU がハングアップしたときに NN_GX_CMDLIST_HW_STATE で取得したハードウェア状態とハングアップの原因の事例を以下に挙げます。

表 15-7. ハードウェア状態と GPU のハングアップの原因

ハードウェア状態

GPU のハングアップの原因

0x00011303
0x00011F03
0x00012F03

GPU の動作中に CPU で頂点バッファの内容を破壊していた。

0x00010103
0x0001011B
0x00014303

GPU の不具合により、VRAM 上の頂点バッファの内容が描画中に破壊されていた。

(「15.9. 意図しない線が描画され、フレームバッファ直後の領域が破壊される」を参照してください)

0x00010107
0x00011307
0x00012307

マルチテクスチャのハードウェア不具合によりハングアップした。

テクスチャアドレスの 128 バイトのアライメントに抵触していた。

(「7.3.1. コンポーネントが 4 ビットのフォーマットについて」を参照してください)

0x00000100

PICA レジスタの 0x0229 のビット 8 および 0x0253 のビット 8 が正しく設定されていなかった。

(GL 関数以外で描画したあとに GL 関数で描画するときは、上記のレジスタを再設定する必要があります)

未使用のロードアレイの要素設定数(レジスタ 0x0205 + n * 3 のビット 31 ~ 28)に 0 が設定されていない。

0x00007300

PICA レジスタの 0x0289 がジオメトリシェーダを使用する設定になっている状態で、ジオメトリシェーダを使用しない頂点シェーダを実行していた。

0x00000000

コマンドバッファのアドレスジャンプ機能関連レジスタが正しく設定されていなかった。

コマンドリクエストの実行待機状態で、アドレスジャンプ機能関連レジスタの設定完了前に nngxFlush3DCommandPartially() を呼び出した。

0x00000001
0x00000002
0x00000003

頂点シェーダから NaN が出力された。

補足:

PICA レジスタの設定方法については「3DS プログラミングマニュアル - グラフィックス応用編」を参照してください。

15.13. 頂点属性の組み合わせによる頂点データの転送速度への影響

頂点バッファを使用する場合、頂点属性のデータタイプとデータサイズ(glVertexAttribPointer()typesize)の組み合わせが頂点データの転送速度に影響します。

頂点バッファに格納された頂点属性データは、1 つまたは複数の頂点属性がまとめられて GPU にロードされます。このときにロードされる頂点データの単位を「ロードアレイ」と呼びます。

補足:

ロードアレイの詳細については、「3DS プログラミングマニュアル - グラフィックス応用編」を参照してください。

GPU は各ロードアレイを転送する際に、ロードアレイを構成する頂点属性のデータタイプとデータサイズの組み合わせによって、先読み転送を行うかどうかを判定します。先読み転送が行われる場合、頂点データの転送速度が速くなります。

下記の条件式が成り立つ場合に、先読み転送が行われます。

( "GL_FLOAT 以外の型の属性数" + "データサイズが 1 の属性数" )
<= ( "GL_FLOAT 型でデータサイズが 4 の属性数" + "GL_FLOAT 型でデータサイズが 3 の属性数" / 2 )

"GL_FLOAT 以外の型の属性数"のデータサイズ、"データサイズが 1 の属性数"のデータタイプは任意です。複数の条件に当てはまる頂点属性は、それぞれの項目でカウントされます。例えば、データサイズが 1 の GL_BYTE 型の頂点属性は、"GL_FLOAT 以外の型の属性数"と"データサイズが 1 の属性数"の両方でカウントされます。

先読み転送が行われるかどうかの条件が同じである場合、転送速度はロードアレイあたりのデータ量に依存します。データ量が少ないほど、転送速度が速くなります。さらに、頂点データのデータ量が同じである場合、転送速度はロードアレイに含まれる属性数に依存します。ロードアレイの属性数が少ないほど、転送速度が速くなります。

15.14. 頂点アレイのアドレスアライメント

頂点バッファを使用して描画する場合、頂点アレイのアドレスのアライメントを 32 バイトにすることで、描画時に頂点アレイの転送処理の効率が向上する可能性があります。ここでいう頂点アレイのアドレスとは、頂点バッファのアドレスに glVertexAttribPointer() で指定するオフセット(ptr で指定する値)を加算した値のことです。

頂点アレイのアドレスアライメントを 32 バイトに揃えない場合に比べて、どのくらい速度が向上するかは頂点属性の型、サイズ、頂点アレイの格納場所、頂点インデックスの内容に依存するため、必ず効果が得られるとは限りません。また、転送処理の性能のみが向上しても、頂点アレイの転送処理がボトルネックとなっている状態でなければ、システム全体としての性能向上にはつながりません。

15.15. マルチテクスチャ使用時に GPU がハングアップする

補足:

SNAKE では、マルチテクスチャの使用で GPU がハングアップすることはありません。ただし、アプリケーションが CTR 上で動作している場合は起こり得ることに注意してください。

以下のすべての条件を満たす場合、GPU がハングアップすることがあります。

  • テクスチャを複数使用している。
  • テクスチャユニット間のパフォーマンスに大きな差がある。

発生条件には、プロシージャルテクスチャは含まれません。通常のテクスチャ 1 枚とプロシージャルテクスチャを同時に使用しても、本現象は発生しません。

以下のいずれかの方法で、本現象を回避できます。

  • テクスチャを 1 枚のみ使用する。
  • 使用するすべてのテクスチャに対して、テクスチャパラメータ GL_TEXTURE_MIN_FILTER の設定を GL_XXX_MIPMAP_LINEAR(トライリニアフィルタ使用)にする。
    実際にミップマップが存在しないテクスチャに対しても、本パラメータを設定する必要があります。

また、本現象の緩和策として以下の方法が挙げられます。

  • 使用するテクスチャの一部に対して、テクスチャパラメータ GL_TEXTURE_MIN_FILTER の設定を GL_XXX_MIPMAP_LINEAR(トライリニアフィルタ使用)にする。
    すべてのテクスチャに対してトライリニアフィルタを使用することで完全に回避することができますが、一部のテクスチャに対して使用することで、現象の発生を緩和させることができます。
  • 同時に使用するすべてのテクスチャを同一の VRAM に配置する。
  • 使用するテクスチャの枚数を減らす。

本現象の発生はタイミングに依存するため、下記のようにテクスチャの設定を変更することで回避できる可能性があります。ただし、場合によっては発生頻度が悪化する可能性もあります。

  • テクスチャのサイズを変更する。
  • テクスチャのフォーマットを変更する。
  • テクスチャのフィルタモードを変更する。
  • テクスチャの格納場所を VRAM-A、VRAM-B、FCRAM(デバイスメモリ)間で変更する。

ハングアップの原因が本現象であるかどうかの確認方法は、「15.12. GPU ハングアップの原因の解析」で紹介しています。

ただし、4 ビットテクスチャフォーマットの格納場所に関する制限に抵触した場合も同じ状態でハングアップすることがあるため、確実な判別はできません。

15.16. テクスチャコンバイナの GL_INTERPOLATE の計算

テクスチャコンバイナの GL_INTERPOLATE の計算式は src0 * src2 + src1 * (1 – src2 ) ですので、src2 が 1 または 0 の場合は、それぞれ src0 または src1 の値そのものとなることが期待されます。しかし、実装上の仕様により、src2 が 0 かつ、src0 < src1 を満たす場合は、期待される src1 そのままの値ではなく、src1 より輝度が 1 小さい値が出力されてしまいます。

この問題を回避するには、GL_MODULATEGL_MULT_ADD_DMP を組み合わせて、2 ステージのコンバイナで計算する必要があります。もしくは、src2 のオペランドを GL_ONE_MINUS_* に変更して src0src1 を入れ替えることで、問題が発生するフラグメントを減らせる可能性があります。

15.17. 同じ頂点座標のポリゴンで描画結果が完全に一致しない

頂点座標が全く同じポリゴンを描画しても、フラグメントの各属性値が完全には一致しないことがあります。

この現象は、ラスタライゼーションモジュールに入力される頂点の順番が異なる場合に、フラグメントの補間計算の誤差により発生します。頂点の入力順序が全く同じ場合は発生しませんが、GL_TRIANGLES を引数に glDrawElements() で描画すると、直前のポリゴンの頂点インデックスとの関係によっては、内部で頂点の入力順序が変更されることがあります。フラグメントが完全に一致するポリゴンを複数回描画したいときは、各ポリゴンの前後関係も含めて同じ頂点インデックスを使用して描画する必要があります。