ハイライト
metal-cpp は、C 言語を通じて Objective-C ランタイムを直接呼び出す、ヘッダーのみの C++ パッケージ ライブラリです。 C++ コードの Metal API を 100% カバーし、Objective-C Metal 関数呼び出しとほぼ 1 対 1 の対応を持ちます。開発者ツール (GPU フレーム キャプチャ、Xcode デバッガー) と完全に互換性があります。
主要内容
ゲーム エンジンやグラフィック アプリケーションは C++ で書かれていますが、Metal はネイティブに Objective-C に基づいています。以前は、2 つの言語を行き来するか、Objective-C でレンダリング層全体を書き直す必要がありました。 metal-cpp はこの問題点を解決します。
(01:09) metal-cpp は軽量の C++ パッケージ化ライブラリです。軽量: ヘッダーのみの実装であり、すべての関数呼び出しはインラインです。これは、C 言語を通じて Objective-C ランタイムを直接呼び出します。これは、メソッド呼び出しを実行する Objective-C コンパイラーとまったく同じメカニズムであるため、追加のオーバーヘッドは最小限に抑えられます。
(02:47) metal-cpp は 100% Metal API をカバーします。また、Foundation および QuartzCore フレームワークの一部のインターフェイスもカプセル化されます。コードはオープンソースであり、Apache 2 に基づいてライセンスされており、直接変更してプロジェクトに統合できます。
1 対 1 のマッピングがあるため、すべての開発者ツールがシームレスに動作します。 GPU フレーム キャプチャ、Xcode デバッガー、およびパフォーマンス分析ツールには特別な構成は必要ありません。
詳細
クイックスタート
(05:30) metal-cpp を使用するには、次の 3 つの手順が必要です。
- metal-cpp をダウンロードし、Xcode プロジェクトのヘッダー検索パスに追加します。
- C++ 言語標準を C++17 以降に設定します。
- 3 つのフレームワークを追加します: Foundation、QuartzCore、および Metal
次に、.cpp ファイルに 3 つのマクロを定義し、ヘッダー ファイルを導入します。
#define NS_PRIVATE_IMPLEMENTATION
#define CA_PRIVATE_IMPLEMENTATION
#define MTL_PRIVATE_IMPLEMENTATION
#include <Foundation/Foundation.hpp>
#include <Metal/Metal.hpp>
#include <QuartzCore/QuartzCore.hpp>
キーポイント:
- これら 3 つのマクロは、コンパイル中に対応するフレームワークの実装コードを生成します。
- 各マクロは 1 回だけ定義できます。繰り返し定義するとリンクエラーが発生します。
- 3 つのヘッダー ファイルを統合ヘッダー ファイルにマージして導入可能
三角形を描く
(03:10) metal-cpp を使用して三角形を描画するコード:
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();
Objective-C バージョンと比較します。
id<MTLCommandBuffer> cmd = [_commandQueue commandBuffer];
id<MTLRenderCommandEncoder> enc = [cmd renderCommandEncoderWithDescriptor:pRpd];
[enc setRenderPipelineState:_pSO];
[enc drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:3];
[enc endEncoding];
[cmd presentDrawable:view.currentDrawable];
[cmd commit];
この 2 つの関数呼び出しはほぼ同じですが、命名スタイルが異なるだけです。すでに Objective-C Metal に精通している場合、metal-cpp に切り替えるための学習曲線は非常に簡単です。
オブジェクトのライフサイクル管理
(06:56) metal-cpp は手動参照カウント (Manual Retain-Release、MRR) を使用し、Cocoa のメモリ管理ルールに従います。
- 使用
alloc、new、copy、mutableCopy、create冒頭のメソッドでオブジェクトが作成され、それを所有するのはあなたです - 使用
retain所有権を取得します - 使用
releaseリリースの所有権 - 所有していないオブジェクトは解放できません
(10:08) Metal は実行時にいくつかの自動解放された一時オブジェクトを作成します。これらのオブジェクトは AutoreleasePool に置かれ、オブジェクトの解放を担当します。スレッドごとに 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();
キーポイント:
commandBuffer()、currentRenderPassDescriptor()、currentDrawable()どちらも自動解放されたオブジェクトを返します- AutoreleasePool による
alloc()->init()作成するには、手動で行う必要がありますrelease() - 各スレッドには独自の AutoreleasePool が必要です
NS::SharedPtr スマート ポインター
(13:40) metal-cpp が提供するNS::SharedPtrライフサイクル管理を簡素化します。それはに関連していますstd::shared_ptr違い: C++ 標準ライブラリに依存せず、参照カウントはオブジェクト内に直接保存されます。
// TransferPtr:转移所有权,不增加引用计数
{
auto ptr = NS::TransferPtr(pMRR);
// ptr 离开作用域时自动 release
}
// RetainPtr:保留对象,增加引用计数
{
auto ptr = NS::RetainPtr(pMRR);
// ptr 离开作用域时自动 release,原始对象仍存活
}
(16:48)NS::TransferPtrレンダリング パイプライン状態オブジェクトを管理します。
// 创建 descriptor 和 pipeline state
MTL::RenderPipelineDescriptor* pDesc = MTL::RenderPipelineDescriptor::alloc()->init();
// ... 配置 descriptor ...
MTL::RenderPipelineState* pPSO = _pDevice->newRenderPipelineState(pDesc, &pError);
// 用 TransferPtr 接管所有权,无需手动 release
auto pPSOPtr = NS::TransferPtr(pPSO);
auto pDescPtr = NS::TransferPtr(pDesc);
// 使用 pPSOPtr->... 访问方法
// 离开作用域时自动释放
デバッグツール: NSZombie
(17:55) use-after-free は手動メモリ管理における一般的なバグです。 NSZombie は、この種の問題を解決するのに役立ちます。 Xcode スキームで Zombie オブジェクトを有効にするか、環境変数を設定しますNSZombieEnabled=YES。解放されたオブジェクトにメッセージが送信されると、ブレークポイントがトリガーされ、呼び出しスタックが表示されます。
Objective-C コードとの相互運用
(19:50) ほとんどのゲームは、依然として Objective-C API である他のフレームワーク (ゲーム コントローラー、オーディオなど) を呼び出す必要もあります。アダプター パターンを使用して 2 つの言語を分離することをお勧めします。
Objective-C から C++ を呼び出す:
@interface AAPLRendererAdapter () {
AAPLRenderer* _pRenderer;
}
@end
@implementation AAPLRendererAdapter
- (void)drawInMTKView:(MTKView *)pMtkView
{
_pRenderer->draw((__bridge MTK::View*)pMtkView);
}
@end
C++ から Objective-C を呼び出す:
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];
}
3 つのブリッジ タイプ:
__bridge:型変換のみを行い、所有権は移行しません。__bridge_retained: Objective-C (ARC) から metal-cpp (MRR) への移行、所有権の取得__bridge_transfer: metal-cpp (MRR) から Objective-C (ARC) への所有権の移転
(24:06)__bridge_retainedMetalKit からテクスチャをロードします。
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];
// 从 ARC 夺取所有权,转为 MRR 管理
return (__bridge_retained MTL::Texture*)texture;
}
キーポイント:
pDevice使用__bridge所有権が移転されないための変換- 返されました
texture使用__bridge_retained所有権が ARC から奪われ、呼び出し元の責任となるため、変換されます。release
重要ポイント
既存の C++ ゲーム エンジンを Apple Silicon に移植
ゲーム エンジンが C++ で記述されている場合、レンダリング レイヤーを Objective-C に書き直すことなく、Metal レンダリング バックエンドを直接統合できるようになりました。 metal-cpp の API は Objective-C Metal と 1 対 1 に対応しており、既存の Metal チュートリアルとドキュメントは完全に適用可能です。 metal-cpp のダウンロードから最初の三角形の描画まで、3 つのマクロとヘッダー ファイル パスを設定するだけで済みます。
C++ プロジェクトでのメタル パフォーマンス シェーダーの使用
画像処理または機械学習推論を行う C++ プロジェクトの場合、metal-cpp を使用して MPS および MPSGraph を呼び出すことができます。組み合わせるNS::SharedPtrオブジェクトのライフサイクルを管理し、手動での保持/解放に伴う面倒さやエラーのリスクを回避します。
クロスプラットフォーム レンダリング抽象化レイヤーの構築
エンジンが Metal (Apple) と Vulkan/DirectX (他のプラットフォーム) の両方をサポートする必要がある場合は、metal-cpp を使用して Metal バックエンドをプラットフォームに依存しない C++ インターフェイスにカプセル化できます。アダプター モードでは、Objective-C フレームワーク (MetalKit など) の呼び出しを少数のアダプテーション ファイルに分離でき、メイン エンジン コードは純粋な C++ のままです。
関連セッション
- Discover Metal 3 — Metal 3 の新機能の概要
- Apple GPU 全体でコンピューティング ワークロードをスケールする — Apple GPU 全体でコンピューティング ワークロードをスケールする
- メタル メッシュ シェーダーを使用してジオメトリを変換する — メタル メッシュ シェーダーのジオメトリ処理パイプライン
コメント
GitHub Issues · utterances