【令和最新】 .NET Core で Susie Plug-in 呼び出し

みなさんは Susie をご存じでしょうか…
SusieはWindows 3.1の頃からあるグラフィクスビューアです。
画像ファイルのみならずアーカイブファイルにも対応したオープンなプラグイン仕様によって、多種多様なプラグインが有志によって作られていて、かつては日本でかなり使われていたんじゃないかというような素晴らしいビューアでした。


前置きはその辺にしておいて、.NET CoreでSusie Plug-inを呼びだして扱えるようにした。プラグイン呼び出し部分は一通り出来たので、あとはアプリケーション部分を飽きるまで。

https://github.com/longod/WhiteAlbum/tree/main/WA.Susie

 

ことの発端は、最近HDDのデータを整理をしているときに、古い画像ファイルとともに、昔使っていたプラグインが出土したことに起因する。
Susieは長らく更新が停止して動作が怪しくなりつつあるし、OSの更新でいつ動かなくなるかも分からない。プラグインに対応した他のアプリケーションも同様に軒並み古くなってしまったし、そもそもアーカイブまでサポートされたものは少なかった記憶がある。
プラグインもプラグインで、インターネット上に存在するプラグインはサイトの消滅と共に徐々に消えつつあり、惜しい状態だ。同様にインターネット上に存在するプラグインに関する情報も徐々に消えつつある。
じゃあ、今のうちに、次を満たす呼び出し側(ホスト)を作っておくか、というのが動機だ。

  • オープンソース
  • 扱いやすい
  • 今の時代にモダンに動作する
  • 将来的にも動作する可能性が高い

.NET CoreのバージョンはPrismの都合で3.1になったが、単体だけなら5とかに6に上げても動作すると思う。

x86 (32-bit) の影響を受ける

当たり前だけれど、プラグインは時代的に32-bitライブラリとして作られている。
正攻法でプラグインを呼び出すには、呼び出し側もx86でなくてはならない。それに伴って連鎖的にアプリケーション全体がx86になってしまう。今の64bit OSが主流の時代に。

プラグイン仕様書だけを見て作るのは大変

本家のプラグイン仕様書は、Plug-in packageのプラグイン集にテキストファイルとして同梱されている。プラグインを作る側はこれでも大体は問題ないが、プラグインを呼び出す側となると、Susieの内部挙動が公開されていないので大変だ。

このあたりの先人の知見は特に役に立った。

自分でダミーのプラグインを作る

いきなり既存のプラグインを叩いてみると、見事にデバッグ出来ない状況でクラッシュしたりハングアップしたりする。呼び出し方が悪いのか、プラグインそのものに問題があるのかを把握するために、自分で仕様書を元に適当なプラグインを作ってそれを呼び出してみた方が良い。
プラグインは当時の状況を想像しながら作る必要がある。

  • 当然x86で作る。原理的には、x86以外にx64版プラグインも作れるだろうけれど、それに対応したアプリは皆無だろう。
  • 時代が時代なので、非Unicodeのマルチバイト文字セットで作る。
  • dllexportは、例えばMSVCだと既に無くなっているので、コンパイラが新たに提供している命令に置き換える必要がある。
  • cdeclとstdcallを正しく指定する必要がある。正しくないといとも簡単にクラッシュする。仕様書にはPASCALを全般に渡って使用しているが、今はそれだと駄目だった。

Visual Studio 2019 Version 16.7以降でディレクトリ生成を伴うシェーダコンパイルに失敗する

8月頃にリリースされた16.7.1から、現在リリースされている16.7.5まで確認済み。
そう、二か月以上Visual Studio 2019でシェーダがコンパイルできない致命的な状態なのである。

再現手順はとても簡単で、DirectX-Graphics-SamplesのVS2019に対応した適当なリビジョン(タグ10.0.19041.0以降あたり)を取得してきて、VS2019でModelViewerをビルドするだけ。シェーダコンパイル時に大体次のような意味不明なエラーを吐く。プロファイルによってはfxcである場合もある。

dxc failed : error code 0x80070459.

詳細な条件は次の通り。

  • hlslファイルをソリューションエクスプローラーに設定して、本来なら正常にコンパイルできる設定にしてある
  • 「ヘッダー ファイル名」または 「オブジェクトファイル名」にディレクトリ生成を伴うパスを指定している
    • 例えば、CompiledShaders\%(Filename).h や $(OutDir)CompiledShaders\%(Filename).cso は該当する
    • 一方、 $(OutDir)%(Filename).cso  はディレクトリを生成しないので該当しない

この原因は以下に報告されている。

Typo in FXC Target in VS 16.7.1 (Developer Community)

要するに、「 FxcOutputs 」と記述されるべき設定が「 _FxcOutputs 」とタイポされているというあまりにもお粗末で下らないバグだ。

幸いにも、プログラムによるバグではなく、設定ファイルによるバグであるため、手元で修正することが出来る。
Visual Studio 2019のインストール場所を変更していない場合、次のファイルをテキストエディタ開く。Community以外の場合も適宜読み替えてほしい。

C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Microsoft\VC\v160\
Microsoft.CppBuild.targets

「 _FxcOutputs 」で検索すると一か所該当する箇所があるので、これを「 FxcOutputs 」に変更し保存する。ただし、管理者権限が必要である。

重複した別の報告(HLSL Shader Compiler fails to create output directory for header files)のリプライによると、Preview版では修正されたようだが、こんな致命的で馬鹿げたバグは即座にhotfixがリリースされるべきだろう。

Asynchronous Capture Screen on Unity 2019.3

Unity 2019.3から、あれこれ自作せずともやっと、ようやく、悲願の非同期スクリーンキャプチャが出来るようになりました。

前提知識として、Unity APIの呼び出しは、一部のAPIを除いてメインスレッドからの呼び出しのみが許可されています。レガシーな同期アプローチは検索すると吐いて捨てるほど出てくるのでそっちを見てください。

スクリーンキャプチャの流れは3工程あり、バージョンアップを経て段階的に非同期可能になっています。

  1. レンダーテクスチャのシステムメモリへの読み戻し
  2. 画像ファイルフォーマットへのエンコード
  3. ファイル書き込み

1は、ScreenCapture.CaptureScreenshotIntoRenderTexture が2019.1で追加され、ようやく非同期でシステムメモリに読み戻せるようになりました。

1で読み戻したメモリはレンダーテクスチャに指定したフォーマットのバイト配列(NativeArray<byte>)ですが、これを一般的な画像ファイルフォーマットにエンコードするには 2017.1から追加されているImageConversionクラスのAPI、ImageConversion.EncodeToPNG など(フォーマット別)を使用します。しかし、これは引数にバイト配列ではなくTexture2D を要求する上に、メインスレッドのみ呼び出し可能なので同期処理を避けられません。
流石に使えないAPIだと思ったのか、2019.3 でバイト配列(byte[])を使用する ImageConversion.EncodeArrayToPNG が追加されました。リファレンスにも”This method is thread safe.”と書かれていて、メインスレッド以外からの呼び出しが許可されています。

3は、.Net APIで以前から非同期可能です。

これらを組み合わせれば、”ほぼ”非同期にスクリーンキャプチャを行えます。

 

今後改善して欲しいところ。

  • ScreenCapture.CaptureScreenshotIntoRenderTexture リファレンスのサンプルコードが間違っている。
    using (var imageBytes = request.GetData<byte>()) で使用されているが、ユーザがこのNativeArrayに対してusingや明示的なDisposeの呼び出しを行うと、例外が出力される(InvalidOperationException: The NativeArray can not be Disposed because it was not allocated with a valid allocator.)。AsyncGPUReadbackRequest が管理しているはず?
  • NativeArray<byte>からbyte[]の変換をメインスレッドで行う必要があるが、そこで巨大なマネージドメモリのアロケートが走る。
    NativeArray<byte>のまま処理するAPI、つまりJob System with Burstでエンコードを行って欲しい。
  • ImageConversion.EncodeArrayToPNG のエンコード済みバイト列として巨大なマネージドメモリのアロケートが走る。
    必要メモリ量の計算と、事前確保したメモリを使用するAPIが欲しい。NativeArrayでの非同期ファイル読み書きをサポートして欲しい。
  • 要するに、最初から最後までNativeArrayのまま処理が完結するようにして欲しい。
  • ImageConversion.EncodeArrayToPNG は出力フォーマットの指定ができない。つまり入力フォーマットと出力フォーマットは同一になる。また、RGBAのデータをRGBしかもたないJPGにすると、rowBytesを指定してもRGBAからRGBへ変換してくれないようだ。
  • 上下が反転している。グラフィクスAPI依存?ImageConversion.EncodeToPNG はTexture2D の段階でそれが打ち消されている?
    ユーザが面倒を見るべき処理ではないので、内部で処理して欲しい。

 

追記:
2019.4で ImageConversion.EncodeNativeArrayToPNG が追加されていました。やったね。

TwitchChatPlugin 1.0 for Yukarinette

TwitchChatPlugin 1.0 on GitHub

TwitchChatPlugin は、ゆかりねっとで音声認識した文章をTwitchの自身のチャンネルに発言することが出来ます。使用にはチャット用OAuthの取得が必要ですが、詳細はREADMEを読んで下さい。

streamingラグを考慮した遅延送信機能と、スパム抑制機能を追加しています。遅延時間は自身の配信設定やTwtichサーバーとのラグを考慮しながらいい感じに調整してみてください。

TwitchChatPlugin 0.1 for Yukarinette

ゆかりねっとプラグインを作りました。

TwitchChatPlugin 0.1 on GitHub

TwitchChatPlugin は、ゆかりねっとで音声認識した文章をTwitchの自身のチャンネルに発言することが出来ます。使用にはチャット用OAuthの取得が必要ですが、詳細はREADMEを読んで下さい。

ニコニコ動画やYoutube Liveでは既に同種のプラグインはあるのに、私が主に利用しているTwitch向けには無かったというので、やってみようかなと思ったら割と楽に出来たやつです。一番大変だったのはWPFでのウインドウ作りだったかもしれない。

とはいえそれよりも面倒臭そうな、streamingラグを考慮した遅延送信とスパムコントロールは後回しです。非同期処理が避けて通れませんが、使用しているTwtichLibに非同期メソッドが容易されていなさそうなのと、適当にスレッドプールに投げて終わりというわけには行かないだろうなという状況があるので。
そういうわけでバージョンは0.1のpre-releaseとなっています。

もう一つ、汚い言葉を使用したときのフィルタリングも機能も本来は必要で、これが無いとTwitch側の機械判断 (AutoMod) に引っかかってペナルティをもらう可能性があります。そういう言葉を使わない善良な人でも音声認識の都合、誤認識で思わぬワードが出ることもあるので注意して下さい。
これはGoogleの音声認識部分である程度はやってくれるのと、Twitch側の処理は日本語に対してはまだそう厳しくないだろう(未対応の可能性すらある)というのと、ゆかりねっとアプリケーション側でやったほうがいい機能だろうかつ、やってくれそうという判断から優先度は低いです。