3DS では、LCD の表示と GX(グラフィックス)ライブラリが密接に関係しています。レンダリングから LCD に表示されるまでの流れは、図 3-1 になります。
工程を大まかに分けると、以下のようになります。
- レンダリング
- レンダーバッファからディスプレイバッファへのコピーとフォーマット変換
- バッファスワップによる LCD に表示される領域の更新
1. のレンダリングについては「4. コマンドリスト」以降の章で説明します。この章では、出力先となる LCD の仕様と、GX ライブラリを使用する際に必要な初期化処理を最初に説明します。続いて 2. と 3. について、バッファの確保・転送、画面更新の同期、終了処理を順を追って説明します。
立体視表示については「3DS プログラミングマニュアル - グラフィックス応用編」を参照してください。
3.1. LCD の解像度、配置方向について
3DS の LCD は、NITRO/TWL の LCD と解像度および配置の方向が異なっています。解像度の違いを表 3-1 に、配置方向の違いを図 3-2 に示します。
LCD |
NITRO/TWL |
3DS |
---|---|---|
上画面 LCD(幅×高さ) |
256 × 192 |
240 × 400 |
下画面 LCD(幅×高さ) |
256 × 192 |
240 × 320 |
NITRO/TWL では長辺が横方向(画面幅)、下画面を手前にしたときが正位置になるように配置されていましたが、3DS では短辺が横方向、下画面を上画面の左側に位置したとき(コントローラの正面から右に 90 度回り込んだ状態)が正位置になるように配置されています。
3.2. 必要な初期化処理
3DS ではフレームバッファアーキテクチャを採用しています。フレームバッファの内容をもとに LCD の表示が行われるため、最初に GX ライブラリの初期化を行わなければなりません。
3.2.1. GX ライブラリの初期化
GX ライブラリの初期化は nngxInitialize()
を呼び出して行います。
GLboolean nngxInitialize( GLvoid* (*allocator)(GLenum, GLenum, GLuint, GLsizei), void (deallocator)(GLenum, GLenum, GLuint, GLvoid)); GLboolean nngxGetIsInitialized();
nngxInitialize()
では、ディスプレイバッファなどのメモリ領域を確保する際に呼び出されるアロケータと、そのメモリ領域を解放する際に呼び出されるデアロケータを allocator
と deallocator
に指定します。アロケータとデアロケータについての詳細は「3.2.1.1. アロケータ」、「3.2.1.2. デアロケータ」を参照してください。
初期化に成功した場合は GL_TRUE
を、失敗した場合は GL_FALSE
を返します。初期化後に nngxFinalize()
で終了処理を行わずに、再度この関数を呼び出した場合は GL_FALSE
を返します。この関数で初期化を行う前に他の gl
、nngx
関数を呼び出した場合の動作は保証されていません。また、初期化でデフォルトのレンダーバッファなどは確保されません。初期化後にアプリケーションで確保する必要があります。
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 引数には確保するメモリ領域の使用目的(バッファ種別)が渡されます。種別によってアドレスのアライメントが異なりますので、アプリケーションは以下の規定に従って、アロケータを実装してください。
渡される値 |
バッファ種別 |
アライメント |
---|---|---|
|
テクスチャ |
全フォーマットとも 128 Byte |
|
頂点バッファ |
頂点属性によって異なります。 4 Byte( |
|
カラーバッファ |
64 Byte |
デプスバッファ |
32 Byte(16 bit デプスの場合) |
|
|
ディスプレイバッファ |
16 Byte |
|
3D コマンドバッファ |
16 Byte |
|
システム用バッファ |
4 Byte(確保サイズが 4 の倍数) |
上記の規定に加えて、以下のハードウェア仕様にも従って実装してください。
- キューブマップテクスチャの 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_VERTEXBUFFER
、NN_GX_MEM_RENDERBUFFER
、 NN_GX_MEM_DISPLAYBUFFER
、NN_GX_MEM_COMMANDBUFFER
が渡された場合に、それぞれのオブジェクトの名前(ID)が渡されます。
第 4 引数には、確保する領域のサイズが渡されます。
アプリケーションはこれらの引数を基に、アドレスのアライメントを考慮して指定されたメモリ領域を確保し、戻り値としてメモリ領域の先頭アドレスを返してください。領域の確保に失敗した場合は 0 を返してください。
3.2.1.2. デアロケータ
第 1 から第 3 までの引数に渡される値は、メモリ領域の確保で渡された値と同じです。第 4 引数にはメモリ領域の先頭アドレスが渡されます。アプリケーションはこれらの引数を基に、アロケータで確保したメモリ領域を解放してください。
3.2.1.3. アロケータの取得
nngxGetAllocator()
を呼び出して、GX ライブラリの初期化時に設定したアロケータを取得することができます。
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 メニューなどからアプリケーションに復帰した際に生じる描画関連の問題への対策用に追加されました。そのため、通常は使用する必要はありません。
GLsizei nngxGetInitializationCommand(GLsizei datasize, GLvoid* data);
data
には、レジスタ設定コマンドを受け取るコマンドバッファへのポインタを指定します。datasize
には、data
が指すバッファのバイトサイズを指定します。
この関数は、初期化用のレジスタ設定コマンドのバイトサイズを返します。data
に 0(NULL
)を指定すると、レジスタ設定コマンドの取得は行われませんが、確保すべきバッファサイズを返します。一度 data
に 0 を指定したあと、そのサイズのバッファを用意して、再度呼び出してコマンドを取得してください。
なお、nngxInitialize()
で初期化を行う前に呼び出した場合は、コマンドは取得されず、0 を返します。
エラー |
原因 |
---|---|
|
|
3.2.2. コマンドリストオブジェクトの作成
GX ライブラリの初期化後に行わなければならないのは、コマンドリストオブジェクトの作成です。コマンドリストオブジェクトは 3DS が独自に導入したもので、このオブジェクトを 3D グラフィックス処理の実行単位として扱います。ここでは、その作成方法のみを紹介します。コマンドリスト(オブジェクト)の詳細については「4. コマンドリスト」を参照してください。
まず、nngxGenCmdlists()
でコマンドリストオブジェクトを生成し、生成したコマンドリストオブジェクトを nngxBindCmdlist()
で GPU にバインド(関連付け)したあと、nngxCmdlistStorage()
でメモリ領域の確保を行います。
コマンドリストオブジェクトは 3D コマンドバッファとコマンドリクエストで構成されており、nngxCmdlistStorage()
の bufsize
で 3D コマンドバッファのサイズを、requestcount
でコマンドリクエストをキューイング可能な個数を指定します。コマンドリストオブジェクトを複数生成する場合は、それぞれのコマンドリストオブジェクトに対して nngxBindCmdlist()
と nngxCmdlistStorage()
を呼び出す必要があります。
以下のコード例では、3D コマンドバッファのサイズが 256 KByte、キューイング可能なコマンドリクエストの数を 128 個で指定したコマンドリストオブジェクトを 1 つ作成しています。
GLuint commandList; nngxGenCmdlists(1, &commandList); nngxBindCmdlist(commandList); nngxCmdlistStorage(256 * 1024, 128);
3.3. バッファの確保
グラフィックス処理で使用するバッファのうち LCD 出力に関係するのは、GPU のレンダーターゲットとなるフレームバッファとレンダリング結果をコピーして LCD への表示に使用するディスプレイバッファの 2 つです。フレームバッファとディスプレイバッファへの処理は、それぞれフレームバッファオブジェクト、ディスプレイバッファオブジェクトを用いて行われます。図 3-3 に構成を示します。
3.3.1. レンダーバッファの確保
最初に、レンダーターゲットの指定に使用するフレームバッファオブジェクトを glGenFramebuffers()
で生成しなければなりません。フレームバッファオブジェクトに関連付けることで、各レンダーバッファ(カラー、デプス、ステンシル)はレンダーターゲットに指定することができるようになります。3DS は上下 2 つの画面を搭載していますが、上下の画面でフォーマットが同じで平行してレンダリングする必要がない場合は、メモリリソースを節約するためにもオブジェクトを共有することを推奨します。その際、バッファの幅と高さは上画面用の設定を使用してください。
GLuint frameBufferObject; glGenFramebuffers(1, &frameBufferObject);
次に、glGenRenderbuffers()
でレンダーバッファオブジェクトを生成します。レンダーターゲットがカラーだけでなく、デプスまたはステンシル、もしくはその両方を含む場合は、フレームバッファオブジェクトに 2 つのレンダーバッファオブジェクトが必要になります。
GLuint renderBuffer[2]; glGenRenderbuffers(2, renderBuffer);
フレームバッファオブジェクトに関連付けるレンダーバッファオブジェクトを glBindRenderbuffer()
で指定し、glRenderbufferStorage()
でレンダーバッファを確保します。
void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
width
と height
にはレンダーバッファの幅と高さを指定します。幅と高さの最大値は、どちらも 1024 ピクセルです。
幅×高さが 262128 ピクセル以上のレンダーバッファでは、特定のピクセルにブロック状のノイズが描画される可能性があります。詳細については、「15.8. 特定のピクセルにブロック状のノイズが描画される」を参照してください。
target
に GL_RENDERBUFFER
と以下のビットマスクとの論理和を渡すことで、バッファを確保するメモリを指定することができます。ビットマスクを指定しなかった場合は、NN_GX_MEM_VRAMA
が指定されたものとして処理されます。
ビットマスク |
バッファの確保先 |
---|---|
|
VRAM-A |
|
VRAM-B |
internalformat
でバッファの種別(フォーマット)を指定しますが、3DS では以下のフォーマットから指定することができます。
フォーマット |
ビット数 |
フォーマットの詳細 |
---|---|---|
|
16 |
16 bit デプス |
|
24 |
24 bit デプス |
|
16 |
RGBA 各成分とも 4 bit |
|
16 |
RGB 各成分が 5 bit、アルファ成分が 1 bit |
|
16 |
RB 成分が各 5 bit、G 成分が 6 bit。アルファ成分なし |
|
32 |
RGBA 各成分とも 8 bit |
|
32 |
24 bit デプス、8 bit ステンシル |
|
32 |
ガスレンダリングで使用する密度情報(レンダリング結果をディスプレイバッファへコピーすることはできません) |
確保したレンダーバッファは、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
に指定する値はレンダーバッファのフォーマットによって異なります。
フォーマット |
attachment に指定する値 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
glCheckFramebufferStatus()
を呼び出すことで、フレームバッファオブジェクトに関連付けられたレンダーバッファオブジェクトの状態をチェックすることができます。引数には GL_FRAMEBUFFER
を指定してください。それ以外を指定すると、GL_INVALID_ENUM
のエラーが生成されます。
GL_FRAMEBUFFER_COMPLETE
が返されたときは、正しいレンダーバッファオブジェクトが関連付けられています。
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
が返されたときは、カラーバッファもデプス(ステンシル)バッファもアタッチされていません。
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT
が返されたときは、関連付けられているバッファのためのメモリが確保されていないか、カラーバッファとデプスバッファに同じレンダーバッファオブジェクトが関連付けられています。
GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS
が返されたときは、関連付けられているバッファのサイズがそれぞれで異なっています。
以下に、レンダーバッファの確保を行うコード例を示します。カラーとデプス(ステンシル)で設定が異なることに注意してください。上下の画面でレンダーバッファを共有するため、幅と高さの指定には上画面の設定を使用しています。
// 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()
で指定しなければなりません。
void nngxActiveDisplay(GLenum display);
display
に渡す値で、上下どちらかの画面を指定することができます。display
に下表以外の値を指定した場合は GL_ERROR_801F_DMP
のエラーを生成します。
display の値 |
指定される画面 |
---|---|
|
上画面(立体視表示時は左目用) |
|
上画面(立体視表示時のみ。右目用) |
|
下画面 |
立体視表示については「3DS プログラミングマニュアル - グラフィックス応用編」を参照してください。
次に、nngxGenDisplaybuffers()
でディスプレイバッファのオブジェクトを生成します。
void nngxGenDisplaybuffers(GLsizei n, GLuint* buffers);
n
には生成するディスプレイバッファのオブジェクト数を、buffers
にはディスプレイバッファのオブジェクトを格納する配列を指定します。マルチバッファリングで 1 つの画面に対して複数のディスプレイバッファを使用する場合は、オブジェクトを必要な数だけ生成する必要があります。
エラー |
原因 |
---|---|
|
|
|
管理領域の確保に失敗した |
生成したディスプレイバッファのオブジェクトを nngxBindDisplaybuffer()
でターゲットのディスプレイバッファに指定します。
void nngxBindDisplaybuffer(GLuint buffer);
buffer
に未使用のオブジェクト名が指定された場合はオブジェクトの生成が行われます。その際、管理領域の確保に失敗した場合は GL_ERROR_8020_DMP
のエラーを生成します。
nngxDisplaybufferStorage()
でディスプレイバッファを確保します。
void nngxDisplaybufferStorage(GLenum format, GLsizei width, GLsizei height, GLenum area);
format
で指定するバッファのフォーマットは以下から指定することができます。カラーバッファの確保で指定したフォーマットよりもピクセルあたりのビット数が大きいフォーマットは指定することができません。
フォーマット |
ビット数 |
フォーマットの詳細 |
---|---|---|
|
16 |
RGBA 各成分とも 4 bit |
|
16 |
RGB 各成分が 5 bit、アルファ成分が 1 bit |
|
16 |
RB 成分が各 5 bit、G 成分が 6 bit。アルファ成分なし |
|
24 |
RGB 各成分とも 8 bit。アルファ成分なし |
width
、height
にはディスプレイバッファのサイズを指定します。ともにブロックサイズ(デフォルトは 8)の倍数の正値でなければなりません。ブロックサイズはレンダーバッファのブロックモードを変更することで 8 または 32 となります。ブロックモードの設定については、「3DS プログラミングマニュアル - グラフィックス応用編」を参照してください。
area
にはバッファを確保するメモリを指定します。
フラグ |
バッファの確保先 |
---|---|
|
メインメモリ(デバイスメモリ) |
|
VRAM-A |
|
VRAM-B |
画面のキャプチャイメージを保存する場合は、CPU がアクセス可能なメインメモリにディスプレイバッファを確保する必要があります。
すでにディスプレイバッファが確保されているディスプレイバッファオブジェクトに対して、再度ディスプレイバッファを確保した場合は、すでに確保されているメモリ領域を解放して新しくメモリ領域を確保します。
エラー |
原因 |
---|---|
|
オブジェクト名が 0 のディスプレイバッファがターゲットになっているとき |
|
|
|
|
|
|
|
メモリの確保に失敗した |
ディスプレイバッファは複数確保することができます。以下のコード例ではダブルバッファリングを採用しています。
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()
で取得することができます。
void nngxGetDisplaybufferParameteri(GLenum pname, GLint* param);
pname
には以下の値を指定することができます。それ以外の値を指定した場合は GL_ERROR_8033_DMP
のエラーを生成します。
pname |
取得するパラメータの内容 |
---|---|
|
ディスプレイバッファの先頭アドレス。 |
|
ディスプレイバッファのフォーマット。 |
|
ディスプレイバッファの幅。 |
|
ディスプレイバッファの高さ。 |
3.4. カラーバッファからディスプレイバッファへのコピー
レンダリングが終了した時点ではカラーバッファの内容がブロックフォーマットであるため、リニアフォーマットでなければ表示することのできない LCD へは出力できません。LCD に出力可能なフォーマットへの変換と、glBindFramebuffer()
で指定したフレームバッファオブジェクトに関連付けられているカラーバッファからディスプレイバッファへのコピーを行う nngxTransferRenderImage()
を呼び出す必要があります。
void nngxTransferRenderImage(GLuint buffer, GLenum mode, GLboolean yflip, GLint colorx, GLint colory);
buffer
にはコピー先のディスプレイバッファのオブジェクトを指定します。3D コマンドバッファに区切られていないコマンドが蓄積されている場合は、区切りのコマンドを追加してから転送のコマンドをコマンドリクエストに追加します。この関数の実行直後は追加された転送コマンドの実行が完了しているとは限りませんので、カラーバッファを削除したり内容を書き換えたりする場合は、コマンドリストの実行が完了するまで待ってください。
mode
の設定で、レンダリング結果をディスプレイバッファにコピーする際に適用するアンチエイリアスを指定します。
mode の値 |
アンチエイリアスの指定 |
幅 |
高さ |
---|---|---|---|
|
オフ(なし) |
等倍 |
等倍 |
|
2x1 アンチエイリアス |
2 倍 |
等倍 |
|
2x2 アンチエイリアス |
2 倍 |
2 倍 |
yflip
の設定で、レンダリング結果をディスプレイバッファにコピーする際に Y フリップ(上下反転)を適用するかどうかを指定します。引数に GL_TRUE
を渡すと適用されます。0 以外の値は GL_TRUE
として扱われます。
colorx
と colory
にはカラーバッファからコピーする際のオフセット(左下が原点、右上が正方向)を指定します。オフセットに指定する値はブロックサイズ(ブロックモードがブロック 8 モードならば 8、ブロック 32 モードならば 32。ブロックモードの設定については「3DS プログラミングマニュアル - グラフィックス応用編」を参照してください)の倍数の正値でなければなりません。
カラーバッファのオフセット位置からディスプレイバッファの幅と高さの領域がディスプレイバッファにコピーされます。カラーバッファの幅と高さからオフセットの値を減算したものが、カラーバッファからコピー可能な領域の幅と高さです。
コピーする領域の幅と高さのピクセル数は最小サイズの制限があります。カラーバッファからコピーする幅と高さの最小値は128です。ディスプレイバッファへコピーする幅と高さの最小値は、アンチエイリアスの設定に依存します。アンチエイリアスが無効の場合は幅と高さともに128、2x1アンチエイリアスが有効な場合は幅が64、高さが128、2x2アンチエイリアスが有効な場合は幅と高さともに64です。
エラー |
原因 |
---|---|
|
オブジェクト名が 0 のコマンドリストがバインドされているときに呼び出した |
|
すでにコマンドリクエストの蓄積数が最大数に達していた |
|
有効なディスプレイバッファがバインドされていない |
|
有効なカラーバッファがバインドされていない |
|
|
|
コピー可能な領域よりもディスプレイバッファのサイズが大きい(アンチエイリアス時は表 3-13 の幅と高さの倍率を適用) |
|
|
|
ディスプレイバッファのピクセル当たりのビット数がカラーバッファのものよりも大きい |
|
この関数が追加するコマンドにより 3D コマンドバッファが一杯になる |
|
ブロック 32 モードで、カラーバッファとディスプレイバッファの幅または高さが 32 の倍数でない |
|
ディスプレイバッファのピクセルサイズが 24 ビット、かつブロック 8 モードで、カラーバッファとディスプレイバッファの幅が 16 の倍数でない |
GL_ERROR_80B5_DMP
|
カラーバッファからコピーする幅または高さのピクセル数に最小値未満の値を指定した |
|
ディスプレイバッファへコピーする幅または高さのピクセル数に最小値未満の値を指定した |
3.5. バッファスワップによる LCD 上の描画領域の更新
ディスプレイバッファへのコピー終了後に、バッファスワップ関数でレンダリング結果を LCD に表示します。
nngxActiveDisplay()
でディスプレイを指定し、nngxBindDisplaybuffer()
で表示に使用するディスプレイバッファを関連付けます。このとき、nngxDisplayEnv()
でディスプレイバッファから LCD へ出力する際のオフセット(左下が原点、右上が正方向。正値のみ)を指定することができます。ディスプレイバッファのサイズが LCD のサイズと同じであれば (0, 0) を指定してください。オフセットに負の値を指定した場合は GL_ERROR_8026_DMP
のエラーが生成されます。
void nngxActiveDisplay(GLenum display); void nngxBindDisplaybuffer(GLuint buffer); void nngxDisplayEnv(GLint displayx, GLint displayy);
次に、バッファスワップ関数で LCD に出力するバッファを切り替えます。
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)
エラー |
原因 |
---|---|
|
|
|
有効なディスプレイバッファバインドされていない |
|
オフセットを考慮した表示領域がディスプレイバッファ外になる |
|
GPU に設定されるディスプレイバッファのアドレスが 16 バイトのアライメントでない |
|
立体視表示時に右目用の上画面( |
|
|
|
2 つの上画面( |
初期状態では LCD には黒画面が強制的に表示されるようになっています。バッファスワップ関数で LCD 出力に対して有効なディスプレイバッファを用意できたら、VSync 発生に合わせて nngxStartLcdDisplay()
を呼び出して LCD 出力を開始してください。この関数の呼び出しが必要なのは最初の一回だけです。
void nngxStartLcdDisplay( void );
3.5.1. アドレス指定によるバッファスワップ
nngxSwapBuffersByAddress()
は、ディスプレイバッファオブジェクトを使用せずに、指定したアドレスにあるバッファの内容を LCD に表示させることができます。
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
には、立体視表示が有効なときに、右目用に表示されるイメージのアドレスを指定します。この引数は上画面を対象としているときにのみ有効です。立体視表示が無効なときや、display
に NN_GX_DISPLAY1
を指定しているときは、この引数の指定は無視されます。指定するアドレスは 16 バイトアライメントでなければなりません。
この関数で指定されたバッファは、nngxDisplayEnv()
による表示位置の指定を無視して表示されます。そのため addr
および addrB
には、オフセットなどを考慮したアドレスを指定してください。アドレスの計算方法については「3.5. バッファスワップによる LCD 上の描画領域の更新」を参照してください。
width
には表示させるバッファの幅のピクセル数を指定します。width
は LCD の幅ではなく、バッファの幅です。LCD の幅のピクセル数は上画面、下画面ともに 240 ですが、幅が 240 より大きい表示バッファを部分的に表示させる場合は、表示しない部分も含めた表示バッファ全体の幅のピクセル数を指定してください。width
は 8 の倍数、かつ 240 以上の値でなければなりません。
format
にはバッファのフォーマットを指定します。指定可能なフォーマットは、ディスプレイバッファを確保するときと同じです(表 3-9)。
エラー |
原因 |
---|---|
|
|
|
|
|
|
|
|
|
|
3.5.2. バッファスワップの詳細
nngxSwapBuffers()
や nngxSwapBuffersByAddress()
によって行われるバッファスワップでは、ディスプレイバッファの内容を直接表示しているわけではありません。これらの関数は VSync 発生後に行われる LCD の描画に使用するディスプレイバッファのアドレス変更を予約するだけで、そのアドレス変更も VBlank 中に行われます。
ディスプレイバッファの内容の LCD への表示は GPU が行っています。LCD への表示の際には、スキャンラインに合わせてディスプレイバッファから 1 ライン分のデータを読み込んでいますので、VBlank 時以外は頻繁にメモリアクセスが発生しています。そのため、VBlank 時以外にディスプレイバッファの内容を書き換えてしまうと、ティアリングが発生してしまいます。
GPU がディスプレイバッファの内容を LCD に転送(表示)する際のメモリアクセスは、GPU 内では最優先で処理されますので、GPU のみがアクセス可能な VRAM 上にディスプレイバッファを配置している場合は、メモリアクセスの競合による問題は発生しません。しかし、メインメモリ(デバイスメモリ)上にディスプレイバッファを配置している場合は、CPU やそのほかのデバイスとの間でメモリアクセスが競合する可能性があります。
GPU と CPU、そのほかのデバイスからの、メインメモリへのアクセスの優先度は nngxSetMemAccessPrioMode()
で調整することができます。
void nngxSetMemAccessPrioMode(nngxMemAccessPrioMode mode);
mode |
GPU |
CPU |
そのほか |
---|---|---|---|
|
すべて均等 |
||
|
|
優先 |
|
|
|
強く優先 |
|
|
優先 |
優先 |
|
|
優先 |
|
|
デフォルトの設定は NN_GX_MEM_ACCESS_PRIO_MODE_1
です。
CPU のアクセスの優先度を上げると、CPU でのメインメモリアクセスを伴う処理にかかる時間が GPU やほかのデバイスの動作によって受ける影響を小さくすることができます。
ディスプレイバッファをメインメモリ上に配置している場合、NN_GX_MEM_ACCESS_PRIO_MODE_2
を指定して CPU から大量のメモリアクセスを発生させると、LCD 表示のための転送帯域が不足して画面に縦線状のノイズが発生することがあります。これを回避するためにはディスプレイバッファを VRAM 上に配置するか、ほかのモードを指定するようにしてください。
LCD 表示のための転送帯域が不足したときは、nngxGetCmdlistParameteri()
の pname
に NN_GX_CMDLIST_HW_STATE
を渡して取得したビット列のビット 18 とビット 17 に 1 が格納されています。nngxGetCmdlistParameteri()
については、「4.1.10. パラメータ取得」を参照してください。
3.6. 画面更新の同期について
LCD の垂直同期(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
関数を呼び出すことは可能ですが、コールバック関数が終了するまでコマンドリクエストの終了割り込みが待たされることに注意してください。そのため、コールバック関数内では、コマンドリクエストを発行する関数の呼び出しはなるべく控えるようにしてください。
どの関数も display
に NN_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()
を呼び出してください。解放していないオブジェクトはすべて解放されます。
void nngxFinalize(void);
LCD 表示のために確保したフレームバッファオブジェクトやレンダーバッファ、ディスプレイバッファを破棄する場合、フレームバッファオブジェクトの破棄には glDeleteFramebuffers()
を、レンダーバッファの破棄には glDeleteRenderbuffers()
を、ディスプレイバッファの破棄には nngxDeleteDisplaybuffers()
をそれぞれ呼び出してください。
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 に示します。カラーバッファからディスプレイバッファへの転送は、アンチエイリアスの指定がオフであることを前提としています。
変数名 | 説明 |
---|---|
cw 、ch |
カラーバッファの幅と高さglRenderbufferStorage() の width 、height で指定される値(コード 3-7) |
cx、cy |
カラーバッファからディスプレイバッファへのコピーする際のオフセット |
dw、dh | ディスプレイバッファの幅と高さnngxDisplaybufferStorage() の width 、height で指定される値(コード 3-13) |
dx、dy |
ディスプレイバッファから LCD へ出力する際のオフセット |
lw、lh |
出力先 LCD の幅と高さ、上画面と下画面で幅と高さは異なります。 |