Skip to main content

非同期テキストレンダリング

説明

candera エンジンは非同期テキスト レンダリングを提供し、時間のかかるテキスト処理の処理を改善します。焦点は、パフォーマンスの向上ではなく、リソース処理の向上です。

非同期テキスト レンダリングには、テキスト ノードの動作に関する知識が必要です。

Candera::TextNode2Dの使用方法に関する一般的な情報は、次のセクションにあります。

非同期テキスト レンダリングには、テキストを表示するためにどの手順が必要かについての詳細な知識が必要です。次のセクションを参照してください。

TextNode2D と CanvasText に関する知識に基づいて、次の章では非同期テキスト レンダリングのしくみについて説明します。さらに、これらの章では、非同期テキスト レンダリングをカスタマイズする方法と、この機能を使用する際に考慮すべき制限について説明します。ドキュメントは主にCandera::TextNode2Dに焦点を当てています。これらの章で説明されている概念は、Candera::CanvasTextにも適用できます。



テキストレンダリング - 役に立つ知識 

TextNode2D – テキスト レンダリング プロセス

テキストは、そのレンダリング プロセスで 3 つのステップを使用します。

  1. 測定と配置
  2. 事前レンダリング – テキスト用にレンダリング デバイスを準備します (必要な場合)。
  3. Render – 実際に表示される出力

目に見える結果を得るには、これらすべての手順を実行する必要があります。TextNode2D の使用には、そのテキストを更新する 3 つの異なる方法があるため、最終結果にアプローチする 3 つの異なる方法があります。主な違いは、情報の提供と 3 つのプロセス ステップの処理にあります。

最初のケース: レイアウトの使用なし

Candera::TextNode2D には実際のレイアウト情報はありません。Candera::TextNode2Dが統合されたレイアウトもありませんしたがって、テキスト レンダリング プロセスのレンダリング プロセスをトリガーするだけで十分です。テキストは領域にバインドされておらず、シーン グラフ内の他のノードに対する可視レイヤー以外の関係はありません。

プロ:

  • text と candera の軽量版。
  • 簡単なテキストは、さほど苦労せずに表示できます。

反対:

  • テキスト配置機能のほとんどが無効になっています。
  • 取り決めは存在しません。
drawing-4-1677046993.png

サブメニューに戻る


2 番目のケース: レイアウト プロセスはあるが、TextNode2D レイアウターがない

Candera::TextNode2D には実際のレイアウト情報はありません。ただし、レイアウト (GridLayout など) 内に配置されます。これは、Candera::TextNode2D が実際のレイアウト結果を提供して、レイアウト領域内にテキストを配置する必要があることを意味します。したがって、レイアウト プロセスは、事前にテキスト レンダリング プロセスを開始する必要があります。テキスト レンダリング プロセスは、レイアウト プロセスがテキストのバウンディング ボックスを評価するときに、最初のステップ (測定と配置) を完了する必要があります。レイアウト プロセスは、境界ボックスのサイズには影響しません。境界ボックスの情報のみを受け取り、テキストの周りに他のすべてのノードを配置できます。この情報の実際の処理は、使用するレイアウタによって定義されます。

プロ:

  • シーン グラフ ノードの配置の利点を備えた軽量バージョンのテキスト。
  • 最初のケースと 2 番目のケースの切り替えは、通常、大きな問題はありません。

反対:

  • テキストの配置は、レイアウト プロセスの影響を受けません。
  • TextNode2D 自体は、最初のケースと 2 番目のケースを区別できません。
drawing-4-1677046938.png

サブメニューに戻る


ケース 3: TextNode2D レイアウターを使用したレイアウト処理

Candera::TextNode2D はレイアウト情報を提供します。さらに、一般的なレイアウト プロセスは、テキストの表示方法にも影響を与える可能性があります。レイアウト プロセスでは、テキストが収まる必要がある最大領域を提案できます。これは配置に直接影響します。テキスト自体は、レイアウト プロセスで実際に使用されたバウンディング ボックスを提供します。

プロ:

  • シーン グラフの配置に統合されたテキスト配置の完全な機能セット。

反対:

  • テキストの重量バージョン。より多くの計算を行う必要があります。
  • 付属のレイアウターは、レイアウターの使用を想定しています。

レイアウターがアタッチされている場合、デフォルトの動作を複雑なものに置き換えるため、レイアウターを使用する必要があります。ただし、レイアウトプロセスをトリガーして情報を提供する必要があります。そうしないと、ほとんどの情報が失われます。これは、他の使用法が間違っていて、未定義または望ましくない動作につながることを意味します。

drawing-4-1677046891.png

拡大縮小や回転などの変換は、テキストがレンダリングされた後に適用されます。つまり、スケーリングを使用して狭いテキストまたは広いテキストを生成することはできません。これは、視覚的なレイアウトが変更されるためです (右揃えのテキストは、スケーリング後に整列されなくなります)。この場合、代わりにスケーリングされたフォントをインポートします。

CanvasText – テキスト レンダリング プロセス

Candera::CanvasText はCandera::TextNode2Dの 3 番目のアプローチを使用します

処理の複雑さを軽減するために、他のすべてのアプローチはCandera::CanvasTextから削除されました。Candera::TextNode2Dとのもう 1 つの違いは、実際の Render() 部分です。Candera::TextNode2D は各ノードで Render() を呼び出しますが、 Candera::CanvasTextは同じレンダリング設定でテキストを収集し、それらを 1 回の描画呼び出しで描画することにより、バッチ形式でレンダリングします。Candera::CanvasText はGlyphAtlas を使用しています。

非同期テキスト処理を使用する場合は、次の制限にも注意してください。

サブメニューに戻る

メニューに戻る



非同期テキスト レンダリング 

非同期テキスト レンダリングは、テキスト レンダリング ステップを独自のスレッドまたはレンダリング スロットに移動するために導入された機能です。正確には、3 つのレンダリング ステップのうち最初のステップのみを非同期で処理できます。他のすべてのステップは、おそらく GPU にバインドされているか、フレームワークとユーザー アプリケーションの両方で目的の出力を受け取る方法の複雑さを拡張する必要があります。

非同期テキスト レンダリング – ワーカー スレッド

非同期テキスト レンダリングのデフォルトの方法は、ワーカー スレッドの使用です。レンダリング ループは、最初のステップである測定と配置をワーカー スレッドに委任します。すべてのデリゲートはキューに入れられ、処理の準備が整います。ワーカー スレッドを使用する主な利点は、レンダー ループをブロックしないことです。テキストの測定と配置が完了するまで、レンダリング ループ自体はブロックされません。このアプローチの欠点は、実行の瞬間と結果の到着時間を制御できないことです。これは、単一のレンダリング サイクルの後に発生する可能性がありますが、未定義のサイクル数の後に発生することもあります。

次の図は、非同期テキスト レンダリングを使用する場合に、測定と配置のプロセスがどのように置き換えられるかを下書きで示しています。簡単にするために、レイアウターを使用しない方法のみを使用します。他のすべての方法は、測定と配置のプロセスをフローチャートの同じアプローチに置き換えます。

非同期プロセスを実際にトリガーする関数は、2 回トリガーする必要があります。1 回はプロセスをキューに入れ、2 回目は結果を取得します。このフローチャートのサンプルでは、​​Render メソッドを 2 回呼び出す必要があります。2 番目の呼び出しは、非同期プロセスが実際に終了したときに実行する必要があります。

drawing-4-1677047084.png



非同期テキスト レンダリング - シングル スレッド

非同期とは、作業負荷を別のスレッドで処理する必要があるという意味ではありません。また、別の時点で同じスレッドによって実行されることもあります。シングル スレッド バージョンの主な欠点は、レンダリング ループ スレッドがまだテキストを処理していることです。ただし、今回は、1 回のレンダリング サイクルですべてのテキストを処理する必要はありません。サイクルでレンダリングされるテキストの量を決定できます。

これは、テキスト/サイクルと処理するテキストの 2 つの要素が与えられていることを意味します。この情報に基づいて、すべてのテキストを計算するために必要なサイクル数を見積もることができます。ただし、各サイクルの期間を決定することはできません。

drawing-4-1677047132.png



同期方法 

テキスト同期

非同期テキスト レンダリングの導入により、すべてのテキスト ノードを同期する方法を提供する必要があります。基本的な考え方は、すべての非同期テキストを同じレンダリング サイクルで視覚的に更新することです。テキストがランダムにポップアップするのを防ぐ必要があります。もう1つの側面は、レイアウトプロセスを減らすことです。レイアウターがCandera::TextNode2Dにアタッチされている場合、レイアウト プロセスはCandera::TextNode2Dのサイズを評価しますこれは、レイアウト プロセスが必要に応じて非同期テキスト レンダリングを開始することも意味します。レイアウト プロセスがプロセスを開始するとき、結果を待つべきではありません。これは、以前と同じブロッキングの問題が発生するためです。

レイアウタは、非同期テキスト レンダリングがまだ行われていない限り有効な古い情報とテキストを使用します。レイアウト プロセスを再実行すると、新しいテキストがまだ有効かどうかがチェックされます。そうでない場合は、古い情報を引き続き使用します。新しいテキストが現在有効な場合、テキストが更新されます。同じ手順は、レイアウターがアタッチされていなくても発生します。ただし、結果の開始と収集の部分は、render 呼び出し自体によって行われます。つまり、非同期テキスト レンダリングを開始し、最低 2 回の呼び出しで結果を受け取る必要があります。呼び出し元が実際に誰であるかは、レイアウト プロシージャの使用法に依存します。

このセクションでは、同期の可能性について説明します。

テキスト同期 – デフォルトの同期

デフォルトの同期は、可能な限りシンプルに保たれています。このセクションでは、検証のライフサイクルと、その欠陥を含めて同期がどのように機能するかについて説明します。

ライフサイクル

同期する必要があるすべてのオブジェクトは、インターフェイスCandera::TextRendering::ITextValidationUserを実装します(例: TextNode2D)。次のステップで、このオブジェクトをCandera::TextRendering::TextValidationGroupにアタッチする必要があります。デフォルト グループはシングルトン インスタンスです。Candera ::TextRendering::TextValidationGroup は特定のバリデータを作成し、それをオブジェクトに添付します。バリデーターの切り離しは、このプロセスに匹敵します。

drawing-4-1677047325.png

同期 - フロー

Candera::TextRendering::TextValidatorの無効化メソッドを呼び出すことで、いつでもオブジェクトを無効化できます無効化されたオブジェクトがその状態を検証に変更するたびに、Candera::TextRendering::TextValidator は検証の変更をグループに通知します。グループのすべてのオブジェクトが現在有効である場合、これらのオブジェクトのすべての更新メソッドが呼び出されます。

drawing-4-1677047365.png

Candera::TextNode2Dにレイアウターがアタッチされている場合、 Candera::TextNode2D が存在するシーンのレイアウトは無効になります。レイアウト プロセスがまったく使用されている場合 (カンデラ レイアウト機能が無効になっている場合)、更新メソッドでは何も実行されません。ただし、render メソッド自体は、新しく呼び出されたときにこのケースを処理できます。ここでの問題は、誰がレンダリングをトリガーするかが明示的に指定されていないことです。のレンダリング メカニズムとレイアウト メカニズムの両方が、グループ全体が有効かどうかをチェックします。グループ全体が有効な場合にのみ、テキストが更新されます。グループが無効であるにもかかわらず、別のオブジェクトによってレイアウトがトリガーされた場合、テキストは更新されません。

Candera::TextNode2D は、デフォルトでCandera::TextRendering::TextValidationGroupシングルトン インスタンスにアタッチされます同期グループから除外するには、最初に明示的に切り離す必要があります。

同期 - 制限事項

デフォルトの同期は可能な限り軽量であり、テキストが視覚的に更新されるまでグループ内のすべてのテキストが有効でなければならないという機能を提供します。テキストはさまざまな組み合わせで使用できるため、同期のための普遍的なソリューションではありません。したがって、場合によっては、本来あるべきほど実用的ではありません。

Candera ::TextNode2D は、同期または非同期にすることも、実行時に両方のケースを切り替えることもできます。したがって、デフォルトでは、非同期フラグが変更されたときに TextNode2D をグループにアタッチ/デタッチすることはお勧めできません。保留中のリクエストは、ここでの主要な問題の 1 つです。

同期Candera::TextNode2D は同期でなければなりません。これは、テキストが変更された場合、その後の最初のレンダリング サイクルでテキストを表示する必要があることを意味します。そのため、テキストが実際に表示されることが確定的になります。これらのノードで同期グループを使用すると、この決定論に反します。ノードはグループに接続されていますが、常に有効な状態です。デフォルトの同期では、ノードは完全に無視されます。

もう 1 つの重要な制限は、テキストの更新率です。これは、同期がなくても発生する問題ですが、同期によって激化します。つまり、テキストが変更されるたびに、グループが無効になります。無効化率が、グループ全体のテキスト レンダリング プロセス全体がレンダリングする必要があるよりも高い場合、表示されるはずのテキストが表示されなくなります。2 つのテキストがより高い割合でそれ自体を無効にしている場合、グループ全体が常に無効な状態になる可能性もあります。グループ自体には、既に終了したテキストをカウントするメカニズムがあるため、グループが有効であるか、まだ表示されていない保留中の有効なテキストがあるという事実に基づいて、テキストを更新できます。この情報に基づいて、新しいテキストを設定したためにテキストの 1 つが無効に戻った場合でも、有効なグループを認識することができます。

同期は単純であるため、テキスト更新を相互にリンクすることはできません。たとえば、すべてのテキストを一度に別の言語に変更することは機能しています。保留中のテキストの変更に関する問題は 1 つだけです。通常、どのテキスト変更が相互に関連しており、同時に変更する必要があるかを特定することはできません。

顧客のすべてのユースケースが、単一の同期方法で満たされるわけではありません。ある方法では、別の方法とは異なる副作用が生じます。

したがって、独自の同期を実装または使用できます。

テキスト同期 – カスタム実装

同期のカスタマイズには、いくつかのレベルの複雑さといくつかの異なる目標があります。

ランタイム中の同期方法の変更は、影響を受けるテキスト オブジェクト ( Candera::TextNode2Dなど) に保留中のテキスト変更がない場合にのみ行う必要があります。これは、予期しない副作用を引き起こす可能性があるためです。

新しい同期グループ

最も単純なカスタマイズは、追加の同期グループです。デフォルトでは、すべての TextNode2D がCandera::TextRendering::TextValidationGroupのシングルトン インスタンスにアタッチされていますカスタム コードでこのクラスの追加インスタンスを作成することができます。Candera::TextNode2D を新しいグループに正しくバインドするには、2 つの手順を実行する必要があります。
最初に、古い Candera ::TextRendering::TextValidationGroupの Detach-method を使用して、古いグループからCandera::TextNode2Dを切り離します。次に、新しい Candera ::TextRendering::TextValidationGroupの Attach-method を使用して、 Candera::TextNode2Dを新しいグループに接続します

追加のトリガー オブジェクト

一般的な問題は、 Candera::TextNode2Dの基礎となるシーン レイアウトを無効に設定するだけでは、実際のレンダリング サイクルをトリガーするのに十分ではないということです。たとえば、レンダリング ウェイクアップ メカニズムには、ウェイクアップしてレンダリング サイクルを開始するためのトリガーも必要です。

Candera ::TextNode2D自体は、特定のアプリケーションに依存するため、どの追加のトリガーが必要かを知りません。

これを拡張すると、グループが有効になるとすぐにシーン グラフ内のノードを更新できます。簡単な使用例は、新しいシーン内のすべてのテキストが有効な場合にのみ新しいシーンを表示するように設定するか、そのテキストが有効な場合にのみコントロールがポップアップするように設定することです。

これを達成するために、これらの呼び出しを担当する特定のオブジェクトは、インターフェイスCandera::TextRendering::ITextValidationUserを実装する必要があります。オブジェクトは、既定の同期の記述されたライフ サイクルに等しいCandera::TextRendering::TextValidationGroupにアタッチできます。その中の Candera ::TextRendering::ITextValidationUser::TriggerValidUpdate()メソッドは、グループ全体が有効になり、カスタム コードを実行できるようになるとすぐに呼び出されます。さらに、オブジェクトは独自のCandera::TextRendering::TextValidatorを受け取ります。これは、グループが有効な時間またはCandera::TextRendering::ITextValidationUser::TriggerValidUpdate()の時間に影響を与える可能性があります。グループ全体のすべてのオブジェクトに対して、追加で呼び出す必要があります。

独自の同期の実装

最もカスタマイズされたソリューションは、独自の同期メカニズムを作成することです。

独自の実装には 2 つの重要なインターフェイスがあります。

Candera::TextRendering::ITextValidationUser は、Candera::TextNode2Dに任意の検証メソッドを提供するインターフェースです。このような検証メソッドを正しくアタッチおよびデタッチします。Candera ::TextRendering::ITextValidationUser::TriggerValidUpdate() は、Candera::TextNode2Dのシーン レイアウトを無効にしますインターフェイスFeatStd::ValidationHelperBase は、オブジェクトを検証および無効化する機能を提供します。それがどのように機能するかはカスタム実装です。ここでの制限は 1 つだけです。同期的に使用される TextNode2D は、カスタム実装の有効な状態を常に無視します。ただし、validate メソッドと invalidate メソッドは呼び出されます。Candera ::TextNode2D次のように関数を呼び出します。

メニューに戻る


構成の可能性 

一般的なセットアップと構成の可能性

このセクションでは、非同期テキスト レンダリングを構成する方法について説明します。

非同期 TextNode2D および CanvasText

非同期テキスト レンダリングの有効化は、各Candera::TextNode2DまたはCandera::CanvasTextで行う必要がありますこれは、SceneComposer 内で実行できます。

drawing-4-1677047433.png

またはコード自体で:

void SetAsyncPreRenderEnabled(bool enabled);
bool IsAsyncPreRenderEnabled() const;

使用例:

TextNode2D node; 
node.SetAsyncPreRenderEnabled(true);   //Enables async rendering 
node.IsAsyncPreRenderEnabled();        //returns true
node.SetAsyncPreRenderEnabled(false);  //Disables async rendering 
node.IsAsyncPreRenderEnabled();        //returns false

SceneComposer とコードのフラグは、Candera::CanvasTextで同様に構成できます。

非同期ディスパッチ方法

CMake には、ワーカー スレッド ソリューションとシングル スレッド バージョンのどちらかを選択するためのフラグが用意されています。フラグを有効にすると、ワーカー スレッドが使用されます。このフラグはデフォルトで有効になっています。スレッドが利用できない場合、またはスレッド セーフが無効になっている場合、コードはこのフラグを無視してシングル スレッド バージョンにフォールバックします。フラグはシステム全体です。これは、すべての非同期 TextNode2D でカウントされます。これはプログラムで定義することもできますが、スレッド セーフ チェックはありません。

drawing-4-1677047461.png

ディスパッチャー設定

TextRenderDispatcher には、以下を呼び出して取得できる設定オブジェクトが含まれています。

class Candera::TextRendering::TextRenderDispatcher::TextRenderSettings{…};

Candera::TextRendering:: TextRenderDispatcher::GetTextRenderSettings();

非同期ディスパッチ方法をプログラムで変更するには、設定の関数を使用します。

#include <Candera/TextEngine/Async/ AsyncTextRenderDispatcher.h>
#include <Candera/TextEngine/Async/ ThreadingTextRenderDispatcher.h>

void SetDefaultTextRenderer(Candera::TextRendering::TextRenderer * val);

シングル スレッド バージョンを使用する場合、DispatchNext ごとに処理されるテキストの量を設定できます。

void SetAsyncLoopCount(UInt8 const val);

メニューに戻る


制限 

このセクションでは、非同期機能を使用する際に留意すべき問題について説明します。

パフォーマンスの向上

非同期テキスト レンダリングの主な目的は、テキスト レンダリングのパフォーマンスを向上させることではなく、レンダリング ループ自体のパフォーマンスを向上させることです。結果を表示するためにレンダリング ループの 2 回目のサイクルが必要な場合でも。最初のサイクルはテキスト レンダリング呼び出しをキューに追加するだけなので、2 番目のサイクルは必須です。最初のサイクルでは、古い結果を使用してテキストを処理します。

ワーカー スレッドは 2 番目のコアで処理できるため、実際のパフォーマンスを向上させるには、ワーカー スレッドを使用し、最小限のデュアル コア プロセッサを使用することが必須です。

高周波変化

最も必須の制限は、非同期テキストの更新間隔です。テキストエンジンが処理できるよりも速くテキストを変更すると、システムの決定論が確実に破られます。高速更新テキストを処理するための 3 つの異なるアプローチを概説できます。最初のアプローチは、すべてのテキストの変更を視覚的に示すことです。すべてのテキストを表示することは決定論的です。それらが単一のフレームでしか表示されず、テキストが表示されるときに決定論的ではない場合でも. 2 番目のアプローチは、キューに入れられた古いテキストを新しいテキストで上書きすることです。すべてのテキストを見るという決定論は失われました。ただし、レンダリング プロセスが開始される前に既に古いテキストをレンダリングすると、パフォーマンスが低下しますが、発生することはありません。3 番目の解決策は、これらのテキストを同期的にレンダリングすることです。

現在利用できるのは、2 番目と 3 番目のアプローチです。この決定は、考えられるユースケースに基づいています。

最初のユース ケースは、フレーム レート、クロック タイムなどの代替テキストです。基本的に、このようなテキスト タイプには 3 つの共通点があります。まず、指定された時間間隔でコンテンツを常に変更します。テキストの背後にあるロジックは、最初は 1 フレーム後、2 回目は 6 フレーム後に更新が表示された時点で既に壊れています。第二に、テキストは通常​​短いテキストです。長いテキストの頻繁な変更は、おそらくユーザビリティの問題です。第 3 に、頻度がそれ自体を追い越すほど高く、テキストが常に交互に表示される場合、キューは無期限に積み重なっていきます。新しいテキストが到着するまで、キューに入れられたすべてのテキストを表示する時間はありません。視覚的なテキスト自体はますます時代遅れになっています。テキストの長さは、キューイング テキストの変化をさらに大きくします。

そのため、周期的な交替でそれらを同期的にレンダリングすることをお勧めします。短いテキストは、レンダリング ループ自体にはそれほど影響しません。また、長いテキストは再検討し、その合理性をチェックする必要があります。

2 番目の使用例は、テキストをすばやく切り替える場合です。たとえば、テキストが表示されますが、ユーザー入力によって次のテキスト更新が既にトリガーされています。この場合、テキストの長さは任意です。主な違いは、テキストの更新が無期限にキューに入れられるわけではなく、おそらく厳しい時間間隔の期限がないことです。この場合、非同期テキスト レンダリングを使用できますが、両方のテキストをレンダリングすると、ユーザー エクスペリエンスに影響を与える可能性もあります。ほとんどの場合、これらは長いテキストで顕著になります。したがって、古いテキストが表示されるまで目に見える遅延があり、新しいテキストが表示されるまで別の目に見える遅延があります。

したがって、非同期アプローチでは、古いテキストが新しいテキストでできるだけ早くオーバーライドされます。古いテキストが既にレンダリングされている場合は、古いテキストが表示されます。古いテキストのレンダリングが開始されていない場合、古いテキストは置き換えられます。古いテキストと新しいテキストの両方が既にレンダリングされていて、レンダリング ループが古いテキストを表示する時間がない場合は、新しいテキストが表示されます。この場合、古いテキストの可視時間は 1 フレームだけである可能性が最も高くなります。

TextNode2D の破棄

添付テキストのレンダリング中に TextNode2D を破棄すると、未定義の動作が発生する可能性があります。

レイアウト プロセスの呼び出し

非同期アプローチを使用するということは、非同期プロセスを開始する機能と、プロセスの結果を処理する別の機能があることを意味します。同期に基づいて、有効な結果のチェックは、結果が利用可能になるまでポーリングすることで解決できます。

特定のユースケースで同期が適切に行われている場合、ポーリングは必要ありません。

ただし、非同期プロセスを開始するには、レイアウト情報が必要です。また、レイアウト プロセスでは、テキスト ノードと依存ノードを配置するための結果が必要です。したがって、レイアウト プロセスを 2 回実行する必要があります。最低要件は 2 回の呼び出しです。2 回目の呼び出しまでテキストが完成していない場合は、結果のポーリングと同じであるため、さらに呼び出しが必要です。

キャッシュ タイプの制限

一部のキャッシュ タイプは、GPU コンテキストにバインドされています。したがって、ラスタライズされたグリフのアップロードは、正しいコンテキストで行う必要があります。ワーカー スレッドを使用しても、アップロードが正しい場所にあるとは限りません。これには、追加のラスタライズとレンダリング スレッド内のグリフごとのアップロードが必要になる場合があります。これらの問題が発生すると予想されるキャッシュ タイプは、SurfaceCache と GlyphAtlas です。Candera::CanvasText は主に GlyphAtlas を使用します。この場合、共有コンテキストと非スレッド方式が推奨されます。

メニューに戻る