- 概要
- フロー
- RenderTextureを用意
- CommandBufferを用意する
- ReadPixelsでピクセル情報を読み出す(※ 必要ありませんでした)
- RenderTextureのGetNativeTexturePtrを使ってネイティブテクスチャのポインタを取得
- ネイティブプラグインへテクスチャのポインタを送る
- テクスチャを適切な形に変換する
- UIImageとして保存する
- まとめ
- その他の役立ちそうなメモ
概要
今回はiOSのネイティブプラグインを利用してRenderTexture
をそのままファイル保存するプラグインを作ったので、作る過程で得られた知見やハマりどころなどをメモしていきたいと思います。
RenderTexture
にはTexture2D
が持っているEncodeToPNG
やEncodeToJPG
メソッドがなく、さらにそもそもEncodeToPNG
などは非常に重い処理となっています。
なのでRenderTexture
をそのままネイティブ側に渡してそれが保存できないかな、と思ったのが実装するに至った経緯です。
実際に実行したときの動画↓
I've created native plugin of Unity what save a render texture as MTLTexture to a file directly. #Unity #iOS #Metal pic.twitter.com/WCVESDKDSl
— edom18@XR / MESON CTO (@edo_m18) 2019年8月19日
今回の実装にあたり、ふじきさんには多大なご助力をいただきました。本当にありがとうございます。
ふじきさんが作られた動画キャプチャツールの投稿を見たのがきっかけで質問させていただき、色々ご教授いただきました。
そのツールはこちら↓
これ↓についてQiitaに少し書きました。https://t.co/UTId87TK6J
— ふじき (@fzkqi) 2019年8月17日
Qiitaはこちら↓https://t.co/zSWGPlevEP #Qiita
フロー
まずは全体の流れを把握するためにフローを概観してみます。
RenderTexture
を用意CommandBuffer
を利用して画面をキャプチャTexture2D
のReadPixels
を利用してRenderTexture
のピクセル情報を読み出す(*1)RenderTexture
のGetNativeTexturePtr
を使ってネイティブテクスチャのポインタを取得- (4)のポインタをネイティブプラグインへ送信
- (5)のテクスチャを適切な形に変換する
- (6)のデータをUIImageにして保存
*1 ... ReadPixelsを行わないと(おそらく)GPUにデータがすぐにアップロードされず、画像保存に失敗します。もしかしたらGL.IssuePluginEventを利用するとうまくいくかもしれません。
色々調査した結果、1フレーム遅延させることでReadPixels
を使わなくても正常に保存することができました。
以上が大まかな処理の流れとなります。
今回実装したものはGitHubに上げてあるので動作を見たい方はそちらをご覧ください。
RenderTextureを用意
ここはむずかしいところはありません。
ファイルとして用意してもいいですし、ランタイムで用意してもよいです。
今回はランタイムで以下のように、Start
時に用意するようにしています。
_buffer = new RenderTexture(Screen.width, Screen.height, 0); _buffer.Create();
CommandBufferを用意する
コマンドバッファは画面をキャプチャする用途で利用しています。
セットアップは以下のように行っています。
_commandBuffer = new CommandBuffer(); _commandBuffer.name = "CaptureScreen"; _buffer = new RenderTexture(Screen.width, Screen.height, 0); _buffer.Create(); _commandBuffer.Blit(BuiltinRenderTextureType.CurrentActive, _buffer); // スクショを撮るタイミングでカメラにコマンドバッファをアタッチ Camera.main.AddCommandBuffer(CameraEvent.BeforeImageEffects, _commandBuffer);
コマンドバッファはシンプルに、現在のアクティブなレンダーバッファの内容をレンダーテクスチャにコピーしているだけです。
コマンドバッファをアタッチするタイミングはスクショを撮るタイミングです(スクショ撮影用メソッドを呼ぶタイミング)。
CommandBufferについては凹みさんがこちらの記事でとても詳しく解説されています。
ReadPixelsでピクセル情報を読み出す(※ 必要ありませんでした)
キャプチャを行ったらTexture2D
のReadPixels
でピクセル情報を読み出します。
保存だけを行いたい場合はこれは不要な処理となりますが、GPUへのデータアップロードの関連なのか、これを行わないとネイティブ側の保存時に空白の画像が保存されてしまい、うまくいきませんでした。
フローのところでも書きましたが、もしかしたらGL.IssuePluginEvent
を利用して読み出すことでうまく動くかもしれません。(これについては後日調査します)
CommandBufferでキャプチャ後、その後すぐに保存するのではなく、1フレーム遅延させることで正常に保存できることを確認しました。
コード的には以下のようにしています。(ReadPixels
していたコードを変更しています)
private IEnumerator SaveTexture() { yield return _waitForEndOfFrame; Debug.Log("Save texture to the file."); Camera.main.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, _commandBuffer); _image.texture = _buffer; // To save the RenderTexture as file needs to wait one frame. yield return _waitForEndOfFrame; Debug.Log("Will show the texture."); _SaveTextureImpl(_buffer.GetNativeTexturePtr(), gameObject.name, nameof(CallbackFromSaver)); }
RenderTextureのGetNativeTexturePtrを使ってネイティブテクスチャのポインタを取得
ここがひとつめの重要な点です。
UnityのTexture2D
とRenderTexture
にはGetNativeTexturePtr
というメソッドがあり、ネイティブ側のテクスチャのポインタを取得する方法があります。
今回のプラグインではネイティブ側で、この生成済みのテクスチャのポインタを利用して処理を行います。
取得して送信している箇所を抜粋すると以下のようになります。
// 第2、第3引数はネイティブ側からコールバックを受け取るためのもの _SaveTextureImpl(_buffer.GetNativeTexturePtr(), gameObject.name, nameof(CallbackFromSaver));
GetNativeTexturePtr
のドキュメントは以下です。
ドキュメントから抜粋すると以下のように書かれています。
On Direct3D-like devices this returns a pointer to the base texture type (IDirect3DBaseTexture9 on D3D9, ID3D11Resource on D3D11, ID3D12Resource on D3D12). On OpenGL-like devices the GL texture "name" is returned; cast the pointer to integer type to get it. On Metal, the id
pointer is returned. On platforms that do not support native code plugins, this function always returns NULL.
要するにこれはプラットフォームによって返ってくる意味が違っているということです。(プラットフォームごとに適切なテクスチャへのポインタが返ってくる)
そして今回はiOS向けの話なのでid<MTLTexture>
へのポインタが返ってくることが分かります。
On Metal, the id
pointer is returned.
ネイティブプラグインへテクスチャのポインタを送る
そしてこちらが重要な点のふたつめです。
前段で取得したネイティブテクスチャのポインタをネイティブプラグイン側へ送り受け取る必要があるわけですが、適切にキャストして利用する必要があります。
これを間違えるとクラッシュしたり、ということがあるので気をつけてください。
ポインタを取得してキャストするコードは以下のようになります。
extern "C" void _SaveTextureImpl(unsigned char* mtlTexture, const char* objectName, const char* methodName) { id<MTLTexture> tex = (__bridge id<MTLTexture>)(void*)mtlTexture; // 後略 }
まず、テクスチャのポインタはunsigned char*
型で受け取ります。
そしてそれを(__bridge id<MTLTexture>)(void*)mtlTexture;
のように、いったんvoid*
型を経由してから最後にid<MTLTexture>
型にキャストします。
加えて注意点として、C側からObjective-C側へのキャストには(__bridge)
を利用してキャストを行う必要があります。
このブリッジにはいくつか種類があり、ARC管理下にいれるか、など状況に応じて使い分ける必要があります。
__bridge
を利用したキャストについては以下の記事を参考にしてみてください。
また今回はUnity側で生成したテクスチャなのでGC対象にもなっています。
そのため、ネイティブ側で管理対象にはせず、そのままキャストするだけに留めます。
これを、ARC管理対象などにしてしまうとBAD ACCESSでアプリがクラッシュするので注意が必要です。
(ここらへんまだ詳しくないのであれですが、万全を期すなら、Unity側でGC対象のポインタをロック(GCHandle.Alloc)してメモリ位置が変更されないようにするなどのケアは必要かもしれません。が、今回はとにかく保存するところまでを書くのでこのあたりには触れません)
テクスチャを適切な形に変換する
テクスチャのポインタを受け取り、テクスチャの情報にアクセスすることができるようになりましたが、このままだと各ピクセルのデータの並びが異なっているため色味が反転したような絵になって保存されてしまいます。
これに対して、適切に対処してくれるコードを公開してくれていた記事があったのでこちらを参考にさせていただきました。
具体的に何をしているかというと、取得したMTLTexture
はRGBAではなくBGRAの並びになっているので、それをRGBAな形に並び替える処理をしています。
上下の反転について
また参考にした記事では、環境によっては上下が反転してしまうため、それを補う処理も同時に施されています。
該当のコードは以下のようになっています。
// flipping image vertically let flippedBytes = bgraBytes // share the buffer var flippedBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: flippedBytes), height: vImagePixelCount(self.height), width: vImagePixelCount(self.width), rowBytes: rowBytes) vImageVerticalReflect_ARGB8888(&rgbaBuffer, &flippedBuffer, 0)
ただ今回のサンプルでは上下の反転は必要なかったのでコメントアウトしてあります。
上下の反転があったら上のコードを試してみてください。
この工程を経ることで無事、望んだ形のデータが手に入ります。
上の記事ではMTLTexture
の拡張として書かれているのでこれを少しだけ改変してコンバータクラスとして実装しました。
実際に利用しているコードは以下です。
// // MTLTexture+Z.swift // ZKit // // The MIT License (MIT) // // Copyright (c) 2016 Electricwoods LLC, Kaz Yoshikawa. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import Foundation import CoreGraphics import MetalKit import GLKit import Accelerate class MTLTextureConverter : NSObject { @objc static func convert(texture: MTLTexture) -> UIImage? { assert(texture.pixelFormat == .bgra8Unorm) // read texture as byte array let rowBytes = texture.width * 4 let length = rowBytes * texture.height let bgraBytes = [UInt8](repeating: 0, count: length) let region = MTLRegionMake2D(0, 0, texture.width, texture.height) texture.getBytes(UnsafeMutableRawPointer(mutating: bgraBytes), bytesPerRow: rowBytes, from: region, mipmapLevel: 0) // use Accelerate framework to convert from BGRA to RGBA var bgraBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: bgraBytes), height: vImagePixelCount(texture.height), width: vImagePixelCount(texture.width), rowBytes: rowBytes) let rgbaBytes = [UInt8](repeating: 0, count: length) var rgbaBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: rgbaBytes), height: vImagePixelCount(texture.height), width: vImagePixelCount(texture.width), rowBytes: rowBytes) let map: [UInt8] = [2, 1, 0, 3] vImagePermuteChannels_ARGB8888(&bgraBuffer, &rgbaBuffer, map, 0) // flipping image virtically // let flippedBytes = bgraBytes // share the buffer // var flippedBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: flippedBytes), // height: vImagePixelCount(texture.height), width: vImagePixelCount(texture.width), rowBytes: rowBytes) // vImageVerticalReflect_ARGB8888(&rgbaBuffer, &flippedBuffer, 0) // create CGImage with RGBA let colorScape = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) guard let data = CFDataCreate(nil, rgbaBytes, length) else { return nil } guard let dataProvider = CGDataProvider(data: data) else { return nil } let cgImage = CGImage(width: texture.width, height: texture.height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: rowBytes, space: colorScape, bitmapInfo: bitmapInfo, provider: dataProvider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) return UIImage(cgImage: cgImage!) } }
UIImageとして保存する
前段までで無事にUnityからテクスチャを受け取ることができました。あとはこれをUIImageとしてファイルに保存すれば終了です。
シンプルに保存するだけでいいのであれば以下のように1行で書くことができます。
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
なお、保存後にコールバックを受け取りたい場合は適切にセットアップして、コールバックを受け取るオブジェクトを生成する必要があります。
具体的には以下のシグネチャを持つオブジェクトを生成し、セレクタを渡してやることで実現できます。
期待されるシグネチャ
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo;
これを実装したオブジェクトを作ると以下のようになります。
(Unityへのコールバックをする部分も一緒に実装した例です)
#import "CaptureCallback.h" @implementation CaptureCallback - (id)initWithObjectName:(NSString *)_objectName methodName:(NSString *)_methodName; { if (self = [super init]) { self.objectName = _objectName; self.methodName = _methodName; } return self; } - (void)savingImageIsFinished:(UIImage *)_image didFinishSavingWithError:(NSError *)_error contextInfo:(void *)_contextInfo { const char *objectName = [self.objectName UTF8String]; const char *methodName = [self.methodName UTF8String]; if (_error != nil) { NSLog(@"Error occurred with %@", _error.description); UnitySendMessage(objectName, methodName, [_error.description UTF8String]); } else { UnitySendMessage(objectName, methodName, "success"); } } @end
これを実際に使うと以下のようになります。
CaptureCallback *callback = [[CaptureCallback alloc] initWithObjectName:@"obj" methodName:@"method"]; UIImageWriteToSavedPhotosAlbum(image, callback, @selector(savingImageIsFinished:didFinishSavingWithError:contextInfo:), nil);
詳細は以下のドキュメントをご覧ください。
保存パスをコールバックで受け取る
上記の関数では保存と保存後のコールバックを受け取れても、保存したファイルのパスを知ることができません。
これを実現するためにはPHPhotoLibrary
のperformChanges
と、PHImageManager
のrequestImageDataForAsset:
を利用します。
コードは以下のようになります。
ポイントはいくつかの非同期関数を連続で呼び出し、最終的にファイルパスを取得している点です。
__block NSString* localId; // Add it to the photo library [PHPhotoLibrary.sharedPhotoLibrary performChanges:^{ PHAssetChangeRequest *assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image]; localId = assetChangeRequest.placeholderForCreatedAsset.localIdentifier; } completionHandler:^(BOOL success, NSError *err) { if (!success) { NSLog(@"Error saving image: %@", err.localizedDescription); [callback savingImageIsFinished:nil didFinishSavingWithError:err]; } else { PHFetchResult* assetResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[localId] options:nil]; PHAsset *asset = assetResult.firstObject; [PHImageManager.defaultManager requestImageDataForAsset:asset options:nil resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { NSURL *fileUrl = [info objectForKey:@"PHImageFileURLKey"]; if (fileUrl) { NSLog(@"Image path: %@", fileUrl.relativePath); [callback savingImageIsFinished:fileUrl didFinishSavingWithError:nil]; } else { NSLog(@"Error retrieving image filePath, heres whats available: %@", info); [callback savingImageIsFinished:nil didFinishSavingWithError:nil]; } }]; } }];
やや複雑ですが、これで保存先のパスを得ることができます。
まとめ
ひとまずここまでで、Unity側で生成したテクスチャをネイティブ側に送りファイルに保存することができました。
今回は保存することを目的に作成しましたが、ネイティブ側で加工をしたり、ネイティブ側で生成したものをUnity側に送るなど、活用の幅は広いと思います。
ネイティブと友だちになれるとやれることが格段に増えるのでぜひともマスターしておきたいですね。
そして以下からは、今回のプラグインを作るにあたってハマった点や試したことなど、後々役に立ちそうなものをメモとして残しておきます。
興味がある方は読んでみてください。
その他の役立ちそうなメモ
Native Plugin Interfaceについて
最後に、今回の実装とは直接は関係ありませんが、デバイス情報(MTLDevice
)などを利用したいケースがある場合にUnityのAPIからそれらデバイスへの参照の取得方法を紹介していきます。
これらはNative Plugin Interface
としてUnityから提供されているAPIになります。
デバイスへの参照を取得する
デバイスへの参照などを得るためにUnityはIUnityInterfaces
というインターフェースを用意しています。
これはプラグインが読み込まれた際に呼ばれるコールバック内で取得することができます。
ドキュメントにはextern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad
を公開しておけば自動的に呼ばれる、と記載されているのですがiOSだとダメなのかこれだけでは呼び出されませんでした。
そこで別の方法を利用してこのインターフェースを取得するようにしました。
具体的には以下の関数をあらかじめ呼び出すことで対応しました。
UnityRegisterRenderingPluginV5(&UnityPluginLoad, &UnityPluginUnload);
名前から分かる通り、プラグインが読み込まれた際に、引数に渡したコールバックが呼ばれる仕組みになっています。
この登録処理を、C#側から呼べるようにしておき、Start
などのタイミングで呼び出しておきます。
これら諸々を記述したコードは以下のようになります。
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces); extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginUnload(); static IUnityGraphicsMetal* s_MetalGraphics = 0; static IUnityInterfaces* s_UnityInterfaces = 0; static IUnityGraphics* s_Graphics = 0; static bool initialized = false; static void UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType) { switch (eventType) { case kUnityGfxDeviceEventInitialize: { // s_RendererType = s_Graphics->GetRenderer(); initialized = false; break; } case kUnityGfxDeviceEventShutdown: { // s_RendererType = kUnityGfxRendererNull; initialized = false; break; } case kUnityGfxDeviceEventBeforeReset: { // TODO: User Direct3D 9 code break; } case kUnityGfxDeviceEventAfterReset: { // TODO: User Direct3D 9 code break; } }; } extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces) { s_UnityInterfaces = unityInterfaces; s_Graphics = s_UnityInterfaces->Get<IUnityGraphics>(); s_MetalGraphics = s_UnityInterfaces->Get<IUnityGraphicsMetal>(); s_Graphics->RegisterDeviceEventCallback(OnGraphicsDeviceEvent); OnGraphicsDeviceEvent(kUnityGfxDeviceEventInitialize); } extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginUnload() { s_Graphics->UnregisterDeviceEventCallback(OnGraphicsDeviceEvent); } /// /// Attach the functions to the callback of plugin loaded event. /// extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API _AttachPlugin() { UnityRegisterRenderingPluginV5(&UnityPluginLoad, &UnityPluginUnload); }
特に大事な点は以下です。
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces) { s_UnityInterfaces = unityInterfaces; s_Graphics = s_UnityInterfaces->Get<IUnityGraphics>(); s_MetalGraphics = s_UnityInterfaces->Get<IUnityGraphicsMetal>(); s_Graphics->RegisterDeviceEventCallback(OnGraphicsDeviceEvent); OnGraphicsDeviceEvent(kUnityGfxDeviceEventInitialize); }
コールバックにはIUnityInterfaces
が引数として渡されてくるので、さらにそこからunityInterfaces->Get<IUnityGraphicsMetal>();
を呼び出すことでデバイスへの参照を持つオブジェクトを取得することができます。
UNITY_INTERFACE_EXPORTとUNITY_INTERFACE_API
ちなみにAPIを利用するにはUNITY_INTERFACE_EXPORT
とUNITY_INTERFACE_API
のマクロを付ける必要があります。
ぱっと見はなにをしてくれるものか分かりづらいので、定義元をメモしておきます。
定義を見てみると以下のように分岐されています。
#if defined(__CYGWIN32__) #define UNITY_INTERFACE_API __stdcall #define UNITY_INTERFACE_EXPORT __declspec(dllexport) #elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WINAPI_FAMILY) #define UNITY_INTERFACE_API __stdcall #define UNITY_INTERFACE_EXPORT __declspec(dllexport) #elif defined(__MACH__) || defined(__ANDROID__) || defined(__linux__) #define UNITY_INTERFACE_API #define UNITY_INTERFACE_EXPORT #else #define UNITY_INTERFACE_API #define UNITY_INTERFACE_EXPORT #endif
プラットフォームによっては関数をDLLにexportするために必要な指定が必要なため、また指定方法が異なるためこういうスイッチが入っています。
iOSなどその他のプラットフォームでは特になにも出力されないので、読むときは単純に関数の宣言と見ておいて大丈夫です。
ちなみに__stdcall
は関数の呼び出し規約となっていて、アセンブラレベルでは関数に対して引数をどう渡すか、また戻り値をどう受け取るか、という取り決めを事前にしておく必要があります。
その取り決めを明示するものであり、これがないと引数がうまく渡せなかったり、など問題がおきます。(多分、誤った指定だとエラーになるか、実行時にクラッシュします。試したこと無いので推測ですが・・・)
このあたりについては以下の記事が参考になるかもしれません。
ハマった点
Photo Libraryへのアクセス権
Photo Libraryに保存するためにアクセス権を取得する必要があります。
実行時にアクセス権を確認し、アクセス権がなければ適切にリクエストする必要があります。
ちなみに確認せずに実行するとクラッシュして以下のようなエラーが出力されます。
$ Photos Access not allowed
このあたりについては以下の記事を参考に修正しました。
その他メモ
Build Settingsをポストプロセスで更新する
Swiftコードも含んでいるためBuild Settingsを修正する必要があり、それを自動化するために以下の記事を参考にさせていただきました。
MTLTextureをコピーする
実装する過程でクラッシュが発生したため、いったんテクスチャをネイティブ側でコピーして保持したらどうだ、っていうことで作ったものがあるのでメモとして残しておきます。
id<MTLTexture> CopyTexture(id<MTLTexture> source) { MTLTextureDescriptor *descriptor = [[MTLTextureDescriptor alloc] init]; descriptor.pixelFormat = MTLPixelFormatBGRA8Unorm; descriptor.width = source.width; descriptor.height = source.height; id<MTLTexture> texture = [s_MetalGraphics->MetalDevice() newTextureWithDescriptor:descriptor]; id<MTLCommandQueue> queue = [s_MetalGraphics->MetalDevice() newCommandQueue]; id<MTLCommandBuffer> buffer = [queue commandBuffer]; id<MTLBlitCommandEncoder> encoder = [buffer blitCommandEncoder]; [encoder copyFromTexture:source sourceSlice:0 sourceLevel:0 sourceOrigin:MTLOriginMake(0, 0, 0) sourceSize:MTLSizeMake(source.width, source.height, source.depth) toTexture:texture destinationSlice:0 destinationLevel:0 destinationOrigin:MTLOriginMake(0, 0, 0)]; [encoder endEncoding]; [buffer commit]; [buffer waitUntilCompleted]; return texture; }
MTLTextureをネイティブ側で生成してポインタを受け取る
今回の実装をしていく過程で、試しにネイティブ側でMTLTexture
を生成してUnity側に渡したらうまくいくかも、と思って実装したのでメモとして残しておきます。
extern "C" uintptr_t UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API _GetNativeTexturePtr(int width, int height) { MTLTextureDescriptor *descriptor = [[MTLTextureDescriptor alloc] init]; descriptor.pixelFormat = MTLPixelFormatBGRA8Unorm; descriptor.width = width; descriptor.height = height; id<MTLTexture> texture = [s_MetalGraphics->MetalDevice() newTextureWithDescriptor:descriptor]; return (uintptr_t)texture; }