4. 文字の表示

文字を表示するには、フォントリソースから取得したグリフの情報をもとに表示する方法と、FONT ライブラリで用意されている文字描画クラスを利用する方法とがあります。

4.1. グリフの情報をもとに表示する

Font クラスの GetGlyph() は、グリフの情報を Glyph 構造体で返します。この構造体には、グリフを文字として表示するために必要な情報が格納されており、以下のように定義されています。

コード 4-1. Glyph 構造体
struct Glyph
{
    const void* pTexture;
    struct CharWidths
    {
        s8  left;
        u8  glyphWidth;
        s8  charWidth;
    } widths;
    u8      height;
    u16     texWidth;
    u16     texHeight;
    u16     cellX;
    u16     cellY;
    u8      isSheetUpdated;
    TexFmt  texFormat;
    const TextureObject* pTextureObject;
};

各メンバに格納されている値は以下のとおりです。幅や高さの値はピクセル単位で格納されています。

表 4-1. Glyph 構造体のメンバ
メンバ 説明
pTexture シート(テクスチャイメージ)へのポインタ。
widths.left 左スペース幅。
widths.glyphWidth グリフ幅。
widths.charWidth

文字幅。

右スペース幅は(文字幅 - グリフ幅 - 左スペース幅)で求めることができます。

height セル(グリフイメージ)の高さ。
texWidth シートの幅。
texHeight シートの高さ。
cellX シートの左上隅を原点としたときの、セル左上隅の X 座標。
cellY シートの左上隅を原点としたときの、セル左上隅の Y 座標。
isSheetUpdated シート更新フラグ。FONT ライブラリが参照します。
texFormat シートの(テクスチャ)フォーマット。
pTextureObject テクスチャオブジェクトなどの情報。FONT ライブラリが参照します。

シートのフォーマットの定義と glTexImage2D() の引数に指定すべき値の対応は以下のとおりです。

表 4-2. シートのフォーマットの定義と glTexImage2D() の引数に指定すべき値
フォーマット format type
FONT_SHEET_FORMAT_A4 GL_ALPHA_NATIVE_DMP GL_UNSIGNED_4BITS_DMP
FONT_SHEET_FORMAT_A8 GL_ALPHA_NATIVE_DMP GL_UNSIGNED_BYTE
FONT_SHEET_FORMAT_LA4 GL_LUMINANCE_ALPHA_NATIVE_DMP GL_UNSIGNED_BYTE_4_4_DMP
FONT_SHEET_FORMAT_LA8 GL_LUMINANCE_ALPHA_NATIVE_DMP GL_UNSIGNED_BYTE
FONT_SHEET_FORMAT_RGBA4 GL_RGBA_NATIVE_DMP GL_UNSIGNED_SHORT_4_4_4_4
FONT_SHEET_FORMAT_RGB5A1 GL_RGBA_NATIVE_DMP GL_UNSIGNED_SHORT_5_5_5_1
FONT_SHEET_FORMAT_RGBA8 GL_RGBA_NATIVE_DMP GL_UNSIGNED_BYTE
FONT_SHEET_FORMAT_RGB565 GL_RGB_NATIVE_DMP GL_UNSIGNED_SHORT_5_6_5
FONT_SHEET_FORMAT_RGB8 GL_RGB_NATIVE_DMP GL_UNSIGNED_BYTE

表示するグリフイメージの、シート内での位置を特定するために必要なメンバは以下のとおりです。

図 4-1. グリフイメージの位置を特定するために必要な Glyph 構造体のメンバ

pTexture cellX texWidth texHeight cellY height widths.glyphWidth

3DS のテクスチャ座標は左下が原点であるのに対し、セル(グリフイメージ)の座標が左上を原点としていることに注意が必要です。そのため、テクスチャ座標の計算は以下のサンプルコードのように実装します。

コード 4-2. テクスチャ座標の計算
Glyph glyph;
myFont.GetGlyph(&glyph, character);

const f32 texLeft   = 1.0f * glyph.cellX / glyph.texWidth;
const f32 texRight  = 1.0f * (glyph.cellX + glyph.widths.glyphWidth)
                      / glyph.texWidth;
// グリフイメージの座標とテクスチャ座標の原点の違いに注意すること
const f32 texTop    = 1.0f * (glyph.texHeight - glyph.cellY) / glyph.texHeight;
const f32 texBottom = 1.0f * (glyph.texHeight - (glyph.cellY + glyph.height))
                      / glyph.texHeight;

4.2. FONT ライブラリのクラスを利用する

FONT ライブラリでは、文字(列)を描画するためのクラスを用意しています。また、単純な文字の描画だけでなく、以下のような機能を有しています。

  • 文字色の変更
  • 文字ごとの縦または横方向のグラデーション
  • 文字の拡大縮小
  • 強制的な等幅フォント表示
  • 文字間、行間、タブ幅の処理
  • 左右寄せ
  • 文字列の自動改行
  • フォーマット文字列による描画(printf 相当)
  • タグ文字列の処理(カスタマイズ可能)

FONT ライブラリによる文字の描画で使用するクラスは複数あり、以下のように連携して処理を行っています。その中で、アプリケーションでインスタンスを生成し、描画のための設定を行うクラスは、下図で橙色の四角形に記述されているクラスです。ちなみに、青色が継承元のクラス、緑色が暗黙的に使用されるクラスです。

図 4-2. FONT ライブラリのクラス構成(描画部分)

(Wide)TextWriter (Wide)TagProcessor TextWriterBase CharWriter TagProcessorBase CharStrmReader DispStringBuffer RectDrawer Fontを継承したクラス フォント情報の取得 タグ文字の処理 文字の読み込み 文字列表示情報の蓄積 文字列表示情報をもとに描画

 

FONT ライブラリのクラスを利用して文字の描画を行う場合は、3D コマンド(PICA レジスタ情報)を直接生成する RectDrawer クラスの使用を推奨します。

表 4-3. 描画に使用するクラスで行われる処理
クラス 行われる処理
CharWriter 文字単体の描画を行います。TextWriter を使用しない場合は、このクラスを継承して、文字列の成形などを行ってください。
TextWriter 文字列の描画を行います。文字列を成形するための基本的な機能を備えています。
TagProcessor タグ文字(0x0000~0x001F の制御文字)の処理を行います。改行(0x000A)とタブ文字(0x0009)以外にも対応したい場合は、継承したクラスをカスタマイズしてください。
RectDrawer TextWriter(CharWriter)クラスが蓄積した、文字列表示情報から 3D コマンドを生成します。専用のシェーダプログラムに合わせた 3D コマンドを生成します。

4.2.1. アプリケーションでの実装手順

ここでは、FONT ライブラリのクラスを利用して文字列を画面に表示するまでに必要な手順を、サンプルデモに収録されている ResFont デモのソースを交えて説明します。

4.2.1.1. フォントリソースのロードと構築

文字の描画に使用するフォントリソースのロードおよび構築を最初に行います。サンプルデモでは、InitFont() に ResFont クラスのインスタンスへのポインタとフォントリソースのファイル名(拡張子は bcfnt)を渡しています。

コード 4-3. フォントリソースのロードと構築
//---------------------------------------------------------------------------
//! @brief      ResFontを構築します。
//!
//! @param[out] pFont 構築するフォントへのポインタ。
//! @param[in]  filePath ロードするフォントリソースファイル名。
//!
//! @return     ResFont構築の成否を返します。
//---------------------------------------------------------------------------
bool
InitFont(
    nn::font::ResFont*  pFont,
    const char*         filePath)
{
    // フォントリソースをロードします
    nn::fs::FileReader fontReader(filePath);

    s32 fileSize = (s32)fontReader.GetSize();
    if ( fileSize <= 0 )
    {
        return false;
    }

    void* buffer = s_AppHeap.Allocate(fileSize, nn::font::GlyphDataAlignment);
    if (buffer == NULL)
    {
        return false;
    }

    s32 readSize = fontReader.Read(buffer, fileSize);
    if (readSize != fileSize)
    {
        s_AppHeap.Free(buffer);
        return false;
    }

    // フォントリソースをセットします
    bool bSuccess = pFont->SetResource(buffer);
    NN_ASSERT(bSuccess);

    //--- 既にリソースをセット済みであるか,ロード済みであるか、
    //    リソースが不正な場合に失敗します。
    if (! bSuccess)
    {
        s_AppHeap.Free(buffer);
    }

    // 描画用バッファを設定します。
    const u32 drawBufferSize = nn::font::ResFont::GetDrawBufferSize(buffer);
    void* drawBuffer = s_AppHeap.Allocate(drawBufferSize, 4);
    NN_NULL_ASSERT(drawBuffer);
    pFont->SetDrawBuffer(drawBuffer);

    return bSuccess;
}

FONT ライブラリでは、ロードされたフォントリソースの、グリフイメージが描かれているシートに GPU が直接アクセスする設定でテクスチャをロードするため、フォントリソースがデバイスメモリ上の 128 Byte アライメントのバッファに読み込まれていなければならないことに注意してください。なお、描画用バッファは 4 Byte アライメントの制限のみで、バッファをデバイスメモリ上に確保しなくてもかまいません。

ResFont クラス以外のフォントリソースを使用した場合の制限については、「3.3. ArchiveFont クラス」や「3.4. PackedFont クラス」を参照してください。

4.2.1.2. シェーダの初期化

表示で使用するシェーダバイナリをロードし、RectDrawer クラスが使用するバッファの確保を行います。サンプルデモでは、InitShaders() に RectDrawer クラスのインスタンスへのポインタを渡しています。

コード 4-4. シェーダの初期化
//---------------------------------------------------------------------------
//! @brief          シェーダの初期化を行います。
//!
//! @param[in,out]  pDrawer 初期化するインスタンスへのポインタ。
//---------------------------------------------------------------------------
void*
InitShaders(nn::font::RectDrawer* pDrawer)
{
    nn::fs::FileReader shaderReader(s_ShaderBinaryFilePath);

    const u32 fileSize = (u32)shaderReader.GetSize();

    void* shaderBinary = s_AppHeap.Allocate(fileSize);
    NN_NULL_ASSERT(shaderBinary);

#ifndef NN_BUILD_RELEASE
    s32 read =
#endif // NN_BUILD_RELEASE
    shaderReader.Read(shaderBinary, fileSize);
    NN_ASSERT(read == fileSize);

    const u32 vtxBufCmdBufSize =
        nn::font::RectDrawer::GetVertexBufferCommandBufferSize(
            shaderBinary, fileSize);
    void *const vtxBufCmdBuf = s_AppHeap.Allocate(vtxBufCmdBufSize);
    NN_NULL_ASSERT(vtxBufCmdBuf);
    pDrawer->Initialize(vtxBufCmdBuf, shaderBinary, fileSize);

    s_AppHeap.Free(shaderBinary);

    return vtxBufCmdBuf;
}

RectDrawer クラスでは、頂点バッファとコマンドバッファの 2 つのバッファを使用します。頂点バッファには、描画のための頂点座標が格納されます。コマンドバッファには描画のコマンドが格納されますが、シェーダバイナリのロードコマンド(プログラム、Swizzle パターン、定数レジスタ)などが含まれます。これらのバッファは GPU が直接アクセスするため、デバイスメモリから確保しなければなりません。バッファのアライメントの指定は特にありませんが、4 Byte または 16 Byte にすることを推奨します。

サンプルデモでは、2 つのバッファを 1 つの領域で確保していますが、それぞれのバッファに必要なサイズを取得する関数や、バッファを分けて確保した場合の初期化関数が用意されています。初期化に成功したあとは、シェーダバイナリをロードしていたバッファを解放することができます。

SDK には、RectDrawer クラスで使用するシェーダバイナリとして nnfont_RectDrawerShader.shbin が収録されています。このシェーダは、射影行列にスクリーンの左上が原点でスクリーンと画面のサイズが一致する正射影行列(ただし、Y 軸と Z 軸の方向が 3DS とは逆です)、モデルビュー行列に単位行列を設定することを想定しています。文字の表示位置や大きさの調整には、変形用の行列を浮動小数点定数レジスタに蓄積して対応しています。

4.2.1.3. 表示文字列用バッファの確保

文字列表示情報を蓄積するバッファ(表示文字列用バッファ)を確保します。表示文字列用バッファに必要なサイズは最大の表示文字数から計算されます。文字列の描画が完了したあとならば、同じバッファを使って文字列表示情報の蓄積を行うことができます。フォントや表示する文字を変更しない場合は、蓄積していた文字列表示情報をそのまま再利用することができ、さらに単一色で文字列を描画していた場合は、再度文字列表示情報の蓄積を行わずに、文字色のみを変更することができます。

コード 4-5. 表示文字列用バッファの確保
//---------------------------------------------------------------------------
//! @brief      表示文字列用バッファを確保します。
//!
//! @param[in]  charMax 表示する文字列の最大文字数。
//!
//! @return     確保した表示文字列用バッファへのポインタを返します。
//---------------------------------------------------------------------------
nn::font::DispStringBuffer*
AllocDispStringBuffer(int charMax)
{
    const u32 DrawBufferSize =
        nn::font::CharWriter::GetDispStringBufferSize(charMax);
    void *const bufMem = s_AppHeap.Allocate(DrawBufferSize);
    NN_NULL_ASSERT(bufMem);

    return nn::font::CharWriter::InitDispStringBuffer(bufMem, charMax);
}

表示文字列用バッファはデバイスメモリ上に確保する必要はありません。バッファのアライメントの指定は特にありませんが、4 の倍数にすることを推奨します。

4.2.1.4. 描画設定

FONT ライブラリは描画のために必要最小限の設定を行いますので、以下の項目については、文字を描画する前にアプリケーションで適切に設定する必要があります。

  • カリング
  • シザーテスト
  • ポリゴンオフセット
  • アーリーデプステスト
  • デプステスト
  • ステンシルテスト
  • マスク処理
  • フレームバッファオブジェクト

アプリケーションで 3D モデル等を表示するためにカリングやデプステストを設定している場合、文字を描画する前に設定を変更しなければ、文字の描画に直前の設定が影響を与えることになります。
サンプルデモでは、InitDraw() で描画設定を行うコマンドを生成しています。

コード 4-6. 描画設定
//---------------------------------------------------------------------------
//! @brief      描画の初期設定を行います。
//!
//! @param[in]  width   画面の幅。
//! @param[in]  height  画面の高さ。
//---------------------------------------------------------------------------
void
InitDraw(
    int width,
    int height
)
{
    // カラーバッファ情報
    // LCDの向きに合わせて、幅と高さを入れ替えています。
    const nn::font::ColorBufferInfo colBufInfo =
    { width, height, PICA_DATA_DEPTH24_STENCIL8_EXT };

    const u32 screenSettingCommands[] =
    {
        // ビューポートの設定
        NN_FONT_CMD_SET_VIEWPORT( 0, 0, colBufInfo.width, colBufInfo.height ),

        // シザー処理を無効
        NN_FONT_CMD_SET_DISABLE_SCISSOR( colBufInfo ),

        // wバッファの無効化
        // デプスレンジの設定
        // ポリゴンオフセットの無効化
        NN_FONT_CMD_SET_WBUFFER_DEPTHRANGE_POLYGONOFFSET(
            0.0f,   // wScale : 0.0 でWバッファが無効
            0.0f,   // depth range near
            1.0f,   // depth range far
            0,      // polygon offset units : 0.0 でポリゴンオフセットが無効
            colBufInfo),
    };
    nngxAdd3DCommand(screenSettingCommands, sizeof(screenSettingCommands), true);

    static const u32 s_InitCommands[] =
    {
        // カリングを無効
        NN_FONT_CMD_SET_CULL_FACE( NN_FONT_CMD_CULL_FACE_DISABLE ),

        // ステンシルテストを無効
        NN_FONT_CMD_SET_DISABLE_STENCIL_TEST(),

        // デプステストを無効
        // カラーバッファの全ての成分を書き込み可
        NN_FONT_CMD_SET_DEPTH_FUNC_COLOR_MASK(
            false,  // isDepthTestEnabled
            0,      // depthFunc
            true,   // depthMask
            true,   // red
            true,   // green
            true,   // blue
            true),  // alpha

        // アーリーデプステストを無効
        NN_FONT_CMD_SET_ENABLE_EARLY_DEPTH_TEST( false ),

        // フレームバッファアクセス制御
        NN_FONT_CMD_SET_FBACCESS(
            true,   // colorRead
            true,   // colorWrite
            false,  // depthRead
            false,  // depthWrite
            false,  // stencilRead
            false), // stencilWrite
    };
    nngxAdd3DCommand(s_InitCommands, sizeof(s_InitCommands), true);
}

4.2.1.5. 描画ごとに行う処理

ここまでに行った処理は初期化にあたり、ここでは描画ごとに行う処理を説明します。

表示位置や文字色を指定した文字列の描画は (Wide)TextWriter クラスで行います。このクラスは、フレームごとにインスタンスを生成することを想定していますので、明示的な初期化関数を持っていません。デフォルトでタグ文字の処理を行うクラスも設定されますので、独自のタグ文字を処理する必要がない限りは、生成したインスタンスをそのまま使用することができます。また、文字列の書式を展開するバッファを設定していない場合は、書式つきの Printf() を呼び出したときに、スタックから 256 文字分のメモリが確保されます。スタックを消費したくない場合や、256 文字分以上のバッファが必要な場合はアプリケーションで確保し、SetBuffer() で設定してください。返り値には以前に設定されていたバッファへのポインタが返されます。

描画された文字列は、文字列表示情報として 1 文字ずつ蓄積されますので、「4.2.1.3. 表示文字列用バッファの確保」で確保した文字列情報を蓄積するバッファを SetDispStringBuffer() で TextWriter クラスに設定してください。描画に使用するフォントリソースは SetFont() で設定します。

表示位置(カーソル位置)の設定は SetCursor() または MoveCursor() で行います。前者は新しい表示位置そのままを指定し、後者は差分で表示位置を指定します。X, Y, Z 座標それぞれを個別に設定することも、まとめて設定することもできます。デフォルトの設定は (0.0, 0.0, 0.0) です。

表示位置を基準にして、文字列をどのように配置するのかは SetDrawFlag() で設定します。文字列の配置は、文字列を描画する矩形領域の原点の配置を指定するフラグ(水平・垂直方向)と、文字列を寄せる方向を指定するフラグ(水平方向のみ)との論理和で指定します。フラグは nn::font::PositionFlag 列挙子で定義されています。デフォルトの設定は、HORIZONTAL_ALIGN_LEFT | HORIZONTAL_ORIGIN_LEFT | VERTICAL_ORIGIN_TOP で、下図はデフォルト設定に対して各フラグを変更したときの配置の変化を示したものです。

図 4-3. フラグの変更による配置の変化

あいう ghij 描画開始時のカーソル位置 描画終了時のカーソル位置 文字揃えの基準 ベースライン HORIZONTAL_ALIGN_LEFT HORIZONTAL_ALIGN_CENTER HORIZONTAL_ALIGN_RIGHT HORIZONTAL_ORIGIN_LEFT HORIZONTAL_ORIGIN_CENTER HORIZONTAL_ORIGIN_RIGHT VERTICAL_ORIGIN_TOP VERTICAL_ORIGIN_BASELINE VERTICAL_ORIGIN_MIDDLE VERTICAL_ORIGIN_BOTTOM

文字の拡大縮小は SetScale() または SetFontSize() で行います。前者はフォントの幅と高さを基準とする倍率で指定し、後者は表示する文字の幅と高さをピクセルで指定します。拡大縮小はアセントとディセントにも影響を及ぼします。そのため、GetFontAscent()GetFontDescent() は、フォントの持つアセントやディセントとは異なる値を返す可能性があります。デフォルトの設定は等倍です。

文字色の設定は SetTextColor() で行います。また、SetGradationMode() で水平・垂直方向にグラデーション効果を与えることができます。グラデーション効果に対しては、SetColorMapping() で線形変換を設定することができ、設定によっては色の変化を逆転させることが可能です。なお、SetAlpha() で文字色とは別に、追加のアルファ値を設定することができます。デフォルトの設定は、文字色が (255, 255, 255, 255)、グラデーション効果なし、線形変換なし、追加のアルファ値が 255 です。

1 行の高さの設定は SetLineHeight() で行います。この設定は改行幅ではなく、行間を自動的に調整します。行間を直接設定する場合は SetLineSpace() で行ってください。そのほか、タブ幅の設定は SetTabWidth()、文字間の設定は SetCharSpace() でそれぞれ行います。デフォルトの設定は、行間と文字間が 0、タブ幅は 4 文字です。

フォントの設定に関係なく、強制的に文字列を等幅で描画する場合は SetFixedWidth() で描画幅を設定し、EnableFixedWidth(true) で強制等幅描画を有効に設定してください。EnableFixedWidth()false を指定すると強制等幅描画を無効に戻すことができます。デフォルトの設定は無効です。

ここまでの設定は文字の表示に関する設定であり、TextWriter クラスに対して行っていました。文字を描画するためのグラフィックス関連の設定は RectDrawer クラスに対して行います。

DrawBegin() で描画を開始するための初期化を行います。描画モードやテクスチャなどを初期化するための描画コマンドが生成され、内部変数などの初期化も行いますので、SetParallax() で視差を設定する場合はこの関数を呼び出したあとで設定しなければなりません。

射影行列とビュー行列の設定は SetProjectionMtx()SetViewMtxForText() で行います。ライブラリが想定している射影行列は左上原点、スクリーンサイズと画面サイズが一致し、Y 軸と Z 軸の正方向が CTR の座標系と逆転している行列です。ビュー行列は単位行列を想定しています。

文字列の描画は TextWriter クラスで行います。文字を描画すると、文字列表示情報として表示文字列用バッファに一旦蓄積され、蓄積された情報から RectDrawer クラスが描画コマンドを生成します。

TextWriter::StartPrint() で文字列描画の開始を宣言します。このとき、表示文字列用バッファがクリアされます。

TextWriter::Print() または TextWriter::Printf() で文字列を描画します。前者は書式文字列の指定なし、後者は書式文字列の指定ありです。

文字列の描画が完了したら、TextWriter::EndPrint() で描画完了を宣言します。そのあとに、RectDrawer::BuildTextCommand() で蓄積された情報をもとに描画コマンドを生成し、TextWriter::UseCommandBuffer() で描画コマンドをコマンドバッファへ送ります。

最後に RectDrawe::DrawEnd() を呼び出すことで描画コマンドのキックなどが行われ、レンダーバッファに文字が描画されます。

描画する文字列の内容や表示位置に変更がない場合は、TextWriter::StartPrint() から TextWriter::EndPrint()RectDrawer::BuildTextCommand() を再度実行することなく、同じ描画結果を得ることができます。さらに、単一色で描画していた場合は、文字色のみを変更することもできます。ただし、これは蓄積された文字列表示情報が保持されている場合に限ります。

以下のコードは、サンプルデモの該当部分です。

コード 4-7. 行列の設定と文字列の描画
//---------------------------------------------------------------------------
//! @brief      文字列表示用にモデルビュー行列と射影行列を設定します。
//!
//! @param[in]  pDrawer RectDrawerオブジェクトへのポインタ。
//! @param[in]  width   画面の幅。
//! @param[in]  height  画面の高さ。
//---------------------------------------------------------------------------
void
SetupTextCamera(
    nn::font::RectDrawer*   pDrawer,
    int                     width,
    int                     height
)
{
    // 射影行列を正射影に設定
    {
        // 左上原点とし、Y軸とZ軸の向きが逆になるように設定します。
        nn::math::MTX44 proj;
        f32 znear   = 0.0f;
        f32 zfar    = -1.0f;
        f32 t       = 0;
        f32 b       = static_cast<f32>(width);
        f32 l       = 0;
        f32 r       = static_cast<f32>(height);
        nn::math::MTX44OrthoPivot(
            &proj, l, r, b, t, znear, zfar, nn::math::PIVOT_UPSIDE_TO_TOP);
        pDrawer->SetProjectionMtx(proj);
    }

    // モデルビュー行列を単位行列に設定
    {
        nn::math::MTX34 mv;
        nn::math::MTX34Identity(&mv);
        pDrawer->SetViewMtxForText(mv);
    }
}

//---------------------------------------------------------------------------
//! @brief ASCII文字列を描画します。
//!
//! @param[in]  pDrawer         RectDrawerオブジェクトへのポインタ。
//! @param[in]  pDrawStringBuf  DispStringBufferオブジェクトへのポインタ。
//! @param[in]  pFont           フォントへのポインタ。
//! @param[in]  width           画面の幅。
//! @param[in]  height          画面の高さ。
//---------------------------------------------------------------------------
void
DrawAscii(
    nn::font::RectDrawer*       pDrawer,
    nn::font::DispStringBuffer* pDrawStringBuf,
    nn::font::ResFont*          pFont,
    int                         width,
    int                         height
)
{
    nn::font::TextWriter writer;
    writer.SetDispStringBuffer(pDrawStringBuf);
    writer.SetFont(pFont);
    writer.SetCursor(0, 0);

    // 文字列が変更されないので、文字列の描画コマンドを一度だけ作成します。
    if (! s_InitAsciiString)
    {
        writer.StartPrint();
            (void)writer.Print("DEMO: ResFont\n");
            (void)writer.Print("\n");
            // ASCIIの文字見本を表示
            (void)writer.Print("All ASCII Character listing:\n");
            (void)writer.Print("\n");
            (void)writer.Print(" !\"#$%&'()*+,-./\n");
            (void)writer.Print("0123456789:;<=>?\n");
            (void)writer.Print("@ABCDEFGHIJKLMNO\n");
            (void)writer.Print("PQRSTUVWXYZ[\\\]^_\n");
            (void)writer.Print("`abcdefghijklmno\n");
            (void)writer.Print("pqrstuvwxyz{|}~\n");
        writer.EndPrint();
        pDrawer->BuildTextCommand(&writer);

        s_InitAsciiString = true;
    }

    // 文字の色は、文字列の描画コマンドを再作成しなくても変更できます。
    writer.SetTextColor(nn::util::Color8(s_Color, 255, s_Color, 255));
    s_Color++;

    pDrawer->DrawBegin();
        SetupTextCamera(pDrawer, width, height);
        writer.UseCommandBuffer();
    pDrawer->DrawEnd();
}

4.2.1.6. ライブラリによる GPU 設定の変化

RectDrawer クラスを利用してフォントを描画すると、テクスチャのサンプラータイプや頂点属性のロードアドレスなど、様々な GPU 設定が変更されます。そのため、フォントを描画したあとは GPU のすべてのステートをバリデートし、アプリケーションで GPU 設定をすべてやり直すことを推奨していますが、これは冗長な処理となる可能性があります。

ここでは、フォントの描画時に行われる GPU 設定を、GPU の再設定にかかるコストを下げるための参考資料として紹介します。

コンバイナの設定

コンバイナは 3 ~ 5 を使用し、以下のように設定されています。シートのテクスチャフォーマットがアルファ成分を持つものかどうかで、一部の設定が異なります。ちなみに、文字色の表現は頂点カラーの設定で行われています。

表 4-4. コンバイナ 3 の設定
設定 カラー アルファ
ソース 0 GL_TEXTURE0 GL_TEXTURE0
ソース 1 GL_CONSTANT GL_CONSTANT
ソース 2 GL_CONSTANT GL_CONSTANT
オペランド 0

(アルファなし)GL_SRC_COLOR

(アルファあり)GL_ONE_MINUS_SRC_COLOR

GL_SRC_ALPHA
オペランド 1 GL_SRC_COLOR GL_SRC_ALPHA
オペランド 2 GL_SRC_COLOR GL_SRC_ALPHA
コンバイン GL_MODULATE GL_MODULATE
スケール 1.0 1.0
定数カラー 白(1.0, 1.0, 1.0, 1.0)
表 4-5. コンバイナ 4 の設定
設定 カラー アルファ
ソース 0 GL_TEXTURE0 GL_TEXTURE0
ソース 1 GL_CONSTANT GL_CONSTANT
ソース 2 GL_PREVIOUS GL_PREVIOUS
オペランド 0

(アルファなし)GL_ONE_MINUS_SRC_COLOR

(アルファあり)GL_SRC_COLOR

GL_ONE_MINUS_SRC_ALPHA
オペランド 1 GL_SRC_COLOR GL_SRC_ALPHA
オペランド 2 GL_SRC_COLOR GL_SRC_ALPHA
コンバイン GL_MULT_ADD_DMP GL_MULT_ADD_DMP
スケール 1.0 1.0
定数カラー 黒(0.0, 0.0, 0.0, 0.0)
表 4-6. コンバイナ 5 の設定
設定 カラー アルファ
ソース 0 GL_PRIMARY_COLOR GL_PRIMARY_COLOR
ソース 1 GL_PREVIOUS GL_PREVIOUS
ソース 2 GL_PREVIOUS GL_PREVIOUS
オペランド 0 GL_SRC_COLOR GL_SRC_ALPHA
オペランド 1 GL_SRC_COLOR GL_SRC_ALPHA
オペランド 2 GL_SRC_COLOR GL_SRC_ALPHA
コンバイン GL_MODULATE GL_MODULATE
スケール 1.0 1.0
定数カラー ※ 未設定のため不定
予約フラグメントシェーダの設定

予約フラグメントシェーダのうち、以下の機能の設定が変更されます。

表 4-7. 予約フラグメントシェーダの設定
機能 設定
フラグメントオペレーションモード 標準モード(GL_FRAGOP_MODE_GL_DMP
フラグメントライティング 無効
シャドウ(シャドウテクスチャ) 無効
フォグ(ガス) 無効
アルファテスト 無効
ブレンディング 有効
テクスチャの設定

テクスチャ 0 のみを使用し、GL_TEXTURE_2D に設定します。テクスチャイメージのアドレスや解像度、フィルタ設定などが変更されます。

シェーダの設定

整数レジスタ i0 と浮動小数点定数レジスタ c0~c95 に、描画に必要なデータを設定しています。

シェーダプログラムは、頂点座標、頂点カラー、テクスチャ座標 0、テクスチャ座標 1、テクスチャ座標 2 を出力していますが、ジオメトリシェーダを使用することはできません。

4.2.2. タグ文字の処理をカスタマイズする

nn::font::TagProcessorBase クラスを継承したクラスをカスタマイズすることで、アプリケーション独自のタグ文字(0x0000~0x001F)の処理を実装することができます。

文字列を描画している途中に描画すべき文字としてタグ文字が現れたとき、TextWriter クラスは自身に設定されている TagProcessor クラスにタグ文字の処理を行わせます。TextWriter クラスには、タブ(0x0009)と改行(0x000A)を処理する TagProcessor クラスがデフォルトで設定されていますが、SetTagProcessor() で独自の TagProcessor クラスを使用するように設定することができます。

タグ文字の処理をカスタマイズする場合は、Process()CalcRect() をオーバーライドします。

コード 4-8. タグ文字の処理をカスタマイズする際にオーバーライドする関数
virtual Operation Process( u16                      code,
                           PrintContext<CharType>*  pContext);
virtual Operation CalcRect(util::Rect*              pRect,
                           u16                      code,
                           PrintContext<CharType>*  pContext);

これらの関数で返した値により、TextWriter クラスは文字を描画する座標の調整を行います。同じタグ文字に対して、Process()CalcRect() が同じ返り値となるように実装してください。

表 4-8. タグ文字の処理で返す値の定義
定義 TextWriter 側で行われる処理
OPERATION_DEFAULT 次の文字との文字間を、行頭ならば空けず、行頭以外ならば空けます。
OPERATION_NO_CHAR_SPACE 次の文字との文字間を必ず空けません。
OPERATION_CHAR_SPACE 次の文字との文字間を必ず空けます。
OPERATION_NEXT_LINE 改行時の処理を行います。X 座標のみが調整され、Y 座標の調整はタグの処理中に行う必要があります。
OPERATION_END_DRAW 文字列の途中で文字列の描画を終了します。

引数 pContext のメンバには、TextWriter クラスへのポインタがありますので、タグ文字によって文字色を変更するなどの処理を行うことができます。