2016年11月30日水曜日

まいとま1

最近GOG.comでMIGHT AND MAGIC® 6-PACK LIMITED EDITIONを買いました。

いや、なんか動画関連のコードをいじるモチベーションが特に理由もなく急速に衰えたので、別のことをやろうかと思いまして。
そういえば以前にも似たような感じで数年ほどネットから消えてたことがあったような…まあ、しばらくしたらまた何かしらフィルタ書いたりする気になるのではないかと思います。

さて、Might and Magicなんですが、1はX1版、2はPC98版をやったことがあるんですが、もう一度やりたくなったのですね。

スタークラフトは既に倒産して久しいうえに洋ゲーの移植なせいかProject Eggでも復活は無理そうだしどうしたものかと思ったのですが、探してみたらDOS版なら今でも買えることが分かったので(しかも1~6のセットで10USドル…安い)、もう英語でもいいやとすぐに購入しました。

で、今回は1のお話。


Might and Magic Book One - Secret of The Inner Sanctum(以下MM1)は1986年のクリスマスセールの頃に発売された栄光のシリーズ第1作。

作者のJon Van Caneghem(ジョン・ヴァン・キャネガン)はUCLAの学生だった20歳のときにNew World Computing,inc.を立ち上げ、それから3年かけてほぼ一人でこのゲームを作り上げました。
なんでも「やっとできたぜさあ売るぞ」と思ったが引受先が見つからなかったので、Activision社が手を上げるまでの数か月はパブリッシャーも自分でやる羽目になり、NWCのオフィス(兼ヴァン・キャネガンの自宅のアパート)にかかってきた800本余りの電話(注文やユーザーサポート)の応対をしたとか。

最初はApple//版しかなかったこのゲームはその後徐々に売れ出してコモドール64やIBM-PC/AT互換機(MS-DOS)等にも移植されます。
はたまた、まだそんなに売れてない頃にいきなり日本から押しかけてきたスタークラフトの社長と契約したので、1987年末には日本製PC版(以下スタクラ版)も発売されるなんてことも起こりました。
こうしてMM1は世界中に広まり、ヴァン・キャネガンはゲームクリエイターとしての確固たる地位を築いたわけです……が……2016年現在にやってみると、ほんとつらいわこのゲーム。

つらい理由その1:グラフィック
モンスターの絵なんかはヴァン・キャネガンが自分で書いたにしては結構雰囲気出てるなぁとは思うんですが、色数も少なく、なにより画面が暗すぎる。
あと戦闘中はテキストしか表示されないので非常に寂しい。
自分はスタクラ版の見た目が結構好きだったので、余計にそう感じるのかもしれません。


つらい理由その2:経験値稼ぎ
DOS版MM1はスタクラ版に比べると、実はちょっと優しくなっています。

たとえばスタクラ版では最初はどのキャラも棍棒1本持ってるだけの無一文で、ある程度の装備を揃えるまでが非常にきつい(慣れれば実はそれほどでもないんだけど)のですが、DOS版はデフォルトキャラのCRAG THE HACKが200GOLD所持しています。

この違いが生まれた理由は、スタークラフトが移植に当たって参考にしたのは初期のApple//版であるのに対し、DOS版は発売後しばらくしてからヴァン・キャネガンが手を加えたバージョンを移植したものだからでしょう。

"M&M is also one of the most difficult games in which to get started, particularly if you have one of the earlier versions. In those, your party starts out with no money, no armor, and only clubs for weapons. This does not make for a very effective fighting force, especially when monsters show up in large groups. Characters die with appalling frequency under those circumstances.
Fortunately, by the time you read this, the newer version of the game should be out. In that one, the pre-created party that comes on disk will have some money with them, and you can mug 'em to equip your own characters a bit more decently. That, of course, is no guarantee they will survive, but at least their chances will have improved slightly."

(Computer Gaming World 1987年4月号より)

多分、発売後に押し寄せた800件の電話のうちの結構な割合が「ゲーム開始後すぐに全滅する」とかの苦情で、ヴァン・キャネガンもさすがに自分がやり過ぎたことに気づいた(もしくは電話をこれ以上受けるのが嫌になった)のではないかと思われます。

これ以外にもDOS版は若干優しくなっているところがいくつかあります。

例えばコーラック(ソーピガル地下)->アガール(エルキューン)->テルゴラン(ダスク)のスクロール運搬は、スタクラ版(&Apple//初期版)では1セットあたり500GOLD+経験値1人1500ptだったのが、DOS版では1500GOLD+経験値1人3500ptに増えています(ちなみにこれがDOS版におけるもっとも効率の良い稼ぎ)。
まあ、スタクラ版はスタクラ版でバグ(PIRATES MAPが一度にたくさんもらえるとか、木登りクエストはどれか1本登るだけでOKとか)がある分、Apple//初期版よりはラクなんですが。

でもやはりきついのですね。

ハードは80186のTandy2000とかではなくi7上のDOSBoxなので1セット2分程度しかかからないのですが、それでも何百回も繰り返すとなるとさすがに御免被りたい。
でも繰り返さないとなかなかゲームは進まない……だってこのゲーム、DOS版でもモンスターとエンカウントしちゃうと逃げるのはまず無理なんだもの……orz


そしてチートに走る
昔は自分もこの程度では音を上げたりせず嬉々としてやりこんだものですが、さすがに年なのか気力がさっぱりわきません(つーか昔の俺は色々頭がおかしかったのでは)。

MM1といえば有名なセーブデータを2つ用意する所持金&アイテム増殖チートもありますが、あれは昔やったしねぇ……。

で、どーしたもんかと思いつつ試しにセーブデータ(ROSTER.DTA)をバイナリエディタで弄ってみたところ、どうやらこのゲーム、データ改変チェック等は一切行っていないようなので(まあ、スタンドアローンのゲームでそんな手間かけても意味ないとも思いますが)、こんなツールを作りました。

mm1cheat

データ解析しながらあれこれやってたら結局一日潰れましたが、(主にコード書くのが)楽しかったので、これはこれで元は十分に取れた(10USドルの6分の1だから190円くらい?)と思います。

あ、まだ一度もやったことのない人はこんなの使ったらダメですよ。
ファミコン版はバランス調整も結構いい感じになってるらしいので、未経験の方はそちらをやることをお勧めいたします。

2016年9月1日木曜日

MPEG2DecPlus その3

MPEG2DecPlus更新しました。バージョンは0.1.1になります。

修正:
- RFFフラグアリのソースでupConv > 0のとき出力が壊れてたのを修正。
- 不要になったバッファの確保を削除。

JEEBサーバにある最新版バイナリへのリンクを右側に追加しましたので、ほしい方はそちらからDLしてください。
なお、今回のバグは0.0.0からすでにあったようなので、旧バイナリはすべて削除されました。

2016年8月31日水曜日

画像端処理

以前書いたCombMaskの画像端処理についての質問が来ていたので記事にして解説することにしました。

例えば縦方向のみで半径2の平均化フィルタを書くとします。
このとき各サンプルの配置は下図のようになりますが、
ここで問題になるのは画像の上端及び下端をどうするかです。
参照する値が五つ揃わなければ(sa+sb+sc+sd+se)/5は出来ないので、足りない分をどうにかして補う必要があります。

ここで考えられるのは以下のようなものとなります。

パターン1:画像端は処理しない。
面倒なことは避けるという極めて常識的な方法です。
上のような場合は画像の上下各2ラインはそのまま入力値をコピーし、3行目から下端-2行目までの計算を行います。
この方法は半径が小さい場合は特に悪くはないのですが、半径が大きくなるとフィルタがかからない部分も大きくなりますし、重ね掛けをすると未処理のままの画像端の値の影響が大きくなっていく欠点があります。

パターン2:足りないところは0として計算する。
「ないものはないんだから0でいいだろう」という身も蓋もない方法です。
たしかにないものはないのだから、ある意味正しいのかもしれませんが、これはパターン1以上に問題があります。
特に重ね掛けをすると画像端の劣化は激しく、RGBなら真っ黒、YUVなら緑色に染まっていくことになります。
なんでもOpenCVのガウシアンブラーには、まさにこれが起こるのがある(あった?)みたいですね。

パターン3:画像端の色でパディング
存在しないからと言って0として扱うのは問題があり過ぎなため、画像端も処理したい場合にはよくとられる方法です。
存在しないところは境界の色がそのまま続いていると考えるわけですね。
これならばとりあえず全体を処理することが可能ですし、真っ黒になったりもしません。
ただし、半径が大きくなればその分だけ画像端部が参照されることも多くなるため、そこら辺の考慮は必要になります。

パターン4:ミラーリング
パディングの欠点を補うため、存在しない部分は上下(または左右)が逆になった画像がくっついているものとして処理する方法です。
これであれば画像端も処理できますし、上端だけが何度も参照されるということもそこそこ避けることができます。
今回来た質問は
> const uint8_t* sc = srcp;
> const uint8_t* sb = sc + spitch;
> const uint8_t* sa = sb + spitch;
> const uint8_t* sd = sc + spitch;
> const uint8_t* se = sd + spitch;
この部分で、sbとsdは同じものになってしまうのではないでしょうか。
というものでしたが、これも一種のミラーリングを行っているのですね。
CombMaskはインタレ映像のコーミングを見つけるわけですが、画像上端では次のような状態になりますので、saとsbはそれぞれseとsdと同一の点を参照するようにしているのです。

と、ざっと画像端処理について書いてみました。
画像端処理の問題は、多くの空間軸処理において起こります。面倒ではありますがなにかしら対策しないわけにもいかないので、どれにするのかよく考えて行いましょう。

2016年8月30日火曜日

MPEG2DecPlus その2

MPEG2DecPlusを更新しました。

変更内容
・idct=5(IEEE1180 reference)を倍精度浮動小数点処理に戻した(より遅くなった)。
・idct=4として単精度浮動小数点処理のLLMアルゴリズムを実装(SSE2またはAVX2使用)

0.0.0においてはとりあえず64bit化を目標にしていたため、iDCT関連は3のみをintrinsicで書き換え、5は単精度浮動小数点処理として新規で書き直し、他は全部削除という少々寂しい感じでしたが、これで少しはましになったように感じます。

今回追加した単精度LLMは従来のidct=4(64bit floating point)の替わりになるものとして追加したつもりです(コード自体はDCTFilterで書いたものとほぼ同じ)。
品質は単精度のためほんのわずかに落ちますが、それでもほぼ最高品質と言っても問題はないですし、速度は向上しています。
そしてIEEE1180 referenceは単精度にしていたのをやめて倍精度に戻しましたので、出力も従来のものと完全に一致するようになりました。(ただし、一応SIMD化はしていますが、従来のIEEE1180 referenceよりも遅いです)。

ところでDGDecodeのidctはたくさんありますが、どれを使うべきなのかをちゃんとわかっている人って、現在だとどのくらいいるのでしょうか?
かつて、idct=5に丸め込みバグがあったころは「普通は6を除いた中で一番速いもの、とことん品質重視なら4」というのが答えでしたが、5のバグが直ってからは、よくわからないという人が増えてしまったのではないかと思ったのでついでに解説しておきたいと思います。

まず1、2、3の三つは使っているCPUの命令セット以外は同じなので、スピード以外はなにも変わりません。
7(simple MMX)は1、2、3よりも少しだけ質が良いですが、スピードを落としてまで選ぶほどのものかどうかは疑問があります。
6(skal)はスピードは3に並ぶほど速いですがIEEE1180 compliantではない(実用上はともかく、規格的にはダメな実装である)ため、3が使えるCPUでは選ぶ意義はまったくありません。
4と5は品質的にほぼ等価なので、もし品質をとことん重視したいならばより速い4を選ぶべきでしょう。
以上によりSSE2がまともになったmerom(core2duo)以降のCPUであれば、3か4のどちらか好きな方ということになります。

で、品質の違いを確認する方法なんですが、ググってみた限りではなぜか「これだ」と思えるようなわかりやすいものがみあたらなかったのでこれも解説します。
src = "xxxxxxxxx.d2v" #d2vであればなんでもよい
ref = MPEG2Source(src, idct=5) #とりあえず最高品質とされる5
target = MPEG2Source(src, idct=4) #5と比較したいもの
d0 = mt_lutxy(ref, target, "x y - abs 0 > 255 0 ?", chroma="process").SUbtitle("diff>0")
d1 = mt_lutxy(ref, target, "x y - abs 1 > 255 0 ?", chroma="process").SUbtitle("diff>1")
w = BlankClip(d0, width=4, color_yuv=$FFFFFF)
StackVertical(StackHorizontal(ref, w, target), StackHorizontal(d0, w, d1))
これでこんな感じの画像が得られるはずです。今回はffmpegでエンコードした640x480のmpeg2を使いました。
下半分の左側はidct=5と比較して出力が一致するサンプルは0、1でも違う場合は255に変換したもの、右側は出力が2以上違う場合のみ255で他は0に変換したものです。
自分的には出力の違いが1を超えない(右側は全て単色)で、かつ違いの出るピクセルの数が0.01%未満であれば最高品質と言っていいのではないかと思います。

出力1の違いを肉眼で判別できる人間はいませんし(もし出来るという人がいれば、それは実は人間ではないか、頭がおかしいか、ただの嘘つきのどれかです)、なにかしら軽くフィルタを一つかけるか非可逆圧縮でもしようものならば簡単に吹き飛んでしまいますので、違いなんてでようがないのです。

品質とコストの兼ね合いをどうするかは人類普遍のテーマです。
安易に遅い方が質が良いとか考えないよう注意してください。

2016年8月29日月曜日

MPEG2DecPlus

最近、MPEG2DecPlusというプロジェクトを始めました。

MPEG2DecPlus

事の起こりはavs2pipemodのgithubのissue trackerにmaki_rxrz氏からtypoの指摘が来ていたことです。
で、typo自体はすぐ直したのですが(バイナリはリリースしてないけど)、ついでにちょっと覗いてみたmaki氏のDGMPGDecレポジトリ(国内放送TS向け修正版)に興味が出たのですね。

「そういや64bit用のDGDecodeって、けっこう昔にJossyD氏がビルドしたやつしかないんだっけ」とか思いながら試しにビルドしてみたら、32bit用バイナリでも結構大変な目にあいました。
まずコードにいくつかNASM用のアセンブラが入っているため、VisualStudioだけではビルドできない。
さらにMASM用のアセンブラもVC6のころのMASM用に書かれているため、古いMASMを拾ってくる必要があるというものでした(数日後には最新のMASMに合わせたコードに書き換える修正が入りましたが)。

ものがMPEG2デコーダだけにソフトウェア特許絡みで面倒そうなためか、maki氏はバイナリは配布していません。
でも自ビルドするにはいささか敷居が高そうだし(もし5年前の自分だったら15分ほどでほっぽりだしてます)、こりゃ何とかしといたほうがよさそうだなぁとか思ったわけです。

で、やったことは
・アセンブラ(MASM、NASM、インラインASM)を削除(x87FPU用のASMなんて読みたくないし、MMX、MMX2、3DNow!とかもいい加減古すぎ)
・削除したASMの替わりにintrinsic(最低ラインはSSSE3以上を目安)で書き直し
・VFAPI用コードの削除(AviUtlやTMPGEnc2.5はこれなしでもavs読めるし、もういらんでしょ)
・YV16、YV24出力対応(YUY2やRGB24で出力するのはavs2.6以降ではデメリットしかない)
・MPEG2Source以外のフィルタの書き直し
といったところです。

さてここで一番問題になるのがMPEG2Sourceのポストプロセス処理(cpuとかcpu2とか)とBlindPPです。
なにが難しいのかといえば、これはTrbarry氏がメイン処理部分をすべてインラインアセンブラのみで書いていてCのコードを残していないため、なにをやっているのかさっぱりわからないからです。
DctFilterもそうでしたが、あの人のコードはいつも参考になりません。
とりあえず半日ほど格闘してみましたがどうにもCに書き直すだけでも大変そうで埒があかないので、諦めて全部捨てることにしました。

かくして64bitでビルドするための障害はすべて消え去ったので、ついに昨日、0.0.0としてファーストリリースについにこじつけることができました。

で、バイナリですが、JEEB氏が再配布してくれることになったため、右のほうのリンク先から入手できます。

現状、ソフトウェア特許を認めていないEU圏のフィンランド人がドイツだかフランスだかにあるサーバで配布してるので、特許料請求とかも関係なくって安心ですね。

まあ、その特許もあらかた切れてるらしいので日本やアメリカで配っても大丈夫なのかもしれないけど、本当に大丈夫なのか調べるのも面倒ですし…。

2016年8月18日木曜日

DCTFilter その4

DCTFilterを更新しました。ver.0.5.0です。

バイナリ

追加・変更等
・avisynth2.6用のコードのバグを修正。
・avisynth+の追加フォーマットに対応。

avs2.6のみで使われるコードにバグがあったのを修正するついでにavs+の10/12/14bitフォーマットにも対応させました。
もともと内部処理はこれを見越してのfloat演算なので、入力クリップのbit深度とRGBかYUVかを判定するコードを追加するだけでした。


さて、このフィルタはMT_NICE_FILTERとして使えるように書いたつもりです。
そしてユーザー側で特にMT modeを設定しない限りはMT_NICE_FILTERとして設定されるようにもしています。
ところがDeblock_QEDにこれを使ってマルチスレッドで実行すると映像が壊れることがあるという報告を一件受けました。
で、試しに手元のサンプル映像に掛けてみたのですが、特にそんな様子もありません。

そもそもMT_NICE_FILTERというのは、avisynth+がマルチスレッドで動かそうとする際に特に障害となるような要素を持っていないだけの、ただのシングルスレッドで動くフィルタのことです。
マルチスレッドで動かしているのはavisynth+のほうなんですから、バグるなら報告先が違うような気がするんですが…?

あと、不具合が出るソースのサンプルもスクリプトも実行環境の説明も何もなしにただバグると言われても正直困ります。
再現可能な情報の提供がなければ動きようがないのです。
逆に再現率が高く再現方法も手軽なものであれば、そのバグは大抵すぐに直ります

というわけで、バグを見つけられた方はなるべく再現方法も教えてください。お願いします。

追記:報告者によると、これのほかにavs+やらいろいろ更新したら再現しなくなったそうです…まあ、そんなところだろうとは思ってたけどね。

2016年8月17日水曜日

最近のAvisynth+を使う場合の色々について

前回の記事についてたコメントあたりから察するに、どうもAvisynth+の基本的な使い方を書いておいた方がいいように感じたので書いておくことにします。
なお、この記事の内容はあくまでも現時点(r2161)でのものですから、今後どのように変わるか(あるいは変わらないか)はわかりません。

SetLogParams
SetLogParamsはr2064で追加されたログ機能です。
avisynth+を実行中に発生した色々な情報を出力します。
SetLogParams(string file, int log_level)
fileはログの出力先の指定で、"stderr"だと標準エラー出力、"stdout"だと標準出力、 "D:\foo\bar\avslog.txt"のようにファイル名にすればファイルに出力されます。
log_levelは出力するログの範囲の指定で、LOG_DEBUGなら詳細なすべてのログ、LOG_INFOならば普通のログ、LOG_WARNINGならば警告及びエラーのみ、LOG_ERRORならばエラーのみを出力します。

この機能によりAvisynth+本体及びプラグインによって発生した疑わしい挙動や不具合が大変わかりやすくなり、バグレポートしやすくなりました。
今後コマンドラインのツールを使う場合はSetLogParams("stderr", LOG_INFO)、AviUtlやVirtualDubのようなGUIツールと併用する時はSetLogParams("出力ファイル名", LOG_INFO)をスクリプトの先頭になるべくつけるようにすることをお薦めします。

SetMemoryMax
こちらはavisynth2.5の頃からのお馴染みですが、どれくらいに設定しているでしょうか?
SetMemoryMax(int mem)
avisynth+は規定値として32bitの場合(より正確には物理メモリより仮想メモリのほうが少ない場合、つまり64bitOS上で32bit版を使う場合)は128MB、64bitの場合(というか物理メモリより仮想メモリのほうが多い場合)は1GBをavisynth本体が使うメモリ量の上限として物理メモリを確保しようとします。
よってこれ以上を使うことが予想される場合、もしくはもっと少なくてもよい場合を除けば、これはスクリプトに書く必要はありません。

また重要なのは、このメモリ上限はあくまでもavisynth+本体が使用するメモリ量であり、プラグインフィルタが使うメモリ量ではないということです。
もしあるプラグインが中間バッファの確保にmallocやstd::vectorを使っても、その分はカウントされません。
Avisynth+には新たにバッファプール機能が追加されたため、この機能を使えばプラグインのメモリ使用量もavisynth+本体で管理することが可能になりましたが、実際に使っているプラグイン作者は現時点では私くらいしかいません(特に問題は見当たらないのですが、一応実験的な機能ってことになってますし)。
よって下手に大きな上限値を設定すると、プラグインのほうでメモリ不足を起こす可能性が出てきます。

で、あるスクリプトの実行時にどれくらいメモリを必要とするかは、使用するフィルタ及びソースの解像度等に依存するわけですが、もし設定された上限値では足りない、若しくはもう少しあったほうがより円滑に処理出来る場合は、先ほどのSetLogParamsによるログで教えてくれるようになりました。

というわけで、決め打ちに走るのもまあ悪くはないかもしれませんが、適切な量を調べてみるのもいいのではないかと思います。
とくにAviUtlと併用する場合はアプリケーション側も32bitなのにメモリ食いまくりますんで、絞れるところは絞るべきでしょう。

SetFilterMTMode
Avisynth+の目玉機能の一つはフレームレベルでのマルチスレッドですが、皆さん使っていますでしょうか?
私は(ここ数年はエンコはせずにコード書くだけの人なので)あまり使ってません。

とりあえずこのフィルタはMT_NICE_FILTERだ、これはMT_MALTI_INSTANCEじゃないとダメだ、とあれこれやるわけですが、ここで重要なのは"DEFAULT_MT_MODE"をどれに設定するかです。

現時点ではDEFAULT_MT_MODEは3(MT_SERIALIZED)以外ありえません。

"DEFAULT_MT_MODE"は、MT modeが設定されていないすべてのフィルタに影響するため、下手に2(MT_MALTI_INSTANCE)などにしようものなら安全な実行はまったく保証されなくなります。

そもそもいままで書かれてきたavisynthプラグインのほとんどはシングルスレッドでの実行を前提としているものが多く、また処理内容的にもマルチスレッド化はスライスレベルでしか実現不可能なものもあります。
このページには"mode 3 is evil."なんて書かれていますが、時間軸IIRフィルタを邪悪呼ばわりするとか、正直頭がおかしいんじゃないかと思います。単にちょっと前までのavisynth+MTにとって都合が悪かったものをそうやってくさしてるだけです。

本当に邪悪なのはmode 2です。
mode 2で動かせるフィルタは、すべてmode 1で動かせるように改変可能なフィルタです。
でも実際にこれまで書かれてきたフィルタを全部改造していくのはちょっと無理なんで、フィルタクラスを複数インスタンス化しそれらを同時に動かすという力技でごまかしているのがmode 2の実態です。
ちなみにmode 2というのは、avisynthMTのSetMTMode(2)と基本的に同じものです。Avisynth+ではキャッシュ機能も改良されたのでメモリ使用量も減って、かなり安定度も上がりました(が、それでも安全とは言いきれない)。

前回記事のコメントで私がなかなかScriptClipの不具合に気づかなかったのは、SetFilterMTMode("DEFAULT_MT_MODE", MT_SERIALIZED)と書かれたavsをオートローディングフォルダに突っ込んでるのを忘れてたからなんですね。


と、だらだらと書いてみました。
もし参考になった方がいましたら幸いです。