ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
metal-cppを使用して、C++でMetalのプログラムを作成する
C++のゲームやAppで、Metalの機能を活用できるようになりました。metal-cppでC++コードをMetalにブリッジする方法やオブジェクトライフサイクルの管理方法を紹介し、さらに、これらの言語をAppで連携させるユーティリティについて解説します。また、Objective-CとC++をスムーズに統合するAppアーキテクチャの設計に関するベストプラクティスも紹介します。
リソース
関連ビデオ
WWDC22
-
ダウンロード
♪ アップテンポなヒップホップ曲 ♪ ♪ 皆さん 始めまして Metal Ecosystem teamの エンジニアのKeyi Yuです 今回は Metal-cpp の説明をします Metal-cpp はApple Platform のAppに C++ 言語でMetal の機能を追加します 低オーバーヘッドのC++記述によるMetal-cpp で App にMetal 機能を組み込めます まず Metal-cpp の機能概要と仕組みを説明 Objective-C のオブジェクト指向の 開発ライフサイクルの詳細にも触れます C++ と Objective-C のライフサイクルの違いを どう取扱うか方法を教えします オブジェクトのライフサイクル管理に Xcode と Metal-cpp は優れた機能を 備えてます 最後にC++ コードをObjective-C クラスで 扱える方法を教えます では Metal-cpp から見ていきましょう Metal による最高の Graphics で Apple Platform のApp やゲームに GPU のパワーを今までにないほど発揮できます Objective-C のパワフルな機能や 技法を元に作成されました C++ がコードベースだとMetal Objective-C の C++ ヘッダの代替えインターフェイスが必要 そこで metal-cpp! C++ App とObjective-C Metal を 集約するC++ 言語です Metal-cpp をApp 作成に 使用するとC++ 言語をMetal クラスで扱える 同時にObjective-Cヘッダの呼び出しにも 遜色ありません Metal-cpp は低付加的なMetal C++ ラッパー 低負荷的な処理の上にシングルファイルのC++ ライブラリ関数と呼び出しインライン展開 Objective-C クラス定数 列挙型のMetal C++ 名前空間のC++ への ダイレクトマッピングで Metal API 実装 Foundation やCoreAnimation に Metal-cpp ラッパー使用 Apache 2 ライセンスのオープンソースなので ライブラリを編集しApp に 簡単に追加できます C++ 関数呼び出しのインライン展開で Objective-C ヘッダ実行 Objective-C のコンパイラと同じ機構で Objective-C メソッド処理を実装します なので低オーバーヘッドのラッパーです Objective-C 呼び出しにC++関数呼び出しへの 1対1マッピングを 実装し Cocoaメモリ管理に従います これについては後ほど説明します 1対1マッピングでGPU Frame Capture と Xcode デバッガーをデベロッパーツールで シームレスに使用できます 以下は Metal-cpp で三角形の描画に必要な 関数呼び出しです C++ 言語に詳しい方は同じシンタックスなので Metal を習得する良い機会だと思います Metal をObjective-C の関数呼び出しに- 使用した事があるならMetal の- Objective-C 呼び出しとmetal-cpp の使用も- ほぼ同じと理解ください 今から metal-cpp の便利な使用方法を紹介 まずコマンドバッファーに コマンドリストを追加 GPU 側で実行するよう命令します Objective-C への認証マッピングには C++ の生ポインターを使用します レンダーコマンドエンコーダーを作成し コマンドバッファにレンダー コマンドのリストを格納 C++ の render CommandEncoder と Objective-C の renderCommandEncoder WithDescriptor は同じ プログラム言語の命名規則だけが 異なります レンダーパイプラインのオブジェクトに 頂点シェーダーとフラグメントシェーダーを 含むその他の描画設定をします エンコード呼び出しをし三角形を描画します レンダーコマンドの 作成を終了したことを示します 可能な描画を示し三角型を描画します 最後にコマンドバッファーに注目します これでコマンドリストをGPU で実行できます Metal-cpp 使用とMetal Objective-C と ではほぼ変わりません Metal-cppの言語のシンタックスを フルに理解しなくても Metalのドキュメントや 概念を把握すればMetalの使用できます 旧Deferred Lightingの 例の問題を解決したことはあるでしょう 今度はMetal-cppのDeferred Lightningの 問題です Metal-cppで描画の練習を 重ねて下さい Metalで解読するインクリメンタルな C++言語のサンプルを多数紹介し 色んなタスクに応用できます
Metal-cppの説明を聞いて実際この言語を 作業にどう使用するか? Metal-cppは昨年公開されました ご覧のページでダウンロードや詳細の 確認できます インストールから使用に ついて今から説明します Xcode を開きMetal-cpp ファイルをダウンロードして内容を展開します Metal-cpp のフォルダを 現行プロジェクトに追加 こうするとC++ 言語をC++17 以降の規格に 更新しないといけません 次にFoundation QuartzCore とMetal の フレームワークを追加 最後に3つのフレームワークの C++ インターフェイスを実行するのため Metal-cpp は3つのヘッダのみの ライブラリなので1つのcpp ファイルに付き以下のマクロコードを追加し 実装を生成するようにします 以下が3つのコードです NS_PRIVATE_IMPLEMENTATION CA_PRIVATE_IMPLEMENTATION MTL_PRIVATE_IMPLEMENTATION Metal-cpp のマクロに関しての詳細は 詳細は Metal-cpp の ヘッドについてのフォルダを参照ください Metal-cpp は単一ヘッダで インクルードするかシングル ヘッダとして追加できます 必要に応じヘッダファイルの インポートができます マクロコードのNS CA と MTL_PRIVATE_IMPLEMENTATION の割り当ては一回だけです 割り当ての複製でエラーが生じるからです Metal-cpp を使用する ためにCocoa のメモリ管理の ポリシーに遵守したルールを 理解するのが重要なのは 便利なユーティリティで オブジェクトライフサイクルや App の アーキテクチャ管理と 他のフレームワークとの規格 デザインに役立つからです オブジェクトライフサイクル 管理から説明します 通常 App を利用をする時は メモリーの確保と解放が必要です コマンドバッファやリソースパイプライン状態 オブジェクトらの管理に 必要なメモリ管理のため Objective-C やCocoa に 含まれる参照カウントを利用 参照カウントは Metal-cppにも同じくあります メモリ管理の合理化のため 参照カウントを利用します 参照カウントを使う場合は RetainCount のプロパティが 全オブジェクトにあります App のコンポーネントは オブジェクトの相互作用の 実施のためにカウントを増やし作用終了の際は カウントを減らします retainCount がゼロに なると実行時のオブジェクトは開放されます Objective-C の参照カウント 方式は2種類あります 手動(Manual) での参照カウントのMRR と 自動(Automatic) 参照カウントのARC です ARC での参照カウントは コンパイル時に適切な メモリ管理呼び出しを 自動的に挿入します Metal-cpp オブジェクトの 保持や解放は手動です Cocoa のポリシーのオブジェクト保持と 解放についてのルールを 理解する必要があります C++ でのオブジェクト作成と 違い metal-cpp では オブジェクト作成をNew” で 削除をDelete” できません Cocoaの方式のalloc “New “Copy“ “コピー元- を反映する”と作成で 生成されたオブジェクトには 所有権があります Retain 機能でオブジェクト の所有権を保持できます オブジェクトの必要ない場合は所有権を 放棄する必要があります オブジェクト所有権の 放棄は自分で設定ができます 所有権のないオブジェクトの放棄を行うと 復元時のエラーが発生する場合があります 今までの説明した作業をCocoa をもとにした 事例で見ていきましょう クラスAではalloc”でオブジェクトを生成し init”で初期設定をします init”の初期設定の復元は絶対しないこと クラスA が所有権を 保持するので割り当て 解除をする必要があります オブジェクトの参照カウントが1になります 次はクラスB が参照を利用し オブジェクトを所有します オレンジ色のキューブが指定する所有権を 2つのオブジェクトが共有しています ここで参照カウントが1増えます
このオブジェクトを必要としないクラスA は オブジェクトを手動で開放する必要があります 結果参照カウントが1減ります 今度はクラスB だけが オブジェクトを所有します これからクラスB がオブジェクトを解放します 参照カウントが0で実行時に オブジェクトは解放されます 以下の状況はクラスBからのオブジェクトを 戻す処理の例です しかしクラスB のオブジェクトは 他のプロがラムに必要なので クラスB のオブジェクトの割り当てを すぐに解除することはできません この場合はクラスB の自動解放機能を利用 自動解放機能 (autorelease) を利用しても参照カウントは 今だ1なので後でオブジェクトが使用できます ここで質問です: クラスB はこのオブジェクトを保有しない ので割り当て解除の責任はどこにあるか フレームワークのFoundationにAutoreleasePool という仕組みがあります Autoreleaseのインターフェイスを利用し AutoreleasePoolにオブジェクト所有をします AutoreleasePoolが消されると 参照カウントが減少されます 自動解放オブジェクトの作成は自分だけでなく Metal も多数の自動解放オブジェクトの 作成をします 一時的オブジェクトを作成しAutoreleasePoolに 追加するメソッドは全てー autorelease”の呼び出しを使用します AutoreleasePoolが自動解放をするべきです これで理解できるようにAutoreleasePoolを 利用するとエレガントなコード作成ができます 一般 App にAutoreleasePoolを 利用するほかワーキングセットを小さく するにもAutoreleasePool”の生成や管理を 小規模で追加することをすすめます 立ち上げたスレッド内ごとに “AutoreleasePool”を 作成する必要もあります 以下がAutoreleasePool”と “Autorelease”された オブジェクトの例ですご覧の通り AutoreleasePoolはalloc”でオブジェクトへ 追加してるので所有権があり 自動解放処理が必要です AutoreleasePoolです 先ほど伝えたようにバッファを コマンド作成をする必要があります Allocで生成してなければ 又はCreateで生成してない 時は所有のものでないです この場合 Metal が生成した 自動解放のオブジェクトです “AutoreleasePool”にこのバッファを実行します AutoreleasePoolは割り 当て解除する事になります “AutoreleasePool”から 解放するまでオブジェクトを 自由に利用できます renderPassDescriptorを 作成しAutoreleasePoolで 実行するようにします あと自動的に解放 されたオブジェクトは: RenderCommandEncoder 確認すべきオブジェクトはcurrentDrawable AutoreleasePoolでこれも実行されます pPool->releaseと書きAutoreleasePoolを オートリリースします 割り当て解除をする前にAutoreleasePool" は 全てをリリースをし今回の例だと コマンド実行のバッファ指定 RenderPassDescriptor RenderCommandEncoder とcurrentDrawable AutoreleasePoolはこの後解除されます こまでCocoa の割り当て オブジェクトの自動的解放と AutoreleasePoolsについて説明しました オブジェクトライフサイクル 管理の便利なツールの 正しい使用でZombieオブジェクトの検出や メモリリークを防ぎデバッグ検出に 役立たせましょう ユーティリティ2種に注目: NS::SharedPtr と NSZombie NS::SharedPtr はライフサイクル管理に役立つ 新しいユーティリティです Foundation フレームワークmetal-cpp の フォルダに含まれています std:shared_ptrと多少異なる事に お気づきでしょうか C++ の標準ライブラリとは別口に利用 参照カウントの保存するのに 追加の負担がありません NS::SharedPtrの説明をしていきます オブジェクトの利用で明確性のある機能と 言えば移譲と保持機能です SharedPtr を 作成するのに所有の移譲が可能で ポインターの参照カウントを保持できます 保持機能がパスするオブジェクトへ送信します この機能は"AutoreleasePool"の オブジェクト保持と ポインタに所有者の 権利既存を示すのに使用します 下層オブジェクトを受け入れるのには T* のgetとoperator->で可能です コピー 移行 構築割り当ては "SharedPtr"の通常の 実行でコピーにより参照カウンとは増えます 移行は迅速に行われ一般的なケースでは 参照カウントは保持されます ポインタが破棄されるまでにSharedPtrs は 一回は解放の送信をします この送信を避けるには 分離機能の呼び出しをします 先ほどの話に戻りますが ポインタの移譲と保持機能と作成する 過程の違いを理解するのが重要です 手動参照カウント- MRR が1のオブジェクト がTransferptrにあります "Transferptr"からsharedptrに オブジェクトの所有権が移行されても retainCountは変わりません ポインタがスコープ外になるとSharedPtr"の デストラクタが実行され MRR のオブジェクトの開放の呼び出しがあり retainCountが0に減ります 今度はNS::RetainPtrの機能について話ます 割り当て解放を避け後でオブジェクトを 使用したい場合はNS::RetainPtrを 利用します MRR オブジェクトでretainCount が1の例です RetainPtrに移行すると retainCountが1増えます スコープ外の実行の際は RetainPtr呼び出しで MRR オブジェクトの開放をし retainCount が1になります 通常はオブジェクトの所有権は NS::TransferPtrにあります 割り当てオブジェクトを 保持 すぐに解放したくない 場合は"NS::RetainPtr"を利用します この二種類の機能でオブジェクトを移行する際 NS::TransferPtr では参照カウントは変わらず NS::RetainPtr の場合 参照カウントが1増えるのは 下層プロトコルからデータ 保持呼び出しがあるからです 両方の機能のデストラクタは 解放呼び出しをパスインセットの オブジェクトへするため 参照カウントは1減ります 参照カウントが0になると オブジェクトはランタイムから解放されます 以下はNS::TransferPtrの事例です 以前レンダーパスで三角形の描画について 説明した際に このレンダーパイプラインの情報が必要でした これがレンダーパイプライン 生成に必要な呼び出しです パイプラインディスクリプタ に必要なアトリビュートです Cocoaのポリシーではこの呼び出しは new と alloc で始まるので自信の所有 オブジェクトとなります なのでオブジェクトに解放の呼び出しをします NS::SharedPtr では MRR のオブジェクトの解放を 呼び出さなくてもよいのは- オブジェクト所有権はNS::SharedPtrs が保有 ここで生ポインタをTransferPtr に返します 今後は以前のスライドで見た ような解放の呼び出しを する必要はありません 自動の参照カウント(ARC)の 利用に慣れてる方はMRR で NS::SharedPtr の扱い方に 似てる事にお気づきでしょう 手動でメモリ使用 解放済み のメモリ領域を使ってしまい バグ発生した事ないですか? この障害は解放された オブジェクトを利用しようと した時に生じます このような時に NSZombie を バグ検出に利用してください 今説明した use-after-freeバグが発生した場合 ブレイクポイントに達し スタックトレースを表示 Zombie の有効化は 環境変数の設定だけで NSZombieEnabled を”YES"設定するだけです Xcode を利用する場合は Zombies の有効化をスキーマ設定でできます 以下の通りです まず既存のレンダーパイプライン設定で 新しいパイプラインのアセットを作成します ファンクションのnewRenderPipelineState を pDesc に挿入
実行をクリックするとブレイクポイントに達し スタックトレースを表示 コードが不完全なようです どこが違うんだろう? NSZombie のバグ検出はまずスキーマを有効化し
プログラムを再度実行すると ブレイクポイントに達します コンソールの出力の表示に 新規データが確認できます: "デアロケート・インスタンス へメッセージ送信" 問題は解除済みの割り当て オブジェクト利用の様です レンダーパイプラインディスクリプタだと表示 なので解除の呼び出しをする 前にレンダーパイプライン ディスクリプタを利用します これでバグ検出ができ問題が解決しました その他ツールや詳細は今年の WWDC サイトの “ゲームメモリの最適化分析データ”をご参照 オブジェクト割り当ての retaincount の追跡管理 など参考にして下さい Apple Platform のツールもご参照下さい ツールはバッグ検出に便利でパフォーマンスの 最適化につながります Metal-cpp のオブジェクト ライフサイクル管理の説明ー 後はゲームコントローラーや オーディオのフレームワーク とのインターフェイス方法について説明します 同じく Objective-C です App のエレガントな アーキテクチャーデザインと APIとのインターフェイスを どう同時に実行するか? Objective-C にViewController を書き込み Metal-cpp のC++ではレンダーコマンドを使用 ViewController のレンダーコマンドの draw の呼び出しが必要です チャレンジは2つの言語をはっきり分け 共有 しないといけない事です 解決策はObjective-C ファイルからC++ 呼び出しを するアダプタクラスを作成することです アダプタクラスを作成すれば機能を実装したい Objective-C か C++ ファイル生成に集中できます 例えば RendererAdapter をObjective-C クラスに作成 インプリメンテーションに Objective-C メソッドを追加して- 直接呼び出しをかけれる様にします インターフェイスではC++ のポインタでオブジェクトの レンダリングを定義します メソッド内ではC++ 方式 のレンダリングを直接呼び出します MTK:View をC++のオブジェクトとして 描画方式まで通過する必要があり キーワードの”__bridge”を利用しC++型として 認識されないとなりません これについては 後ほどより詳しく説明します 対照に Objective-Cで書かれた MTKView は C++ で書かれた Renderer に 呼び出す必要があります かなりのチャレンジです 似た解決策としてアダプタ クラスの作成もできます C++インターフェイスでの Objective-C呼び出しがC++ファイルで可能です 例えば ViewAdapterのクラスを作成します インターフェイスではC++で 書き込むので RendererクラスでC++の 呼び出しが簡単にできます インプリメンテーションに MTKViewからcurrentDrawableと depthStencilTextureらの Objective-C方式を呼び出す ここで”__bridge”を含む キーワードに気づきました? Metal-cppとObjective-Cオブジェクトの「cast- (型変換)」として利用します 事前に知ってる通り Metal-cppオブジェクトは 手動で保持と解放されるので Objective-Cから 生成されたオブジェクトの by Objective-C 自動参照カウントとは違います ですからオブジェクトを MRRからARCへ逆のARCから MRRへ移行するべきです ここで3種類の”_bridge”castを紹介し Objective-CとC++間の型変換に役立つでしょう ”__bridge”のcastはObjective-Cと Metal-Cpp間のオブジェクトの 所有権の移行も可能です ここでは所有権の移行はされてません ”__bridge_retained”で Objective-Cのポインタを Metal-Cppポインタに型変換 ARCから所有権を取得します ”__bridge_transfer”でMetal-Cppのポインタを Objective-Cのポインタへ castし所有権をARCへ移行 問題の戻るとMetal-CppとObjective-C間 ではオブジェクトのcastをする必要があります 所有権の移行がされてない場合は ”__bridge”を利用します Metal-cppからObjective-Cにオブジェクト castし Objective-Cに所有権を移行したい時は ”__bridge_transfer”を利用します Objective-Cからmetal-cppにオブジェクトを castし所有権の移行をARCから取得したい時は ”__bridge_retained”を利用します ここではMetalKitを活用し アセットロードのコードを完成させます C++ AppにMetal-cppオブジェクトとして テクスチャーが必要と言う事です Objective-Cメソッドで作成されます ARCに所有権を移行するので 手動解放の機能が必要です この事例では ”__bridge_retained”のcastを選択します C++でカタログからテクスチャーをロードし Metal-cppのテクスチャーを返します 内部ではいくつかのObjective-C呼び出しを Metal-Kitで実行 テクスチャーのロードに オプション定義が必要で MetalKitを利用しテクスチャーロード作成 Objective-Cで 呼び出し 作成したローダーでテクスチャー オブジェクトを生成しカタログのテクスチャー をロード この方法はMetalKitのObjective-C メソッドと同じです Objective-Cのテクスチャー生成後は Metal-cppオブジェクトに型変換し ARCから 移行する作業です 説明した手順を実行する時間になりました 練習を通してcastの利用も説明します 最初テクスチャーのロードが 必要なテクスチャーロードの オプション定義をします Objective-CにMetal-cppの保存と使用- モードの安全な型変換が 可能なのはMetal-cpp型の 数値の定義が同じだからです テクスチャーロードを作成 Metal-cppのオブジェクトのデバイスなので initWithDeviceメソッドに 移行しなくてはなりません Metal-cppオブジェクトは Objective-Cのオブジェクト であるので toll-free オブジェクトでcastします 所有権の移行もしません ここでテクスチャーのロードのオプションと テクチャーロード利用し テクスチャー生成します テクスチャーロードはMetal-cpp型として 返しすので ARCから解放して対応する ポインター型にcastをする必要があります ”__bridge_retained”のcastを利用します 今後このテクスチャーをMetal-cppオブジェクト として使用できます 割り当ての開放も自分の責任です 今の話はアダプタについての紹介で2つの言語を プログラムで同時に実装可能にする ツールを教えしました Objective-C と C++間で インターフェイスが可能な 3種類のcastも説明しました 今回の話の概要としてMetal-cppは 低オーバーヘッドの効率的な Metal C++ラッパです Metal-cppを利用する時のオブジェクト- ライフサイクル管理 そしてObjective-Cとエレガントな インターフェイスの実施方法と バグ検出に役立つツール について説明をしました Metal-cppをダウンロードし 最高のツールをご活用下さい Metalでどんな作品が 構築できるかお試しください 皆さんがのC++ AppがApple Platformで 活用できることを楽しみにしています 参照してくれて有難う! ♪
-
-
3:10 - Draw a single triangle in C++
MTL::CommandBuffer* pCmd = _pCommandQueue->commandBuffer(); MTL::RenderCommandEncoder* pEnc = pCmd->renderCommandEncoder( pRpd ); pEnc->setRenderPipelineState( _pPSO ); pEnc->drawPrimitives( MTL::PrimitiveTypeTriangle, NS::UInteger(0), NS::UInteger(3)); pEnc->endEncoding(); pCmd->presentDrawable( pView->currentDrawable() ); pCmd->commit();
-
3:27 - Draw a single triangle in Objective-C
id<MTLCommandBuffer> cmd = [_commandQueue commandBuffer]; id<MTLRenderCommandEncoder> enc = [cmd renderCommandEncoderWithDescriptor:pRpd]; [enc setRenderPipelineState:_pPSO]; [enc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3]; [enc endEncoding]; [cmd presentDrawable:view.currentDrawable]; [cmd commit];
-
6:10 - Generate the implementation
#define NS_PRIVATE_IMPLEMENTATION #define CA_PRIVATE_IMPLEMENTATION #define MTL_PRIVATE_IMPLEMENTATION #include <Foundation/Foundation.hpp> #include <Metal/Metal.hpp> #include <QuartzCore/QuartzCore.hpp>
-
11:46 - How to use autoreleased objects and AutoreleasePool
NS::AutoreleasePool* pPool = NS::AutoreleasePool::alloc()->init(); MTL::CommandBuffer* pCmd = _pCommandQueue->commandBuffer(); MTL::RenderPassDescriptor* pRpd = pView->currentRenderPassDescriptor(); MTL::RenderCommandEncoder* pEnc = pCmd->renderCommandEncoder( pRpd ); pEnc->endEncoding(); pCmd->presentDrawable( pView->currentDrawable() ); pCmd->commit(); pPool->release();
-
11:47 - How NS::TransferPtr works
{ auto ptr = NS::TransferPtr( pMRR ); // Do something with ptr . . . }
-
17:19 - How NS::RetainPtr works
{ auto ptr = NS::RetainPtr( pMRR ); // Do something with ptr . . . }
-
20:43 - Create an adapter class calling C++ from Objective-C files
@interface AAPLRendererAdapter () { AAPLRenderer* _pRenderer; } @end @implementation AAPLRendererAdapter - (void)drawInMTKView:(MTKView *)pMtkView { _pRenderer->draw((__bridge MTK::View*)pMtkView); } @end
-
21:49 - Create an adapter class calling Objective-C from C++ files
CA::MetalDrawable* AAPLViewAdapter::currentDrawable() const { return (__bridge CA::MetalDrawable*)[(__bridge MTKView *)m_pMTKView currentDrawable]; } MTL::Texture* AAPLViewAdapter::depthStencilTexture() const { return (__bridge MTL::Texture*)[(__bridge MTKView *)m_pMTKView depthStencilTexture]; }
-
24:59 - Cast between Objective-C and C++ objects and transfer ownership
MTL::Texture* newTextureFromCatalog( MTL::Device* pDevice, const char* name, MTL::StorageMode storageMode, MTL::TextureUsage usage ) { NSDictionary<MTKTextureLoaderOption, id>* options = @{ MTKTextureLoaderOptionTextureStorageMode : @( (MTLStorageMode)storageMode ), MTKTextureLoaderOptionTextureUsage : @( (MTLTextureUsage)usage ) }; MTKTextureLoader* textureLoader = [[MTKTextureLoader alloc] initWithDevice:(__bridge id<MTLDevice>)pDevice]; NSError* __autoreleasing err = nil; id< MTLTexture > texture = [textureLoader newTextureWithName:[NSString stringWithUTF8String:name] scaleFactor:1 bundle:nil options:options error:&err]; return (__bridge_retained MTL::Texture*)texture; }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。