3. LCD

3DS では、LCD の表示と GX(グラフィックス)ライブラリが密接に関係しています。レンダリングから LCD に表示されるまでの流れは、図 3-1 になります。

図 3-1. レンダリングから LCD に表示されるまでの流れ

Renderbuffer(Block format) Displaybuffer(Linear format) LCD Rendering Copy and Format conversion GPU Antialiasing Y-Flip Buffer swap

工程を大まかに分けると、以下のようになります。

  1. レンダリング
  2. レンダーバッファからディスプレイバッファへのコピーとフォーマット変換
  3. バッファスワップによる LCD に表示される領域の更新

1. のレンダリングについては「4. コマンドリスト」以降の章で説明します。この章では、出力先となる LCD の仕様と、GX ライブラリを使用する際に必要な初期化処理を最初に説明します。続いて 2. と 3. について、バッファの確保・転送、画面更新の同期、終了処理を順を追って説明します。

補足:

立体視表示については「3DS プログラミングマニュアル - グラフィックス応用編」を参照してください。

3.1. LCD の解像度、配置方向について

3DS の LCD は、NITRO/TWL の LCD と解像度および配置の方向が異なっています。解像度の違いを表 3-1 に、配置方向の違いを図 3-2 に示します。

表 3-1. LCD の解像度

LCD

NITRO/TWL

3DS

上画面 LCD(幅×高さ)

256 × 192

240 × 400

下画面 LCD(幅×高さ)

256 × 192

240 × 320

図 3-2. LCD の配置方向

Upper LCD Lower LCD 256 192 NITRO/TWL 320 400 3DS

NITRO/TWL では長辺が横方向(画面幅)、下画面を手前にしたときが正位置になるように配置されていましたが、3DS では短辺が横方向、下画面を上画面の左側に位置したとき(コントローラの正面から右に 90 度回り込んだ状態)が正位置になるように配置されています。

3.2. 必要な初期化処理

3DS ではフレームバッファアーキテクチャを採用しています。フレームバッファの内容をもとに LCD の表示が行われるため、最初に GX ライブラリの初期化を行わなければなりません。

3.2.1. GX ライブラリの初期化

GX ライブラリの初期化は nngxInitialize() を呼び出して行います。

コード 3-1. GX ライブラリの初期化関数
GLboolean nngxInitialize(
        GLvoid* (*allocator)(GLenum, GLenum, GLuint, GLsizei),
        void (deallocator)(GLenum, GLenum, GLuint, GLvoid));
GLboolean nngxGetIsInitialized();

nngxInitialize() では、ディスプレイバッファなどのメモリ領域を確保する際に呼び出されるアロケータと、そのメモリ領域を解放する際に呼び出されるデアロケータを allocatordeallocator に指定します。アロケータとデアロケータについての詳細は「3.2.1.1. アロケータ」、「3.2.1.2. デアロケータ」を参照してください。

初期化に成功した場合は GL_TRUE を、失敗した場合は GL_FALSE を返します。初期化後に nngxFinalize() で終了処理を行わずに、再度この関数を呼び出した場合は GL_FALSE を返します。この関数で初期化を行う前に他の glnngx 関数を呼び出した場合の動作は保証されていません。また、初期化でデフォルトのレンダーバッファなどは確保されません。初期化後にアプリケーションで確保する必要があります。

nngxGetIsInitialized() は、nngxInitialize() によって GX ライブラリが初期化済みならば GL_TRUE を返します。

3.2.1.1. アロケータ

アロケータの第 1 引数には、どのメモリから領域を確保するかが渡されます。渡される値の種類には、以下のものがあります。

  • NN_GX_MEM_FCRAM
    メインメモリ(デバイスメモリ)から確保します。
  • NN_GX_MEM_VRAMA
    VRAM-A から確保します。
  • NN_GX_MEM_VRAMB
    VRAM-B から確保します。

NN_GX_MEM_FCRAM が渡された場合、メインメモリから確保しますが、その領域はデバイスメモリ上でなければなりません。デバイスメモリとは、周辺デバイスからのアクセスの際にアドレスの整合性が保証されているメモリ領域のことです。メモリの管理はアプリケーションで行わなければなりません。デバイスメモリとして確保したメモリ領域の先頭アドレスとそのサイズは nn::os::GetDeviceMemoryAddress() と、nn::os::GetDeviceMemorySize() で取得することができます。

補足:

デバイスメモリについての詳細は「3DS プログラミングマニュアル - システム編」を参照してください。

NN_GX_MEM_VRAMA または NN_GX_MEM_VRAMB が渡された場合、VRAM の開始、終了アドレスやサイズを nn::gx::GetVramStartAddr()nn::gx::GetVramEndAddr()nn::gx::GetVramSize() でそれぞれ取得し、メインメモリと同様にアプリケーションでメモリ管理を行ってください。

第 2 引数には確保するメモリ領域の使用目的(バッファ種別)が渡されます。種別によってアドレスのアライメントが異なりますので、アプリケーションは以下の規定に従って、アロケータを実装してください。

表 3-2. バッファ種別によるアライメントの違い

渡される値

バッファ種別

アライメント

NN_GX_MEM_TEXTURE

テクスチャ
(2D、環境マップ)

全フォーマットとも 128 Byte

NN_GX_MEM_VERTEXBUFFER

頂点バッファ

頂点属性によって異なります。

4 Byte(GLfloat 型)
2 Byte(GLshort 型、GLushort 型)
1 Byte(GLbyte 型、GLubyte 型)

NN_GX_MEM_RENDERBUFFER

カラーバッファ

64 Byte

デプスバッファ
(ステンシルバッファ)

32 Byte(16 bit デプスの場合)
96 Byte(24 bit デプスの場合)
64 Byte(24 bit デプス+8 bit ステンシルの場合)

NN_GX_MEM_DISPLAYBUFFER

ディスプレイバッファ

16 Byte

NN_GX_MEM_COMMANDBUFFER

3D コマンドバッファ

16 Byte

NN_GX_MEM_SYSTEM

システム用バッファ

4 Byte(確保サイズが 4 の倍数)
2 Byte(確保サイズが 4 の倍数ではない 2 の倍数)
1 Byte(上記以外)

上記の規定に加えて、以下のハードウェア仕様にも従って実装してください。

  • キューブマップテクスチャの 6 面すべてが同じ 32 MByte 境界内に収まっていなければなりません。
  • キューブマップテクスチャの 6 面それぞれのアドレス上位 7 bit が共通の値でなければなりません。
  • キューブマップテクスチャでは、GL_TEXTURE_CUBE_MAP_POSITIVE_X の面のアドレスが、すべての面のアドレスのうちで一番小さい値または等しい値でなければなりません。
  • VRAM-A と VRAM-B をまたいで配置してはいけません。

 

注意:

ディスプレイバッファは VRAM-A/B の最後尾から 1.5 MByte には確保しないでください。

テクスチャアドレスのアライメントが正しく設定されていない場合、GPU がハングアップしたり、描画結果が壊れたりするなどの現象が発生する可能性があります。

第 3 引数には、第 2 引数で NN_GX_MEM_VERTEXBUFFERNN_GX_MEM_RENDERBUFFERNN_GX_MEM_DISPLAYBUFFERNN_GX_MEM_COMMANDBUFFER が渡された場合に、それぞれのオブジェクトの名前(ID)が渡されます。

第 4 引数には、確保する領域のサイズが渡されます。

アプリケーションはこれらの引数を基に、アドレスのアライメントを考慮して指定されたメモリ領域を確保し、戻り値としてメモリ領域の先頭アドレスを返してください。領域の確保に失敗した場合は 0 を返してください。

3.2.1.2. デアロケータ

第 1 から第 3 までの引数に渡される値は、メモリ領域の確保で渡された値と同じです。第 4 引数にはメモリ領域の先頭アドレスが渡されます。アプリケーションはこれらの引数を基に、アロケータで確保したメモリ領域を解放してください。

3.2.1.3. アロケータの取得

nngxGetAllocator() を呼び出して、GX ライブラリの初期化時に設定したアロケータを取得することができます。

コード 3-2. アロケータの取得
void nngxGetAllocator (
                GLvoid* (**allocator)(GLenum, GLenum, GLuint, GLsizei),
                void (*deallocator)(GLenum, GLenum, GLuint, GLvoid));

allocator および deallocator には、アロケータおよびデアロケータへのポインタを受け取るポインタを指定します。それぞれの引数に NULL を指定すると、アロケータまたはデアロケータを取得しません。

3.2.1.4. 初期化用レジスタ設定コマンドの取得

nngxGetInitializationCommand() を呼び出して、nngxInitialize() を呼び出したときに実行される初期化用のレジスタ設定コマンドを取得することができます。

補足:

この関数は、SDK がサポートしているグラフィックスライブラリを利用せず、直接レジスタへの設定コマンドを作成しているような場合に、HOME メニューなどからアプリケーションに復帰した際に生じる描画関連の問題への対策用に追加されました。そのため、通常は使用する必要はありません。

コード 3-3. 初期化用レジスタ設定コマンドの取得
GLsizei nngxGetInitializationCommand(GLsizei datasize, GLvoid* data);

data には、レジスタ設定コマンドを受け取るコマンドバッファへのポインタを指定します。datasize には、data が指すバッファのバイトサイズを指定します。

この関数は、初期化用のレジスタ設定コマンドのバイトサイズを返します。data に 0(NULL)を指定すると、レジスタ設定コマンドの取得は行われませんが、確保すべきバッファサイズを返します。一度 data に 0 を指定したあと、そのサイズのバッファを用意して、再度呼び出してコマンドを取得してください。

なお、nngxInitialize() で初期化を行う前に呼び出した場合は、コマンドは取得されず、0 を返します。

表 3-3. nngxGetInitializationCommand() が生成するエラー

エラー

原因

GL_ERROR_80B4_DMP

datasize に指定した値が、取得されるレジスタ設定コマンドのバイトサイズより小さい

3.2.2. コマンドリストオブジェクトの作成

GX ライブラリの初期化後に行わなければならないのは、コマンドリストオブジェクトの作成です。コマンドリストオブジェクトは 3DS が独自に導入したもので、このオブジェクトを 3D グラフィックス処理の実行単位として扱います。ここでは、その作成方法のみを紹介します。コマンドリスト(オブジェクト)の詳細については「4. コマンドリスト」を参照してください。

まず、nngxGenCmdlists() でコマンドリストオブジェクトを生成し、生成したコマンドリストオブジェクトを nngxBindCmdlist() で GPU にバインド(関連付け)したあと、nngxCmdlistStorage() でメモリ領域の確保を行います。

コマンドリストオブジェクトは 3D コマンドバッファとコマンドリクエストで構成されており、nngxCmdlistStorage()bufsize で 3D コマンドバッファのサイズを、requestcount でコマンドリクエストをキューイング可能な個数を指定します。コマンドリストオブジェクトを複数生成する場合は、それぞれのコマンドリストオブジェクトに対して nngxBindCmdlist()nngxCmdlistStorage() を呼び出す必要があります。

以下のコード例では、3D コマンドバッファのサイズが 256 KByte、キューイング可能なコマンドリクエストの数を 128 個で指定したコマンドリストオブジェクトを 1 つ作成しています。

コード 3-4. コマンドリストオブジェクトの作成例
GLuint commandList;

nngxGenCmdlists(1, &commandList);
nngxBindCmdlist(commandList);
nngxCmdlistStorage(256 * 1024, 128);

3.3. バッファの確保

グラフィックス処理で使用するバッファのうち LCD 出力に関係するのは、GPU のレンダーターゲットとなるフレームバッファとレンダリング結果をコピーして LCD への表示に使用するディスプレイバッファの 2 つです。フレームバッファとディスプレイバッファへの処理は、それぞれフレームバッファオブジェクト、ディスプレイバッファオブジェクトを用いて行われます。図 3-3 に構成を示します。

図 3-3. フレームバッファとディスプレイバッファの構成

Framebuffer Renderbuffer Displaybuffer

3.3.1. レンダーバッファの確保

最初に、レンダーターゲットの指定に使用するフレームバッファオブジェクトを glGenFramebuffers() で生成しなければなりません。フレームバッファオブジェクトに関連付けることで、各レンダーバッファ(カラー、デプス、ステンシル)はレンダーターゲットに指定することができるようになります。3DS は上下 2 つの画面を搭載していますが、上下の画面でフォーマットが同じで平行してレンダリングする必要がない場合は、メモリリソースを節約するためにもオブジェクトを共有することを推奨します。その際、バッファの幅と高さは上画面用の設定を使用してください。

コード 3-5. フレームバッファオブジェクトの生成
GLuint frameBufferObject;
glGenFramebuffers(1, &frameBufferObject);

次に、glGenRenderbuffers() でレンダーバッファオブジェクトを生成します。レンダーターゲットがカラーだけでなく、デプスまたはステンシル、もしくはその両方を含む場合は、フレームバッファオブジェクトに 2 つのレンダーバッファオブジェクトが必要になります。

コード 3-6. レンダーバッファオブジェクトの生成
GLuint renderBuffer[2];
glGenRenderbuffers(2, renderBuffer);

フレームバッファオブジェクトに関連付けるレンダーバッファオブジェクトを glBindRenderbuffer() で指定し、glRenderbufferStorage() でレンダーバッファを確保します。

コード 3-7. glRenderbufferStorage() の定義
void glRenderbufferStorage(GLenum target, GLenum internalformat, 
                           GLsizei width, GLsizei height);

width height にはレンダーバッファの幅と高さを指定します。幅と高さの最大値は、どちらも 1024 ピクセルです。

注意:

幅×高さが 262128 ピクセル以上のレンダーバッファでは、特定のピクセルにブロック状のノイズが描画される可能性があります。詳細については、「15.8. 特定のピクセルにブロック状のノイズが描画される」を参照してください。

target GL_RENDERBUFFER と以下のビットマスクとの論理和を渡すことで、バッファを確保するメモリを指定することができます。ビットマスクを指定しなかった場合は、NN_GX_MEM_VRAMA が指定されたものとして処理されます。

表 3-4. glRenderbufferStorage() の target に指定可能なビットマスク

ビットマスク

バッファの確保先

NN_GX_MEM_VRAMA

VRAM-A

NN_GX_MEM_VRAMB

VRAM-B

internalformat でバッファの種別(フォーマット)を指定しますが、3DS では以下のフォーマットから指定することができます。

表 3-5. glRenderbufferStorage() の internalformat で指定可能なフォーマット

フォーマット

ビット数

フォーマットの詳細

GL_DEPTH_COMPONENT16

16

16 bit デプス

GL_DEPTH_COMPONENT24_OES

24

24 bit デプス

GL_RGBA4

16

RGBA 各成分とも 4 bit

GL_RGB5_A1

16

RGB 各成分が 5 bit、アルファ成分が 1 bit

GL_RGB565

16

RB 成分が各 5 bit、G 成分が 6 bit。アルファ成分なし

GL_RGBA8_OES

32

RGBA 各成分とも 8 bit

GL_DEPTH24_STENCIL8_EXT

32

24 bit デプス、8 bit ステンシル

GL_GAS_DMP

32

ガスレンダリングで使用する密度情報(レンダリング結果をディスプレイバッファへコピーすることはできません)

確保したレンダーバッファは、glBindFramebuffer() で指定したフレームバッファオブジェクトに glFramebufferRenderbuffer() で関連付けることでレンダーターゲットに指定されます。

コード 3-8. glBindFramebuffer() と glFramebufferRenderbuffer() の定義
void glBindFramebuffer(GLenum target, GLuint framebuffer);
void glFramebufferRenderbuffer(GLenum target, GLenum attachment, 
        GLenum renderbuffertarget, GLuint renderbuffer);

どちらの関数も target には GL_FRAMEBUFFER を指定してください。

framebuffer にはフレームバッファオブジェクトを、renderbuffer にはレンダーバッファオブジェクトを指定します。renderbuffertarget には GL_RENDERBUFFER を指定してください。

attachment に指定する値はレンダーバッファのフォーマットによって異なります。

表 3-6. レンダーバッファのフォーマットと attachment の対応

フォーマット

attachment に指定する値

GL_DEPTH_COMPONENT16

GL_DEPTH_ATTACHMENT

GL_DEPTH_COMPONENT24_OES

GL_RGBA4

GL_COLOR_ATTACHMENT0

GL_RGB5_A1

GL_RGB565

GL_RGBA8_OES

GL_GAS_DMP

GL_DEPTH24_STENCIL8_EXT

GL_DEPTH_STENCIL_ATTACHMENT

glCheckFramebufferStatus() を呼び出すことで、フレームバッファオブジェクトに関連付けられたレンダーバッファオブジェクトの状態をチェックすることができます。引数には GL_FRAMEBUFFER を指定してください。それ以外を指定すると、GL_INVALID_ENUM のエラーが生成されます。

GL_FRAMEBUFFER_COMPLETE が返されたときは、正しいレンダーバッファオブジェクトが関連付けられています。

GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT が返されたときは、カラーバッファもデプス(ステンシル)バッファもアタッチされていません。

GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT が返されたときは、関連付けられているバッファのためのメモリが確保されていないか、カラーバッファとデプスバッファに同じレンダーバッファオブジェクトが関連付けられています。

GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS が返されたときは、関連付けられているバッファのサイズがそれぞれで異なっています。

以下に、レンダーバッファの確保を行うコード例を示します。カラーとデプス(ステンシル)で設定が異なることに注意してください。上下の画面でレンダーバッファを共有するため、幅と高さの指定には上画面の設定を使用しています。

コード 3-9. レンダーバッファ(カラー、デプス、ステンシル)の確保
// FrameBuffer
glBindFramebuffer(GL_FRAMEBUFFER, frameBufferObject);
// Color
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer[0]);
glRenderbufferStorage(GL_RENDERBUFFER | NN_GX_MEM_VRAMA, GL_RGBA8_OES, 
    nn::gx::DISPLAY0_WIDTH, nn::gx::DISPLAY0_HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 
    GL_RENDERBUFFER, renderBuffer[0]);
// Depth / Stencil
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer[1]);
glRenderbufferStorage(GL_RENDERBUFFER | NN_GX_MEM_VRAMB, 
    GL_DEPTH24_STENCIL8_EXT, nn::gx::DISPLAY0_WIDTH, nn::gx::DISPLAY0_HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 
    GL_RENDERBUFFER, renderBuffer[1]);

3.3.2. ディスプレイバッファの確保

ディスプレイバッファを確保する前に、上下どちらの画面に対するバッファであるのかを nngxActiveDisplay() で指定しなければなりません。

コード 3-10. nngxActiveDisplay() の定義
void nngxActiveDisplay(GLenum display);

display に渡す値で、上下どちらかの画面を指定することができます。display に下表以外の値を指定した場合は GL_ERROR_801F_DMP のエラーを生成します。

表 3-7. display に指定する値

display の値

指定される画面

NN_GX_DISPLAY0

上画面(立体視表示時は左目用)

NN_GX_DISPLAY0_EXT

上画面(立体視表示時のみ。右目用)

NN_GX_DISPLAY1

下画面

補足:

立体視表示については「3DS プログラミングマニュアル - グラフィックス応用編」を参照してください。

次に、nngxGenDisplaybuffers() でディスプレイバッファのオブジェクトを生成します。

コード 3-11. nngxGenDisplaybuffers() の定義
void nngxGenDisplaybuffers(GLsizei n, GLuint* buffers);

n には生成するディスプレイバッファのオブジェクト数を、buffers にはディスプレイバッファのオブジェクトを格納する配列を指定します。マルチバッファリングで 1 つの画面に対して複数のディスプレイバッファを使用する場合は、オブジェクトを必要な数だけ生成する必要があります。

表 3-8. nngxGenDisplaybuffers() が生成するエラー

エラー

原因

GL_ERROR_801C_DMP

n に負の値を指定した

GL_ERROR_801D_DMP

管理領域の確保に失敗した

生成したディスプレイバッファのオブジェクトを nngxBindDisplaybuffer() でターゲットのディスプレイバッファに指定します。

コード 3-12. nngxBindDisplaybuffer() の定義
void nngxBindDisplaybuffer(GLuint buffer);

buffer に未使用のオブジェクト名が指定された場合はオブジェクトの生成が行われます。その際、管理領域の確保に失敗した場合は GL_ERROR_8020_DMP のエラーを生成します。

nngxDisplaybufferStorage() でディスプレイバッファを確保します。

コード 3-13. nngxDisplaybufferStorage() の定義
void nngxDisplaybufferStorage(GLenum format, GLsizei width, GLsizei height, 
                              GLenum area);

format で指定するバッファのフォーマットは以下から指定することができます。カラーバッファの確保で指定したフォーマットよりもピクセルあたりのビット数が大きいフォーマットは指定することができません。

表 3-9. nngxDisplaybufferStorage() の format で指定可能なフォーマット

フォーマット

ビット数

フォーマットの詳細

GL_RGBA4

16

RGBA 各成分とも 4 bit

GL_RGB5_A1

16

RGB 各成分が 5 bit、アルファ成分が 1 bit

GL_RGB565

16

RB 成分が各 5 bit、G 成分が 6 bit。アルファ成分なし

GL_RGB8_OES

24

RGB 各成分とも 8 bit。アルファ成分なし

width height にはディスプレイバッファのサイズを指定します。ともにブロックサイズ(デフォルトは 8)の倍数の正値でなければなりません。ブロックサイズはレンダーバッファのブロックモードを変更することで 8 または 32 となります。ブロックモードの設定については、「3DS プログラミングマニュアル - グラフィックス応用編」を参照してください。

area にはバッファを確保するメモリを指定します。

表 3-10. nngxDisplaybufferStorage() の area に指定可能なフラグ

フラグ

バッファの確保先

NN_GX_MEM_FCRAM

メインメモリ(デバイスメモリ)

NN_GX_MEM_VRAMA

VRAM-A

NN_GX_MEM_VRAMB

VRAM-B

画面のキャプチャイメージを保存する場合は、CPU がアクセス可能なメインメモリにディスプレイバッファを確保する必要があります。

すでにディスプレイバッファが確保されているディスプレイバッファオブジェクトに対して、再度ディスプレイバッファを確保した場合は、すでに確保されているメモリ領域を解放して新しくメモリ領域を確保します。

表 3-11. nngxDisplaybufferStorage() が生成するエラー

エラー

原因

GL_ERROR_8021_DMP

オブジェクト名が 0 のディスプレイバッファがターゲットになっているとき

GL_ERROR_8022_DMP

width または height に不正な値を指定した

GL_ERROR_8023_DMP

format に指定可能な値以外を指定した

GL_ERROR_8024_DMP

area に指定可能な値以外を指定した

GL_ERROR_8025_DMP

メモリの確保に失敗した

ディスプレイバッファは複数確保することができます。以下のコード例ではダブルバッファリングを採用しています。

コード 3-14. ディスプレイバッファの確保
GLuint display0Buffers[2];
GLuint display1Buffers[2];
// Displaybuffer for UpperLCD
nngxActiveDisplay(NN_GX_DISPLAY0);
nngxGenDisplaybuffers(2, display0Buffers);
nngxBindDisplaybuffer(display0Buffers[0]);
nngxDisplaybufferStorage(GL_RGB8_OES, nn::gx::DISPLAY0_WIDTH, 
        nn::gx::DISPLAY0_HEIGHT, NN_GX_MEM_FCRAM);
nngxBindDisplaybuffer(display0Buffers[1]);
nngxDisplaybufferStorage(GL_RGB8_OES, nn::gx::DISPLAY0_WIDTH, 
        nn::gx::DISPLAY0_HEIGHT, NN_GX_MEM_FCRAM);
nngxDisplayEnv(0, 0);
// Displaybuffer for LowerLCD
nngxActiveDisplay(NN_GX_DISPLAY1);
nngxGenDisplaybuffers(2, display1Buffers);
nngxBindDisplaybuffer(display1Buffers[0]);
nngxDisplaybufferStorage(GL_RGB8_OES, nn::gx::DISPLAY1_WIDTH, 
        nn::gx::DISPLAY1_HEIGHT, NN_GX_MEM_FCRAM);
nngxBindDisplaybuffer(display1Buffers[1]);
nngxDisplaybufferStorage(GL_RGB8_OES, nn::gx::DISPLAY1_WIDTH, 
        nn::gx::DISPLAY1_HEIGHT, NN_GX_MEM_FCRAM);
nngxDisplayEnv(0, 0);

3.3.2.1. パラメータの取得

ターゲットとなっているディスプレイバッファの情報を nngxGetDisplaybufferParameteri() で取得することができます。

コード 3-15. ディスプレイバッファのパラメータの取得関数
void nngxGetDisplaybufferParameteri(GLenum pname, GLint* param);

pname には以下の値を指定することができます。それ以外の値を指定した場合は GL_ERROR_8033_DMP のエラーを生成します。

表 3-12. ディスプレイバッファのパラメータ

pname

取得するパラメータの内容

NN_GX_DISPLAYBUFFER_ADDRESS

ディスプレイバッファの先頭アドレス。

NN_GX_DISPLAYBUFFER_FORMAT

ディスプレイバッファのフォーマット。

NN_GX_DISPLAYBUFFER_WIDTH

ディスプレイバッファの幅。

NN_GX_DISPLAYBUFFER_HEIGHT

ディスプレイバッファの高さ。

3.4. カラーバッファからディスプレイバッファへのコピー

レンダリングが終了した時点ではカラーバッファの内容がブロックフォーマットであるため、リニアフォーマットでなければ表示することのできない LCD へは出力できません。LCD に出力可能なフォーマットへの変換と、glBindFramebuffer() で指定したフレームバッファオブジェクトに関連付けられているカラーバッファからディスプレイバッファへのコピーを行う nngxTransferRenderImage() を呼び出す必要があります。

コード 3-16. カラーバッファからディスプレイバッファへのコピー関数
void nngxTransferRenderImage(GLuint buffer, GLenum mode, GLboolean yflip, 
                             GLint colorx, GLint colory);

buffer にはコピー先のディスプレイバッファのオブジェクトを指定します。3D コマンドバッファに区切られていないコマンドが蓄積されている場合は、区切りのコマンドを追加してから転送のコマンドをコマンドリクエストに追加します。この関数の実行直後は追加された転送コマンドの実行が完了しているとは限りませんので、カラーバッファを削除したり内容を書き換えたりする場合は、コマンドリストの実行が完了するまで待ってください。

mode の設定で、レンダリング結果をディスプレイバッファにコピーする際に適用するアンチエイリアスを指定します。

表 3-13. アンチエイリアスの指定

mode の値

アンチエイリアスの指定

高さ

NN_GX_ANTIALIASE_NOT_USED

オフ(なし)

等倍

等倍

NN_GX_ANTIALIASE_2x1

2x1 アンチエイリアス

2 倍

等倍

NN_GX_ANTIALIASE_2x2

2x2 アンチエイリアス

2 倍

2 倍

yflip の設定で、レンダリング結果をディスプレイバッファにコピーする際に Y フリップ(上下反転)を適用するかどうかを指定します。引数に GL_TRUE を渡すと適用されます。0 以外の値は GL_TRUE として扱われます。

colorx colory にはカラーバッファからコピーする際のオフセット(左下が原点、右上が正方向)を指定します。オフセットに指定する値はブロックサイズ(ブロックモードがブロック 8 モードならば 8、ブロック 32 モードならば 32。ブロックモードの設定については「3DS プログラミングマニュアル - グラフィックス応用編」を参照してください)の倍数の正値でなければなりません。

カラーバッファのオフセット位置からディスプレイバッファの幅と高さの領域がディスプレイバッファにコピーされます。カラーバッファの幅と高さからオフセットの値を減算したものが、カラーバッファからコピー可能な領域の幅と高さです。

コピーする領域の幅と高さのピクセル数は最小サイズの制限があります。カラーバッファからコピーする幅と高さの最小値は128です。ディスプレイバッファへコピーする幅と高さの最小値は、アンチエイリアスの設定に依存します。アンチエイリアスが無効の場合は幅と高さともに128、2x1アンチエイリアスが有効な場合は幅が64、高さが128、2x2アンチエイリアスが有効な場合は幅と高さともに64です。

表 3-14. nngxTransferRenderImage() が生成するエラー

エラー

原因

GL_ERROR_8027_DMP

オブジェクト名が 0 のコマンドリストがバインドされているときに呼び出した

GL_ERROR_8028_DMP

すでにコマンドリクエストの蓄積数が最大数に達していた

GL_ERROR_8029_DMP

有効なディスプレイバッファがバインドされていない

GL_ERROR_802A_DMP

有効なカラーバッファがバインドされていない

GL_ERROR_802B_DMP

mode に不正な値を指定した

GL_ERROR_802C_DMP

コピー可能な領域よりもディスプレイバッファのサイズが大きい(アンチエイリアス時は表 3-13 の幅と高さの倍率を適用)

GL_ERROR_802D_DMP

colorx colory に不正な値を指定した

GL_ERROR_802E_DMP

ディスプレイバッファのピクセル当たりのビット数がカラーバッファのものよりも大きい

GL_ERROR_802F_DMP

この関数が追加するコマンドにより 3D コマンドバッファが一杯になる

GL_ERROR_8059_DMP

ブロック 32 モードで、カラーバッファとディスプレイバッファの幅または高さが 32 の倍数でない

GL_ERROR_805A_DMP

ディスプレイバッファのピクセルサイズが 24 ビット、かつブロック 8 モードで、カラーバッファとディスプレイバッファの幅が 16 の倍数でない

GL_ERROR_80B5_DMP カラーバッファからコピーする幅または高さのピクセル数に最小値未満の値を指定した

GL_ERROR_80B6_DMP 

ディスプレイバッファへコピーする幅または高さのピクセル数に最小値未満の値を指定した

 

 

3.5. バッファスワップによる LCD 上の描画領域の更新

ディスプレイバッファへのコピー終了後に、バッファスワップ関数でレンダリング結果を LCD に表示します。

nngxActiveDisplay() でディスプレイを指定し、nngxBindDisplaybuffer() で表示に使用するディスプレイバッファを関連付けます。このとき、nngxDisplayEnv() でディスプレイバッファから LCD へ出力する際のオフセット(左下が原点、右上が正方向。正値のみ)を指定することができます。ディスプレイバッファのサイズが LCD のサイズと同じであれば (0, 0) を指定してください。オフセットに負の値を指定した場合は GL_ERROR_8026_DMP のエラーが生成されます。

コード 3-17. ディスプレイとディスプレイバッファを指定する関数
void nngxActiveDisplay(GLenum display);
void nngxBindDisplaybuffer(GLuint buffer);
void nngxDisplayEnv(GLint displayx, GLint displayy);

次に、バッファスワップ関数で LCD に出力するバッファを切り替えます。

コード 3-18. バッファスワップ関数
void nngxSwapBuffers(GLenum display);

nngxSwapBuffers() は、次の VSync 発生と同時にバッファのスワップを行います。任意のタイミングで呼び出すことができ、VSync 発生までに複数回呼び出された場合は最後の呼び出しが有効となります。

display にはバッファスワップの対象となるディスプレイを指定します。NN_GX_DISPLAY0 ならば上画面の LCD のみが、NN_GX_DISPLAY1 ならば下画面の LCD のみが、NN_GX_DISPLAY_BOTH ならば両方の LCD が対象となります。

この関数は、表示させるディスプレイバッファのアドレスを GPU に設定することで LCD に表示される画像を切り替えています。ディスプレイバッファのアドレスは、nngxDisplaybufferStorage() で確保されたバッファの先頭アドレスに、ディスプレイバッファの解像度、ピクセルサイズ、LCD の解像度、nngxDisplayEnv() で設定したオフセット値などを考慮して計算された値で、この値が最終的に GPU に設定されることになります。

設定されるアドレスは以下の式で計算されます。

BufferAddress + PixelSize × (DisplayBufferWidth × (DisplayBufferHeight - LcdHeight - DisplayY) + DisplayX)

表 3-15. nngxSwapBuffers() が生成するエラー

エラー

原因

GL_ERROR_8030_DMP

display に不正な値を指定した

GL_ERROR_8031_DMP

有効なディスプレイバッファバインドされていない

GL_ERROR_8032_DMP

オフセットを考慮した表示領域がディスプレイバッファ外になる

GL_ERROR_8053_DMP

GPU に設定されるディスプレイバッファのアドレスが 16 バイトのアライメントでない

GL_ERROR_9000_DMP

立体視表示時に右目用の上画面(NN_GX_DISPLAY0_EXT)にバインドされているディスプレイバッファが 0 またはその領域が確保されていない

GL_ERROR_9001_DMP

nngxDisplayEnv() で指定された表示領域がディスプレイバッファの外部を指している

GL_ERROR_9002_DMP

2 つの上画面(NN_GX_DISPLAY0NN_GX_DISPLAY0_EXT)にそれぞれバインドされているディスプレイバッファの解像度、フォーマット、メモリ領域が異なる

初期状態では LCD には黒画面が強制的に表示されるようになっています。バッファスワップ関数で LCD 出力に対して有効なディスプレイバッファを用意できたら、VSync 発生に合わせて nngxStartLcdDisplay() を呼び出して LCD 出力を開始してください。この関数の呼び出しが必要なのは最初の一回だけです。

コード 3-19. LCD 出力を開始する関数
void nngxStartLcdDisplay( void );

3.5.1. アドレス指定によるバッファスワップ

nngxSwapBuffersByAddress() は、ディスプレイバッファオブジェクトを使用せずに、指定したアドレスにあるバッファの内容を LCD に表示させることができます。

コード 3-20. アドレス指定によるバッファスワップ関数
void nngxSwapBuffersByAddress(GLenum display,
                              const GLvoid* addr, const GLvoid* addrB,
                              GLsizei width, GLenum format);

nngxSwapBuffers() と同様に、この関数を呼び出したあとに発生した最初の VSync でバッファが切り替わり、VSync の発生までに同じディスプレイに対する呼び出しを複数回行った場合は、最後の呼び出しが有効となります。

display にはバッファスワップの対象となるディスプレイを指定します。NN_GX_DISPLAY0 ならば上画面の LCD のみが、NN_GX_DISPLAY1 ならば下画面の LCD のみが対象となります。

addr には表示させるバッファのアドレスを指定します。立体視表示を有効にして上画面を対象に指定したときは、この引数に指定するアドレスは左目用に表示されるイメージのアドレスです。指定するアドレスは 16 バイトアライメントでなければなりません。

addrB には、立体視表示が有効なときに、右目用に表示されるイメージのアドレスを指定します。この引数は上画面を対象としているときにのみ有効です。立体視表示が無効なときや、displayNN_GX_DISPLAY1 を指定しているときは、この引数の指定は無視されます。指定するアドレスは 16 バイトアライメントでなければなりません。

この関数で指定されたバッファは、nngxDisplayEnv() による表示位置の指定を無視して表示されます。そのため addr および addrB には、オフセットなどを考慮したアドレスを指定してください。アドレスの計算方法については「3.5. バッファスワップによる LCD 上の描画領域の更新」を参照してください。

width には表示させるバッファの幅のピクセル数を指定します。width は LCD の幅ではなく、バッファの幅です。LCD の幅のピクセル数は上画面、下画面ともに 240 ですが、幅が 240 より大きい表示バッファを部分的に表示させる場合は、表示しない部分も含めた表示バッファ全体の幅のピクセル数を指定してください。width は 8 の倍数、かつ 240 以上の値でなければなりません。

表 3-16. nngxSwapBuffersByAddress() が生成するエラー

エラー

原因

GL_ERROR_8087_DMP

display に不正な値を指定した

GL_ERROR_8088_DMP

addr で指定されたアドレスが 16 バイトのアライメントでない

GL_ERROR_8089_DMP

addrB で指定されたアドレスが 16 バイトのアライメントでない

GL_ERROR_808A_DMP

width に不正な値を指定した

GL_ERROR_808B_DMP

format に指定可能なフォーマット以外の値を指定した

3.5.2. バッファスワップの詳細

nngxSwapBuffers()nngxSwapBuffersByAddress() によって行われるバッファスワップでは、ディスプレイバッファの内容を直接表示しているわけではありません。これらの関数は VSync 発生後に行われる LCD の描画に使用するディスプレイバッファのアドレス変更を予約するだけで、そのアドレス変更も VBlank 中に行われます。

ディスプレイバッファの内容の LCD への表示は GPU が行っています。LCD への表示の際には、スキャンラインに合わせてディスプレイバッファから 1 ライン分のデータを読み込んでいますので、VBlank 時以外は頻繁にメモリアクセスが発生しています。そのため、VBlank 時以外にディスプレイバッファの内容を書き換えてしまうと、ティアリングが発生してしまいます。

GPU がディスプレイバッファの内容を LCD に転送(表示)する際のメモリアクセスは、GPU 内では最優先で処理されますので、GPU のみがアクセス可能な VRAM 上にディスプレイバッファを配置している場合は、メモリアクセスの競合による問題は発生しません。しかし、メインメモリ(デバイスメモリ)上にディスプレイバッファを配置している場合は、CPU やそのほかのデバイスとの間でメモリアクセスが競合する可能性があります。

GPU と CPU、そのほかのデバイスからの、メインメモリへのアクセスの優先度は nngxSetMemAccessPrioMode() で調整することができます。

コード 3-21. メインメモリへのアクセスの優先度の調整関数
void nngxSetMemAccessPrioMode(nngxMemAccessPrioMode mode);
表 3-17. メインメモリへのアクセスの優先度の違い

mode

GPU

CPU

そのほか

NN_GX_MEM_ACCESS_PRIO_MODE_0

すべて均等

NN_GX_MEM_ACCESS_PRIO_MODE_1

 

優先

 

NN_GX_MEM_ACCESS_PRIO_MODE_2

 

強く優先

 

NN_GX_MEM_ACCESS_PRIO_MODE_3

優先

優先

 

NN_GX_MEM_ACCESS_PRIO_MODE_4

優先

 

 

デフォルトの設定は NN_GX_MEM_ACCESS_PRIO_MODE_1 です。

CPU のアクセスの優先度を上げると、CPU でのメインメモリアクセスを伴う処理にかかる時間が GPU やほかのデバイスの動作によって受ける影響を小さくすることができます。

注意:

ディスプレイバッファをメインメモリ上に配置している場合、NN_GX_MEM_ACCESS_PRIO_MODE_2 を指定して CPU から大量のメモリアクセスを発生させると、LCD 表示のための転送帯域が不足して画面に縦線状のノイズが発生することがあります。これを回避するためにはディスプレイバッファを VRAM 上に配置するか、ほかのモードを指定するようにしてください。

補足:

LCD 表示のための転送帯域が不足したときは、nngxGetCmdlistParameteri()pnameNN_GX_CMDLIST_HW_STATE を渡して取得したビット列のビット 18 とビット 17 に 1 が格納されています。nngxGetCmdlistParameteri() については、「4.1.10. パラメータ取得」を参照してください。

3.6. 画面更新の同期について

LCD の垂直同期(VSync)に合わせて処理を行う必要があるときには、以下の関数を利用することができます。

コード 3-22. VSync 関数
GLint nngxCheckVSync(GLenum display);
void nngxWaitVSync(GLenum display);
void nngxSetVSyncCallback(GLenum display, void (*func)(GLenum));

nngxCheckVSync() はライブラリ内部で更新している VSync カウンタの値を返します。この値の変化をチェックすることで非同期に VSync の更新をチェックすることができます。返される値はライブラリ内部の更新カウンタのため、実装の上限値(将来の実装で変更になる場合があります)を超えると 0 に戻ります。

nngxWaitVSync() は、指定された LCD の VSync が更新されるまで待ちます。呼び出し後は、VSync が更新されるまで制御が戻りません。

nngxSetVSyncCallback() は、VSync 更新のタイミングで呼び出されるコールバック関数を登録します。func に 0(NULL)を渡して呼び出した場合はコールバック関数の登録が解除されます。登録されたコールバック関数はメインスレッドとは異なるスレッドから呼び出されますので、メインスレッドと共有するデータを参照する場合は排他処理が必要です。ただし、同じグラフィックス関連でも nngxSetCmdlistCallback() によって登録された割り込みハンドラとの間では排他処理が不要となっています。

注意:

VSync のコールバック関数内で nngx 関数を呼び出すことは可能ですが、コールバック関数が終了するまでコマンドリクエストの終了割り込みが待たされることに注意してください。そのため、コールバック関数内では、コマンドリクエストを発行する関数の呼び出しはなるべく控えるようにしてください。

どの関数も displayNN_GX_DISPLAY0 を渡した場合は上画面 LCD を、NN_GX_DISPLAY1 を渡した場合は下画面 LCD を、NN_GX_DISPLAY_BOTH を渡した場合は両方の LCD を対象にして処理が行われます。それ以外の値を渡した場合、nngxCheckVSync()GL_ERROR_8019_DMP のエラーを、nngxWaitVSync()GL_ERROR_801A_DMP のエラーを、nngxSetVSyncCallback()GL_ERROR_801B_DMP のエラーを生成します。

システム側で極端な時間差が生じないように考慮していますが、VSync 更新タイミングには上下の画面間で約 100 マイクロ秒(システムコアの負荷状況により変動します)の時間差があります。ただし、上画面の VSync コールバック関数で重い処理を行ったり、優先度が非常に高いスレッドを起動していたりすることで下画面の VSync コールバックが待たされる可能性があるため、この時間差に過度に依存したコードを作成しないようにしてください。

液晶画面の VSync の間隔は上下画面ともに 59.831 Hz です。立体視表示(視差バリア)の有効/無効による変化はありません。

3.7. 終了処理

アプリケーションの終了時など、GX ライブラリの使用を終了するときは nngxFinalize() を呼び出してください。解放していないオブジェクトはすべて解放されます。

コード 3-23. nngxFinalize() の定義
void nngxFinalize(void);

LCD 表示のために確保したフレームバッファオブジェクトやレンダーバッファ、ディスプレイバッファを破棄する場合、フレームバッファオブジェクトの破棄には glDeleteFramebuffers() を、レンダーバッファの破棄には glDeleteRenderbuffers() を、ディスプレイバッファの破棄には nngxDeleteDisplaybuffers() をそれぞれ呼び出してください。

コード 3-24. フレームバッファオブジェクト、レンダーバッファ、ディスプレイバッファを破棄する関数
void glDeleteFramebuffers(GLsizei n, const GLuint* framebuffers);
void glDeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers);
void nngxDeleteDisplaybuffers(GLsizei n, GLuint* buffers);

どの関数も、n には第 2 引数に渡したオブジェクト配列の個数を指定します。nngxDeleteDisplaybuffers()n に負の値を指定した場合は GL_ERROR_801E_DMP のエラーが生成されます。

指定されたオブジェクトに使用中のものが含まれていた場合、フレームバッファオブジェクトとレンダーバッファについては使用中のオブジェクトに影響はなく、そのほかに指定されたオブジェクトは破棄されます。ディスプレイバッファはオブジェクト名が 0 のディスプレイバッファに切り替わり、使用中のオブジェクトの破棄が行われます。

3.8. 表示部分の指定について

カラーバッファからディスプレイバッファへの転送、ディスプレイバッファの LCD への表示において、表示部分の指定がどのように設定されるかを図 3-4 に示します。カラーバッファからディスプレイバッファへの転送は、アンチエイリアスの指定がオフであることを前提としています。

図 3-4. 表示部分の指定

表 3-18. 表示部分の指定
変数名 説明
cwch カラーバッファの幅と高さ
glRenderbufferStorage()widthheight で指定される値(コード 3-7
cx、cy

カラーバッファからディスプレイバッファへのコピーする際のオフセット
nngxTransferRenderImage()colorxcolory で指定される値(コード 3-16

dw、dh ディスプレイバッファの幅と高さ
nngxDisplaybufferStorage()widthheight で指定される値(コード 3-13
dx、dy

ディスプレイバッファから LCD へ出力する際のオフセット
nngxDisplayEnv()displayxdisplayy で指定される値(コード 3-17

lw、lh

出力先 LCD の幅と高さ、上画面と下画面で幅と高さは異なります。
3.1. LCD の解像度、配置方向について」を参照してください。