トップページ > 記事閲覧
フォントによってDrawStringToHandleの描画時間が異なる原因を教えて下さい
名前:とらふずく 日時: 2022/07/07 02:01

お世話になっています。 DrawStringToHandleで文字を描画するときに、使うフォントによって描画時間が大幅に異なります。 後述するIPA明朝はエッジありアンチエイリアスありでも高速に描画されますが、他のフォントはかなり遅くなります。 フォントによって遅くなる原因について教えて頂けないでしょうか。 また遅いフォントを早く描画するテクニックがあれば教えて頂けないでしょうか。 後述のコードでそれぞれのフォント、フォントタイプ設定での描画時間を計測した結果が以下になります。 NotoSansJPと源様明朝はEDGEやANTIALIASINGの設定をすると遅くなる傾向にありますが、IPA明朝のみ設定に関わらず高速です。 IPA明朝が遅くならない理由が知りたいです。理由がわかれば、遅くならないフォントを探す手掛かりにしたいです。 ○検証結果(数値はミリ秒) NotoSansJP DX_FONTTYPE_NORMAL: 27 NotoSansJP DX_FONTTYPE_EDGE: 34 NotoSansJP DX_FONTTYPE_ANTIALIASING: 53 NotoSansJP DX_FONTTYPE_ANTIALIASING_4X4: 33 NotoSansJP DX_FONTTYPE_ANTIALIASING_8X8: 37 NotoSansJP DX_FONTTYPE_ANTIALIASING_EDGE: 82 NotoSansJP DX_FONTTYPE_ANTIALIASING_EDGE_4X4: 64 NotoSansJP DX_FONTTYPE_ANTIALIASING_EDGE_8X8: 69 IPA明朝 DX_FONTTYPE_NORMAL: 20 IPA明朝 DX_FONTTYPE_EDGE: 19 IPA明朝 DX_FONTTYPE_ANTIALIASING: 19 IPA明朝 DX_FONTTYPE_ANTIALIASING_4X4: 21 IPA明朝 DX_FONTTYPE_ANTIALIASING_8X8: 19 IPA明朝 DX_FONTTYPE_ANTIALIASING_EDGE: 22 IPA明朝 DX_FONTTYPE_ANTIALIASING_EDGE_4X4: 19 IPA明朝 DX_FONTTYPE_ANTIALIASING_EDGE_8X8: 19 源様明朝 DX_FONTTYPE_NORMAL: 24 源様明朝 DX_FONTTYPE_EDGE: 33 源様明朝 DX_FONTTYPE_ANTIALIASING: 36 源様明朝 DX_FONTTYPE_ANTIALIASING_4X4: 37 源様明朝 DX_FONTTYPE_ANTIALIASING_8X8: 39 源様明朝 DX_FONTTYPE_ANTIALIASING_EDGE: 65 源様明朝 DX_FONTTYPE_ANTIALIASING_EDGE_4X4: 61 源様明朝 DX_FONTTYPE_ANTIALIASING_EDGE_8X8: 71 ○検証フォント(全部フリーフォントです) NotoSansJP(otf)  //fonts.google.com/noto/specimen/Noto+Sans+JP?subset=japanese IPA明朝(ttf)  //moji.or.jp/ipafont/ipa00303/ 源様明朝(ttf)  //github.com/ButTaiwan/genyo-font ※URLの先頭文字は削除しています ○検証関数 DrawStringToHandle サイズ:40 厚み:3 タイプ: DX_FONTTYPE_NORMAL DX_FONTTYPE_EDGE DX_FONTTYPE_ANTIALIASING DX_FONTTYPE_ANTIALIASING_4X4 DX_FONTTYPE_ANTIALIASING_8X8 DX_FONTTYPE_ANTIALIASING_EDGE DX_FONTTYPE_ANTIALIASING_EDGE_4X4 DX_FONTTYPE_ANTIALIASING_EDGE_8X8 ○検証環境 Windows10 C++版 DXLib_Ver3.22a VisualStudio2019 リリース版で検証 ○検証コード void TextDraw(int font, char drawTexts [16][200]) { int loopCount = 0; while (!ProcessMessage()) { ClearDrawScreen(); for (int ii = 0; ii < 16; ii++) { DrawStringToHandle(10, 10 + ii * 45, drawTexts[ii], GetColor(255, 255, 255), font); } loopCount++; if (loopCount >= 10) { break; } ScreenFlip(); clsDx(); } } void DXlibQA() { SetGraphMode(1280, 720, 32); ChangeWindowMode(true); SetDrawScreen(DX_SCREEN_BACK); SetDrawMode(DX_DRAWMODE_BILINEAR); if (DxLib_Init() == -1) {//DXライブラリ初期化 return ; } char drawTexts[][200] = { "吾輩わがはいは猫である。名前はまだ無い。", "どこで生れたかとんと見当けんとうがつかぬ。", "何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。", "吾輩はここで始めて人間というものを見た。", "しかもあとで聞くとそれは書生という人間中で一番獰悪どうあくな種族であったそうだ。", "この書生というのは時々我々を捕つかまえて煮にて食うという話である。", "しかしその当時は何という考もなかったから別段恐しいとも思わなかった。", "ただ彼の掌てのひらに載せられてスーと持ち上げられた時何だかフワフワした感じがあったばかりである。", "掌の上で少し落ちついて書生の顔を見たのがいわゆる人間というものの見始みはじめであろう。", "この時妙なものだと思った感じが今でも残っている。", "第一毛をもって装飾されべきはずの顔がつるつるしてまるで薬缶やかんだ。", "その後ご猫にもだいぶ逢あったがこんな片輪かたわには一度も出会でくわした事がない。", "のみならず顔の真中があまりに突起している。", "そうしてその穴の中から時々ぷうぷうと煙けむりを吹く。", "どうも咽むせぽくて実に弱った。", "これが人間の飲む煙草たばこというものである事はようやくこの頃知った。", }; int startTime; int endTime; FILE *fp = fopen("test.txt", "w"); int type[] = { DX_FONTTYPE_NORMAL, DX_FONTTYPE_EDGE, DX_FONTTYPE_ANTIALIASING, DX_FONTTYPE_ANTIALIASING_4X4, DX_FONTTYPE_ANTIALIASING_8X8, DX_FONTTYPE_ANTIALIASING_EDGE, DX_FONTTYPE_ANTIALIASING_EDGE_4X4, DX_FONTTYPE_ANTIALIASING_EDGE_8X8, }; char typestr[][80] = { "DX_FONTTYPE_NORMAL", "DX_FONTTYPE_EDGE", "DX_FONTTYPE_ANTIALIASING", "DX_FONTTYPE_ANTIALIASING_4X4", "DX_FONTTYPE_ANTIALIASING_8X8", "DX_FONTTYPE_ANTIALIASING_EDGE", "DX_FONTTYPE_ANTIALIASING_EDGE_4X4", "DX_FONTTYPE_ANTIALIASING_EDGE_8X8", }; //------------------------------------------------------------------------------------------ // フォント その1 //------------------------------------------------------------------------------------------ for (int ii = 0; ii < 8; ii++) { LPCSTR font_path1 = "GameData1\\font\\NotoSansJP.otf"; AddFontResourceEx(font_path1, FR_PRIVATE, nullptr); int font1 = CreateFontToHandle("Noto Sans JP", 40, 3, type[ii], -1, -1); startTime = GetNowCount(); TextDraw(font1, drawTexts); endTime = GetNowCount(); fprintf(fp, "NotoSansJP %s: % d\n", typestr[ii], (endTime - startTime) / 10); } //------------------------------------------------------------------------------------------ // フォント その2 //------------------------------------------------------------------------------------------ for (int ii = 0; ii < 8; ii++) { LPCSTR font_path2 = "GameData1\\font\\ipam.ttc"; AddFontResourceEx(font_path2, FR_PRIVATE, nullptr); int font2 = CreateFontToHandle("IPA明朝", 40, 3, type[ii], -1, -1); startTime = GetNowCount(); TextDraw(font2, drawTexts); endTime = GetNowCount(); fprintf(fp, "IPA明朝 %s: % d\n", typestr[ii], (endTime - startTime) / 10); } //------------------------------------------------------------------------------------------ // フォント その3 //------------------------------------------------------------------------------------------ for (int ii = 0; ii < 8; ii++) { LPCSTR font_path3 = "GameData1\\font\\GenYoMin-R.ttc"; AddFontResourceEx(font_path3, FR_PRIVATE, nullptr); int font3 = CreateFontToHandle("GenYoMin TW R", 40, 3, type[ii], -1, -1); startTime = GetNowCount(); TextDraw(font3, drawTexts); endTime = GetNowCount(); fprintf(fp, "源様明朝 %s: % d\n", typestr[ii], (endTime - startTime) / 10); } fclose(fp); DxLib_End(); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DXlibQA(); return 0; }
メンテ

Page: 1 |

Re: フォントによってDrawStringToHandleの描画時間が異なる原因を教えて下さい ( No.1 )
名前:管理人 日時:2022/07/08 00:58

DXライブラリは文字を高速で描画するためにOSから取得できる文字のビットマップを一度テクスチャに転送してから描画しているのですが 何万文字もある文字のビットマップを全部テクスチャに転送していたらVRAMを無駄に消費してしまうので、文字のビットマップを一時的に 保存するテクスチャのサイズは 1フォントハンドルに付き SetGraphMode で指定された画面サイズの 85% くらいの大きさのテクスチャ1枚として、 そのテクスチャに文字が入り切らなくなったら使用頻度の低い文字から一時保存用のテクスチャから削除するようになっています そして、その一時保存用のテクスチャは『フォントの最大幅の文字の横幅』×『フォントの高さ』で格子状に区切り、1枠につき1文字を割り当てます ( 大抵の文字は最大幅より小さいので余白の部分が無駄になってしまいますが、カチカチに敷き詰めるような処理にした場合  『空いている箇所を探す処理』の負荷が上がってしまうのでこのような処理になっています ) 上記のようなアルゴリズムで一時保存用テクスチャを作成すると、本件の各フォントが一時保存できる文字数は以下のようになっていました NotoSansJP 64文字 IPA明朝 256文字 源様明朝 64文字 IPA明朝だけ一時保存できる文字数が多いです これは何故かと言いますと、フォントから取得できる『最大幅の文字の横幅』の情報が関係しています 上記3つのフォントから取得できた『最大幅の文字の横幅』は以下の通りでした NotoSansJP 120pixel IPA明朝 57pixel 源様明朝 115pixel 何か異様に横に長い文字が NotoSansJP と 源様明朝 には存在するようで、それによって一時保存用テクスチャに 保存できる文字数が著しく減っていました そして "吾輩わがはいは猫である。名前はまだ無い。" の文章に含まれる文字の種類は 140個程なので、IPA明朝だけは全ての文字が一時保存用テクスチャに収まるため、 同じ文字が2回以上出現した場合は、1回目のみ『OSから文字のビットマップを取得して一時保存テクスチャに転送して描画』の処理が行なわれ 2回目以降は『一時保存用テクスチャにある文字を描画』だけで済むので他の2つのフォントよりも圧倒的に高速に描画できているということになります ( 他の2つは一時保存用テクスチャに収まり切らないため、2回めの描画でも『OSから文字のビットマップを取得して一時保存テクスチャに転送して描画』  となるケースが発生するために処理時間が大きくなるわけです ) 対策ですが、デフォルトではDXライブラリが一時保存用テクスチャのサイズを決め、一次保存できる文字数を自動で決定しますが、 以下の関数で一時保存できる文字数を指定することが出来ます // フォントキャッシュでキャッシュできる文字数を設定する( CreateFontToHandle の前に実行する必要があります ) int SetFontCacheCharNum( int CharNum ) ; 試しに DxLib_Init の直後に SetFontCacheCharNum( 150 ) ; という記述を追加したところ、IPA明朝以外のフォントも IPA明朝と同じくらいの処理時間で描画処理が完了しましたので、よろしければお試しください m(_ _)m ( 尚、一時保存用のテクスチャは 1枚のみで、作成できるテクスチャの最大サイズもGPU毎に決まっているので、仮に  SetFontCacheCharNum( 1000000000 ) ; ←このような大きな数値を渡して実行しても、実際に一時保存できる文字数は  GPU が対応している最大サイズのテクスチャに収まる文字数となります ) ところで本件とは関係ありませんが、DxLib_Init の前で SetDrawScreen や SetDrawMode を使用されていますが、 こちらの2つの関数は DxLib_Init の前に実行しても効果はありませんので、DxLib_Init の後に実行するようにしてください m(_ _)m if (DxLib_Init() == -1) {//DXライブラリ初期化 return ; } SetDrawScreen(DX_SCREEN_BACK); SetDrawMode(DX_DRAWMODE_BILINEAR);
メンテ
Re: フォントによってDrawStringToHandleの描画時間が異なる原因を教えて下さい ( No.2 )
名前:とらふずく 日時:2022/07/09 00:09

詳細に現象について説明して頂きありがとうございます。 IPA明朝のみ描画が早い理由が理解出来ました。 また、SetFontCacheCharNum(150);で描画がIPA明朝以外も早くなったことを確認できました。 > ところで本件とは関係ありませんが、DxLib_Init の前で SetDrawScreen や SetDrawMode を使用されていますが、 > こちらの2つの関数は DxLib_Init の前に実行しても効果はありませんので、DxLib_Init の後に実行するようにしてください m(_ _)m ご指摘の通りですね。こちらも修正しました。 迅速な回答本当にありがとうございました。
メンテ
Re: フォントによってDrawStringToHandleの描画時間が異なる原因を教えて下さい ( No.3 )
名前:とらふずく(解決済み) 日時:2022/07/18 13:56

解決済みにしました。
メンテ

Page: 1 |

題名
名前
コメント
パスワード (記事メンテ時に使用)

   クッキー保存