ゲーム開発の備忘録

趣味のゲーム開発でのノウハウや、技術的に嵌ったポイントを忘れないように書き記しておくブログです。

D3D11On12利用環境でフルスクリーン/ウィンドウ切替を実行できるようにする

はじめに

 DirectX12でフルスクリーンとウィンドウを切り替える方法については、書籍やWeb上でも情報があります。基本的にはDirectX11の時にも使用していたDXGI側の関数を呼んで切り替えればよいのですが、DirectX12の場合、解像度に依存するリソースの事前解放および再生成が必要になります。具体的には、バックバッファと深度ステンシルバッファの再生成が必要になります。これを実施しないと、IDXGISwapChain4#ResizeBuffers()関数実行時にエラーになります。

 しかし、D3D11On12を利用している場合、上記の対処ではエラーを解消できません。以下のstackoverflowでも議論されていますが、最終的にMicrosoftに不具合報告をするという結論で終わってしまっています。
stackoverflow.com

 本記事では、D3D11On12利用時のフルスクリーン/ウィンドウ切替について、最終的に私がどう対処したかをまとめておきます。

バイスロスト対応

 結論から言うと、D3D11On12を利用している場合は、IDXGISwapChain4#ResizeBuffers()関数実行前にデバイスロスト発生時と同じ対処を行わなければなりません。即ち、ID3D12Deviceまで削除し、また再生成する必要があります。事前生成したデバイスやリソースをどこまで削除したらエラーが発生しなくなるのかステップバイステップで検証しましたが、一度全てをまっさらにしないとエラーが発生してしまうという結果になりました。

 余談ですが、デバイスロストはDirectX9時代では対処必須なほどよく発生していましたが、DirectX11、DirectX12では発生率が低くなっています。しかし、それでも発生する可能性はゼロではないため、フルスクリーン/ウィンドウ切替への対応と同時に対処してしまいましょう。
learn.microsoft.com

リソース再生成の手段について

 デバイスロスト対応を行うにあたり、問題になるのがグラフィックス関連リソースをどのように再生成するかです。ゲーム起動直後に一括して生成しているリソースは対処が簡単で、同様に一括生成すればよいだけです。PCゲームはそこまでメモリに厳しくないこともあり、ほとんどのリソースを一括生成しているのではないでしょうか。私の場合、以下のリソースは一括生成しています。

  • シェーダ
  • パイプラインステート
  • テクスチャ
  • フォントデータ
  • ブラシ
  • テキストフォーマット

上記に該当するリソースは、デバイスロストからの復帰時に生成用の関数を呼ぶだけで済ませています。

 さて、問題はゲーム起動時に生成するわけにはいかないリソースです。具体的には以下が挙げられます。

  • コンスタントバッファ
  • テキストレイアウト
  • テキストパスジオメトリ

後者2つは場合によっては一括生成できるかもしれませんが、ゲーム内で文字列を動的に生成する場合は事前に生成することはできませんよね。これら3つはオンデマンドで生成できるような仕組みを作っておく必要があります。私の場合は全てmapに格納していますので、描画命令実行時にmapから要素が無くなっていることを検知したら、各エフェクト・描画物を表すクラスのメンバ関数として用意していた生成用の関数を、すぐに呼び出すようにしました。このため、各エフェクト・描画物のインスタンス生成時に確保し、以降は値を更新しないようなコンスタントバッファ(例えば、値が固定された色情報など)についても、コンスタントバッファが消失したことをチェックできるようにするために、毎描画時に初期値で値の更新をかけるようにしています。

DirectX12でのコンピュートシェーダを利用したテクスチャレンダリングについてのあれこれ

はじめに

 ここ最近、DirectX12のコンピュートシェーダを利用したテクスチャレンダリング周りで悪戦苦闘していたので、備忘の目的で記事を作っておきます。

コンピュートシェーダ上でのテクスチャ参照について

 テクスチャレンダリングを行い、その結果にコンピュートシェーダでポストエフェクトをかけて、その結果を最終的に画面出力することを考えます。この時、モザイク処理など、あるピクセルの周囲のピクセルを参照する場合は、入力テクスチャと出力テクスチャを異なるものにしないと正しい結果を得られません。ここで問題になるのは、ピクセル情報を更新しない入力テクスチャをTexture2Dにするのか、それともRWTexture2Dにするのかです。

 一見、参照するだけならわざわざRWTexture2Dにする必要はないのではと考えるかもしれませんが、コンピュートシェーダ内ではTexture2Dのテクスチャサンプリングが実行できないため、参照用途であってもRWTexture2Dとすることが必須です。コンピュートシェーダ内でTexture2DやSamplerStateの宣言ができるので、そのまま利用できるのではと考えてしまうかもしれませんが、サンプリングしようとするとエラーになります。なお、必然的にRWTexture2Dを2枚使う(レジスタu0、u1)ことになります。アプリケーション側で入力テクスチャと出力テクスチャを連続した領域に確保しなければならないので注意しましょう。

GPGPU用のコマンドリストを用いたテクスチャレンダリングについて

 描画命令用のコマンドリスト(D3D12_COMMAND_LIST_TYPE_DIRECT)でコンピュートシェーダのディスパッチ命令を実行できます。コンピュートパイプラインステート、コンピュートルートシグネチャは描画命令用のコマンドリストでも設定できるのです。

 テクスチャレンダリング用途以外でも言えることですが、コンピュートシェーダはGPGPU用のコマンドリスト(D3D12_COMMAND_LIST_TYPE_COMPUTE)を用意して、描画命令とは別に実行することでパフォーマンスが高くなると言われています。しかし、テクスチャレンダリングをコンピュートシェーダで行う際に、GPGPU用にコマンドリストを分けて実行する場合はエンジンの大規模な変更が必要になるため、エンジン開発初期から実装を組み入れておかなければなりません。

 なぜ、エンジンの大規模な変更が必要になるのでしょうか?その理由は、コンピュートシェーダ実行タイミングの違いにあります。以下の順番で描画することを考えてみましょう。

1. 入力テクスチャのレンダリングのためのコマンド発行
2. コンピュートシェーダでのポストエフェクト実施のためのコマンド発行
3. UIなど、上位の描画レイヤでのレンダリングのためのコマンド発行
4. ID3D12CommandQueue#ExecuteCommandLists()でのコマンド実行による、レンダリングの実際の反映

 描画命令用のコマンドリストでコンピュートシェーダを実行する場合、ID3D12CommandQueue#ExecuteCommandLists()でコマンドを順次実行できるため、上記の順番そのままで正しく描画を完了できます。しかし、GPGPU用のコマンドリストでコンピュートシェーダを実行する場合、描画命令用のコマンドリストとは分けて実行しなければなりません。そのため、上記手順2のタイミングでコンピュートシェーダを実行しても、前段の手順1が実際にはまだ未実行であるため、入力テクスチャが準備できていないことになります。正しく実行するには、描画命令用のコマンドリストを複数用意し、手順1,手順4それぞれのタイミングで実行しなければなりません。DirectX12でエンジンを作っている方ならなんとなく分かるかもしれませんが、ほぼエンジンができた後にこのための変更を行うのはかなり厳しいです……。GPGPU用のコマンドリストを使用したい場合は、エンジン開発初期に対応させるようにしましょう。

MobiVMでビルド時にIBAgent-iOS failed to launchが発生した際の対処法

はじめに

今回はMobiVMでCreate IPAを実行した際にIBAgent-iOS failed to launchが発生し、ビルドが失敗した際の対処法について説明します。このエラー自体はMobiVMを利用しないiOSアプリ開発でも発生することがあり、エラーメッセージで検索するとXcodeの再インストールやMacの再起動をはじめとした様々な対処法がヒットするかと思います。しかし、MobiVMのCreate IPAで当該エラーが発生した場合は上記の対処法では解決できませんでした。今後も同様のエラーが発生する可能性があるため、備忘のために本記事に対処法を残しておきます。

Console.appでのログの確認

当該エラーが発生した際、MobiVMのビルドログには Please check Console.app for crash reports for "IBAgent-iOS" for further information. というメッセージが出力されています。
ここは、提案通りConsole.appを確認してみましょう。IBAgent-iOSに関するログは、ログレポートCoreSimulator.logから確認できます。私が確認した際は、 Unable to discover any simulator runtimes. というメッセージが出力されていました。
このエラーメッセージの内容から、何らかの要因で当時の最新のiOSランタイムであるiOS17.0.1ランタイムがMacにインストールされていないことが分かりました。通常はXcode起動時にランタイムの不足があれば更新を促してくれるのですが、iOS17.0.1のランタイムはXcodeを起動してもインストールされなかったため、それに気付かずランタイムが不足していたという訳です。

iOSランタイムの最新化

以下のコマンドを実行することで、iOSランタイムを最新化できます。ランタイムを最新化することでCreate IPAが実行可能となります。

xcodebuild -downloadPlatform iOS

MobiVMでビルド時にAssertion Failed: (aliasSectionNum == sectionNum && "alias and its target must be located in the same section")が表示される場合の対処法

はじめに

今回は、MobiVMでビルド時にAssertion Failed: (aliasSectionNum == sectionNum && "alias and its target must be located in the same section")という、アサーションチェックに失敗した旨のエラーメッセージが表示された場合の対処法について共有します。
このエラーはiOS17対応がなされているXcode 15以降を用いてビルドする際に、それ以前のバージョンのXcode向けに作成されたプロジェクトをビルドしようとすると発生するものです。

MobiVMを使用しない場合は、XcodeプロジェクトのBuild SettingsにてOther Linker Flagsに-ld_classicを追加することで対処できます。(-ld64でもよいですが、deprecatedとなっているので-ld_classicのほうを使用しましょう)

では、Xcodeプロジェクトがビルド前に存在しないMobiVMの場合は、どのようにすればOther Linker Flagsに-ld_classicを指定してビルドできるのでしょうか。

MobiVMプラグインの更新

実は、つい最近まではMobiVMではOther Linker Flagsへの設定は行えませんでした。
2023/9/17に、Android Studioで利用可能なMobiVM Pluginのver 2.3.20がリリースされました。このバージョンであればOther Linker Flagsへの設定ができるようになっています。(そのため、MobiVMのフォーク元であるRoboVMでは本件には対処不可能ということになります)

まずは、Android StudioのMobiVM Pluginを最新化しましょう。

robovm.xmlの変更

MobiVM Plugin ver 2.3.20であれば、以下のようにしてrobovm.xmlからOther Linker Flagsの設定が行えます。(ちなみに、設定方法はMobiVMのGitHubリポジトリのPRを漁って見つけました)

<config>
 <!-- その他の設定 -->
 
 <tools>
  <linker>
   <flags>
    <flag>-ld_classic</flag>
   </flags>
  </linker>
 </tools>
</config>

おわりに

今回は直近のXcodeの更新への対応ということでWeb上の情報量が極端に少なく、Android StudioのMobiVM Pluginのリリースノートが一番の情報源でした。今後もiOSXcodeの更新に追従してMobiVMが更新されることを祈るばかりです。

1フレーム中にDirectX12の描画とD3D11On12の描画を任意の順序で実行する

はじめに

本題に入る前に、まず、レイヤ分けについての話をします。ゲームを作っていると、通常の3D描画と、UI表示等の2D描画を任意の順序で実行したい場合が出てきます。例えば、ポストエフェクトが良い例で、UIを含めた上からぼかしやモザイクをまとめて掛けるといったことが、ポーズ画面の実装等の場合で必要になるはずです。このような例も含め、一般的にゲームの画面描画は複数のレイヤに分けて実施することが多いでしょう。2Dゲームであっても、任意のエフェクトを別のあるエフェクトよりも必ず上側に表示されるようにしたいといったケースはあり、そうするとレイヤを明確に分けないと管理が煩雑になります。
レイヤ分け自体はどのDirectXのバージョンでも自前での実装が必要となります。私の場合は、エフェクトオブジェクトを格納するlistを要素とするarrayを用意しています。このarrayの各要素が独立したレイヤと表現でき、各要素のlistごとにまとめてエフェクトを描画していくことでレイヤごとの描画を実現しています。以下にサンプルコードを示します。

for (auto& effects : effectLists) {
  for (auto& effect : effects) {
	  effect->draw();
  }
}

さて、ここからが本題です。DirectX11の時点で基本的な2D描画はDirect3D11側で実施することになるので、例え2Dゲームを作る場合であっても2Dと3Dが混在したレイヤ分けが必要になります。DirectX11であれば、これはそこまで難しくありません。Direct3D11、Direct2D、DirectWriteのすべてでレンダリングターゲットを同一のテクスチャにしたうえで通常通りレイヤごとの描画を行い、その後ピクセルシェーダで効果を加えたうえでそのテクスチャをバックバッファに出力すればよいのです。レンダリングターゲットの設定が一回で済み、Direct3D11、Direct2D、DirectWriteの描画は順不同でまとめて実施できるということがポイントです。

これがDirectX12とD3D11On12となると途端に難題になります。なぜなら、Direct3Ð12とD3D11On12は同時に描画できないためです。D3D11On12が内部実装で利用しているCommandListは外部から取得できず非D3D11On12のコンテキストからはCommandを投入できないため、純粋なDirect3D12で利用するCommandListとは独立して扱うことになります。つまり、2D描画用のCommandList、3D描画用のCommandListが独立して存在することになります。そして、1フレーム中で任意の回数、順不同で2D描画、3D描画を実施するにあたっては、それぞれの描画で利用するCommandListをはじめとしたリソースの初期化、フラッシュ等をどのタイミングで実施するべきかという頭の痛い問題がついて回ります。当然のようにWebサイトや書籍や公式のリファレンス実装等に解説がなく、トライ&エラーの繰り返しで何とか解消する羽目になったので、今回はこの問題への対応策について解説します。

描画ループの全体構成

まずは1フレーム中の描画ループをどのような構成にしたらよいかについて示します。描画ループは大きく分けて以下の6つの部位に分けることができます。

① 全体初期化処理
② 3D描画開始
③ 3D描画終了
④ 2D描画開始
⑤ 2D描画終了
⑥ 全体終了処理

このうち、②、③および④、⑤は必ず連続して実行されることになります。それぞれひとまとめにすると以下のようになります。

① 全体初期化処理
② 3D描画
③ 2D描画
④ 全体終了処理

上記の4つの処理のうち、②、③は順不同で複数回繰り返す必要がありますのでループで実装します。具体例を以下で示します。

/// <summary>
/// エフェクトを描画する
/// </summary>
/// <returns></returns>
void EffectManager::draw() const noexcept {
  for (auto& effects : effectLists) {
    graphicsCore->beginDrawWithD3D();
    for (auto& effect : effects) {
      if (effect->getDimension() == EffectBase::Dimension::D3D) {
        effect->draw();
      }
    }
    graphicsCore->endDrawWithD3D();

    graphicsCore->beginDrawWithD2D();
    for (auto& effect : effects) {
      if (effect->getDimension() == EffectBase::Dimension::D2D) {
        effect->draw();
      }
    }
    graphicsCore->endDrawWithD2D();
  }
}

このEffectManager::draw()を呼ぶ前に全体初期化処理を行っており、EffectManager::draw()を呼んだ後に全体終了処理を行っています。また、3D描画開始、3D描画終了、2D描画開始、2D描画終了の各処理をまとめたメソッドを作成して呼んでいるのが分かると思います。そして、各エフェクトに2Dで描画すべきか3Dで描画すべきかを判定させるためのメンバdimensionを持たせているシンプルな設計です。これでレイヤ毎に3D描画、2D描画を実施し、例えば、最上位のレイヤでポストエフェクトを描画することを3D描画側で実行できるわけです。

なお、実際は上記にもう一工夫必要です。CommandListの切替えはオーバーヘッドが高くつくため、3D側のエフェクトが存在しない場合は3D描画開始、3D描画終了を通過させない等といった追加実装が必要です。

さて、後は、最初に挙げた6つの処理のそれぞれで何を実施するべきかという解説をしましょう。

全体初期化処理

Direct3D12のCommandListを用いて以下を順に実施します。

・レンダーターゲットの設定
・バックバッファのリソースバリア設定(PRESENT⇒RENDER_TARGET)
・レンダーターゲットビュー初期化
・デプスステンシルビュー初期化

2D描画実行時にはここで利用しているCommandListをそのまま利用し続けられないため、直後に2D描画が来ても問題ないように画面の初期化結果はこの時点でバックバッファにフラッシュさせる必要があります。
そのため、全体初期化処理の最後にCommandListを実行しますが、GPUの待ち合わせは不要です。

3D描画開始

Direct3D12のCommandListを用いて以下を実施します。もちろん最後はCommandListは実行せず、各エフェクトの描画命令を積み上げられるようにしておきます。

・レンダーターゲットの設定
・ビューポートの設定
・シザー矩形の設定
ディスクリプタヒープの設定

3D描画終了

CommandListのクローズおよび実行のみ実施します。直後にGPUの待ち合わせが必要です。

2D描画開始

ここが本記事のメインコンテンツといっても良いでしょう。2D描画開始では以下を実施します。コードのほうがわかりやすいのでコードを掲載しましょう。

d3d11On12Device->AcquireWrappedResources(wrappedBackBuffer.GetAddressOf(), 1);
d2dDeviceContext->SetTarget(backBufferFor2D.Get());

WrappedResourceを掴んだままDirect3D12側の描画をしようとするとエラーが発生するため、AcquireWrappedResources()およびSetTarget()は全体初期化時ではなく、2D描画開始時に実施しなければならないことに注意して下さい。このエラーが、エラーメッセージの内容と原因が乖離しており、エラーメッセージを基に修正するのは困難で、トライ&エラーで何とかこの結論に達しました。
なお、今回の構成では2D描画開始時にバックバッファの状態がRENDER_TARGETで入ってくることになり、2D描画が完了してもすぐ次の描画に移るためRENDER_TARGETのまま抜ける必要があります。そのため、D3d11On12Device#CreateWrappedResource()の第3引数、第4引数はともにD3D12_RESOURCE_STATE_RENDER_TARGETを指定して作成する必要があります。忘れずに変更しておきましょう。

2D描画終了

以下を実施します。ここもコードを掲載します。

d3d11On12Device->ReleaseWrappedResources(wrappedBackBuffer.GetAddressOf(), 1);
d3d11On12DeviceContext->Flush();

直後にDirect3D12側でGPUの待ち合わせを実施しなくても問題なかったため、おそらくFlush()内部でバックバッファへの出力とGPUの待ち合わせの両方を実施していると思われます。

全体終了処理

Direct3D12側のCommandListを用いてバックバッファのリソースバリアの設定(RENDER_TARGET⇒PRESENT)のみを行い、CommandListを実行します。リソースバリアの設定しかしていないので、GPUの待ち合わせはもちろん不要です。その後、バックバッファのPresentを行います。
このためだけにCommandListの準備を行うのはもったいなく感じますが、このタイミングで実施する以外に方法はないと思われます。もしより良い方法があれば教えてください!

おわりに

今回はとにかく設計を考えるのが大変だったのと、エラーメッセージが役に立たないGPUエラーに苦しめられました。D3D11On12の内部実装を推測しながら対処を打つ必要があり難しかったのですが、理解が深まり良い経験になったとは思います。(誤った推測の上での理解をしていなければよいのですが……。ご指摘があればお待ちしております)

MobiVMでビルドしたアプリがiCloud Driveにアクセスできるようにする

はじめに

今回は、MobiVMでビルドしたアプリが、GKSavedGame等の機能の利用時にiCloud Driveにアクセスできるようにする方法を共有します。
MobiVMについては以下の前回記事をご確認ください。
deep-verdure.hatenablog.com

MobiVMでビルドしたアプリが、本記事の対応をせずにiCloud Driveにアクセスしようとすると、以下のようなエラーメッセージが出力されます。

Error Domain=GKErrorDomain Code=27 "The requested operation could not be completed because you are not signed in to iCloud or have not enabled iCloud Drive" UserInfo={NSLocalizedDescription=The requested operation could not be completed because you are not signed in to iCloud or have not enabled iCloud Drive}

Entitlements.plist.xmlの追加

MobiVMではRoboVMと同様に、Xcodeプロジェクトファイルが存在しません。そのため、iCloud Driveの利用に必要な資格設定をXcode上で実施できません。

ではどうすればよいのかというと、通常はXcodeが自動出力する、資格情報を記述したファイルであるEntitlements.plist.xmlを手動作成し、MoviVMプロジェクト直下(robovm.xmlと同じ場所)に格納します。

そして、robovm.xmlに以下の一行を追加し、ipaビルド時に自作したEntitlements.plist.xmlが参照されるようにします。

<iosEntitlementsPList>Entitlements.plist.xml</iosEntitlementsPList>

Entitlements.plist.xmlの内容も掲載します。「iCloud Containers IDを指定」となっている箇所には、払出済みのiCloud Containers IDを指定してください。
設定内容の詳細は、以下の公式リファレンスをご確認ください。
https://developer.apple.com/documentation/bundleresources/entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.developer.icloud-container-environment</key>
    <array>
      <string>Production</string>
    </array>
    <key>com.apple.developer.icloud-container-identifiers</key>
    <array>
      <string><!-- iCloud Containers IDを指定 --></string
    </array>
    <key>com.apple.developer.icloud-services</key>
    <array>
      <string>CloudDocuments</string>
    </array>
    <key>com.apple.developer.ubiquity-container-identifiers</key>
    <array>
      <string><!-- iCloud Containers IDを指定 --></string>
    </array>
  </dict>
</plist>

シミュレータ利用時の注意点

シミュレータでMobiVMプロジェクトの動作確認を実施する場合、自作したEntitlements.plists.xmlを読み込むことができず、設定が空のEntitlements.plists.xmlが既定のものとして一時的に作成、適用される仕様となっています。(MobiVMが残すビルドログを細かく確認すると気付けます……)
そのため、自作したEntitlements.plist.xmlは実機でしか動作確認できないことに注意して下さい。

私はこれに気付かずに頭を抱え、かなりの時間を無駄にしてしまいました……

libGDXでMobiVMを用いてiOS版アプリを開発する

はじめに

2018年11月に、RoboVMが開発終了し、iOS12が非対応となったため、libGDXのiOSバックエンドをIntel-MOEに変更しなければならなくなった旨について記事を書きました。
deep-verdure.hatenablog.com

Intel-MOEも大元のIntel側での開発が終了し、有志によるコミュニティ版のアップデートが続けられていましたが、そんな中、2020年10月31日のlibGDX ver 1.9.12で衝撃的な変更が加えられます。

・As announced in Status Report #1, the iOS MOE backend was removed in favour of the RoboVM one.

libGDXはIntel-MOEを非対応とし、RoboVMを優先するとのコメントがリリースノートに記載されています。

変更コストを鑑みて、Intel-MOEでのビルド時のみver 1.9.11のlibGDXコンポーネントを用いるような構成とし、今日まで凌いでいましたが、2022年10月中旬頃のXcodeの更新後、moe-binding内のコードがエラーを出力するようになり、とうとうビルドができなくなってしまいました。libGDXの声明通り、RoboVMに戻る時が来たのです。しかし、RoboVMは開発終了したはず……。一体どうすればよいのでしょうか?

実は、RoboVMからフォークされ、最新のiOSに対応したオープンソースのクロスコンパイラが登場していました。それがMobiVMです。(一部でZombie RoboVMと言われていたり……)
mobivm.github.io

libGDX ver 1.9.12以降で記述されているRoboVMは、実際にはこのMobiVMを指しています。
(実は、libGDXのプロダクトページと、MobiVMのプロダクトページはUIがそっくりです。libGDXがIntel-MOEを切り捨て、MobiVMを優先した理由が読み取れる気がします)

本記事では、MobiVMを用いたiOSアプリ開発と、それに付随するlibGDX ver 1.11.0に完全対応したDesktop版の開発における注意点を説明します。

MobiVMを用いたiOS版アプリの開発

MobiVMを採用するとなると、libGDXの最新バージョンを全面的に使用可能になります。ios-moeプロジェクトが存在していた頃のlibGDXと比較すると、トップレベルから各プロジェクトに至るまで、build.gradleを始めとした様々な資材の内容が大幅に変更されています。
そこでおすすめしたいのが、最新のgdx-setup.jarを用いてプロジェクトを作成し、そこに今まで利用していたandroidios、desktop、core内のソースファイルを持ってくることです。思い切って綺麗にしてしまいましょう。

MobiVMを利用したエミュレータによるアプリ実行およびIPAの生成は、かつてのRoboVMを用いた方法と変わりありません。(Intel-MOEを使用していた場合、思い出すのに苦労するかもしれませんが……)
一点注意するとすれば、MobiVMプラグインを導入したAndroid Studio for MacでRun/Debugの構成設定編集をする際に、「RoboVM iOS」を押下してから設定項目欄が表示されるまでの間に結構な時間がかかることです。この間にAndroid Studio for Macの別のところをクリックしたりしてしまうとフリーズします。
私は当初これに気付かず、MobiVMプラグインに問題があるのではと疑って無駄な調査時間を過ごしてしまいました……。「RoboVM iOS」を押下したらじっと待ちましょう。

libGDX ver 1.11.0自動生成プロジェクトにおけるDesktop版アプリのエントリポイントについて

libGDX ver 1.11.0において、Desktop版アプリのエントリポイントをKotlinで記述する場合は注意が必要です。
以前はmain関数のみ記述したファイルをAndroid Studio for Macの構成設定編集で指定して実行が可能でしたが、ver 1.11.0の自動生成プロジェクトにおいてはその方法が使用できなくなりました。
Gradleスクリプトからのエントリポイント起動のみ受け付けるため、Javaで記述されたエントリポイントと正しく同じものをKotlinで記述しなければなりません。以下を参考に記述してください。

object DesktopLauncher {
    @JvmStatic
    fun main(args: Array<String>) {
        val gameMainObject = GameMain() //Gameを継承した独自クラス
        val config = Lwjgl3ApplicationConfiguration()
        config.setTitle("title")
        config.setWindowIcon("img/desktop/icon.png")
        Lwjgl3Application(gameMainObject, config)
    }
}

なお、libGDX ver 1.11.0からは、LwjglApplicationではなくLwjgl3Applicationクラスを利用するようになっています。これはM1 Mac対応のために拡張されたLwjglApplicationクラスです。

ちなみに、LwjglApplicationクラスでは、widthフィールドとheightフィールドの値を設定することでウィンドウサイズを設定できていましたが、Lwjgl3Applicationクラスではその方法は使えなくなっています。
coreプロジェクト側でGdx.graphics.setWindowedMode()関数を呼び出して、ウィンドウサイズを設定しましょう。