METAL-NYUMON

「技術書でご飯は食べられるのか? #技術書典 」の記事を読み、なんとなく買う機会を逃していた《Metal入門》 堤修一【著】を購入してみました。「いまでも日本語では唯一のMetalのまとまった情報源」と記事内に書かれているように、ざっと読んでMatalの概要を知ることがでるので、ありがたいです。

少し前に《Metal入門》の8章にある「8.7 GLSLをMSLに移植する」の雛形であるところの[iOS] Metalシェーダことはじめ - WebGL/GLSLの豊富なサンプルを参考にMSLを書くを読んで、GLSL Sandboxを元にしているのなら、MSLでもライブコーディングができたらいいなと、そのようなアプリケーションをつくったのですが、今回《Metal入門》を読んでみてコードを少し更新しましたので、拙いですがサンプルコードを公開します。(macOS/Objective-C++で申し訳ないですが)

Metalはシェーダプログラムのファイル(.metal)を、Xcodeでアプリケーションをビルドする際にライブラリファイル(.metallib)としてコンパイルします。このライブラリファイルをアプリケーションのプログラムで読み込むのが一般的なようです。

id<MTLLibrary> library = [_device newLibraryWithFile:
    [[NSBundle mainBundle] pathForResource:@"main" ofType:@"metallib"] 
    error:nil
];

GLSLのように文字列から実行時にシェーダプログラムをコンパイルすることも可能なようですが、Lintなどを考慮するとMetal Tool(Command Line)を使って事前にコンパイルされたものを読むほうがベターな気がします。

シェーダプログラムのビルド方法等はUsing Command Line Utilities to Build a Libraryに詳細があります。

xcrun -sdk macosx metal -c $filename -o main.air; xcrun -sdk macosx metallib main.air -o main.metallib

このスクリプトを叩くことでmain.metalからmain.metallibが作成できます。(-cのオプションがXcode 10のCommand Line Toolsから必要な模様です)エディタでシェーダを書いてスクリプトを実行し、アプリケーション側でmain.metallibが更新されたかどうかを監視して、更新があればライブラリファイルを読み込むことでMSLのライブコーディングができます。

私はCodeRunnerというエディタを愛用しているのですが、このエディタは⌘+Rで任意のスクリプトを実行できます。スクリプトの設定の方法はCodeRunnerのメニューのPreferenceからLanguagesのタブを開き、下部の+ボタンでMetalのセッティングを新規につくりRun Command:に上記スクリプトをペーストするだけです。

Metal-Nyumon.app/Contents/Resources/

のパスにあるmain.metalをエディタで開いて、値や処理を変えたりして⌘+Rを押しRunすれば、main.metallibにコンパイルされて、リアルタイムにシェーダが更新されることが確認できます。

コンパイルされたmain.metallibですが、読み込むアプリケーション側の実装で気をつけないといけない点としてnewLibraryWithFile:error:を使いファイルのパスでライブラリファイルを読み込もうとするときに、同じ名前のmain.metallibだと、キャッシュされているのかシェーダが反映されません。newLibraryWithData:error:を使いバイナリデータで読む必要があります。ただこのメソッドの第一引数のdataはNSData型でなくdispatch_data_t型となっているので読み込みが少し面倒でした

MSLを移植する際には《Metal入門》 の「8.7 GLSLをMSLに移植する」にGLSLとMSLの型および修飾子の対応表がありましたが、こんな感じで型を定義しておくと手間が下がります。

#define vec2 float2

またGLSLのビルドイン関数との違いについてはMetal組み込み関数という記事が参考になりました。

《Metal入門》には載っていなかったのですが、個人的に便利だと思うメソッドにgetBytes:bytesPerRow:fromRegion:mipmapLevel:があります。

OpenGLでいうところのglReadPixelに相当するもので、簡単に説明するとGPUの描画内容をピクセルデータとして取得するメソッドです。glReadPixelsは重かった記憶がありますがgetBytes:bytesPerRow:fromRegion:mipmapLevel:は1920x1080のサイズでピクセルデータを取得しても0.003secくらいの処理時間でした。

例えばGPUの描画内容をピクセルデータとして取得できれば、⌘+Cを押すとPNGとしてクリップボードへのコピーする機能の実装ができます。

シェーダのコードは《Metal入門》にならってhttp://glslsandbox.com/e#36694.0を使わせていただきました。

この記事が気に入ったらサポートをしてみませんか?