« Theora の MMX の IDCT その1 | メイン | Theora の MMX その他 »
2007年11月27日
動画再生エンジン開発日誌:: Theora の MMX の IDCT その2
Tweet @jin1016をフォローよくわからないので、ソースをいじりながらどのように結果が変わるか見てみる。
転置をなくしても結果はたいして変わっていないように見える。
IDCTをまったく行わない場合とは明らかに異なるが、似ているようでもある。
よくわからない。
ほとんどのブロックが1色で塗りつぶされていたり、縞々だったりするので、高周波成分がなくなっているような気もするのだが、そのあたりの処理を重点的に見ても間違いは感じられない。
何かケアレスミスのような気がするのだが、見つけられない。
同じような処理を繰り返すのでマクロを使っていたが、その部分を展開してデバッガで途中の結果を見てみることにした。
当然のことながら、そんな値を見ても手計算して比較しないとわからないんだけど、何かつかめるかもしれないと試す。
で、思ったのは妙にマイナスの値が多いと言うこと。
色差ならマイナスもありうるが…… 色差の後に輝度のデコードをしているのだろうか?
IDCT は何度も呼び出されるので、違うプレーンに移るまで調べるのは面倒だ。
ソースをさかのぼればいいんだろうけど、と考えていてマイナス値がどうも引っかかる。
アセンブリのコサインのテーブルは、unsigned shortでキャストしていたな。
コサイン値は、固定小数点で計算するために65536でスケールされている。
で、実際の値を見てみると、64277 や 60547、54491 などが並ぶ。
これって short ならマイナスだな。
それはまずい。
テーブル値は、64bit値にするために 0xFB15FB15FB15FB15LL てな感じにして int64 に入れていた。
これはまずそうだと __m64 に unsigned short の配列で入れておこうと思ったが、__m64 は共用体になっていて最初のメンバがunsigned short の配列ではないので、初期値を unsigned short の配列で入れられない ( __m64 が共用体だとはじめて知った ) 。
仕方ないので、unsigned short の配列にして、使用前に __m64* にキャストして代入してしまうことにした。
これでよしと思ったがダメ。よく考えたら _mm_mulhi_pi16 を使っているので signed として扱われてしまう ( 固定小数点なので下位 16bit を切り捨てるために _mm_mulhi_pi16 を使っている )。
掛け算の unsigned 版はない。
どうすれば…… ってそうか、掛ける値をもう一度足してやればいいんだ。
つまり、cos_val * a + a とする。
こうやってやれば、マイナスでない値と計算結果は同じになる。
で、動かすと綺麗に表示されるようになった。
予想外に苦労したな。
Cの時は 32bit で計算されていたから、マイナス値になっていなかったんだな。
そういえば、アセンブリソースを見ていて、足し算が多い気がするけどなぜだろうと思っていたが、このためだったのか。
命令の並び替えとかいろいろやっていてアセンブリソースは読み辛いからとそちらからソースを起こさなかったのはミスったな。
---- cos_val * a + a で結果が同じになることの説明 ----
直感的に同じになると思ってそのようにしたが、意味がわからない人用の説明を試みてみる。
似たようなことで感覚的にわかりやすいものに角度がある。
270°と -90°は同じになる。
270°は 0 ~ 360 °で表した時の値だが、これを -180°~ 180°で表現しようとしたら -90°になる。
この -90°を 270°に戻すためには、-180°~ 180°の範囲 360 を加算してやる必要がある。
つまり、-90 + 360 = 270 と言うこと。
ようはこれと同じようなことをしてやったというわけ。
コサイン値を固定小数点で計算するために 65536 でスケールするとは、0 ~ 1 の間に収まるようにコサイン値を置き換えることを意味する。
つまり、1 = 65536 ( 17bit ) と言うことになる。
で、マイナス値がどうなるかと言うのを 4ビットだけを使って2の補数であらわすと次のようになる。
−8 = 1000、−7 = 1001、−6 = 1010、−5 = 1011、−4 = 1100、−3 = 1101、−2 = 1110、−1 = 1111、0 = 0000、1 = 0001、2 = 0010、3 = 0011、4 = 0100、5 = 0101、6 = 0110、7 = 0111 ( これは、2の補数 ( Wikipedia ) の一番下の表を見てもらうとわかりやすい ) 。
この例で、もし符号なしであれば、-1 は 15 となる。
ここで説明しようとしているのは、この -1 になってしまった値を 15 に戻したいというもの。
これは、16 ( 5bit ) を足してやればいい。
上の小数部 16bit の固定少数で言えば、65536 ( 17bit ) を足してやるのと同じこと。
1 = 65536 と言う事から、1 を足してやると言っているに等しい。
cos_val * a + a で、cos_val が 0 ~ 1 の間と言う事から、cos_val が 1 の時は 1 * a = a となる。
つまり、1 を足してやるとは、a を足してやることになる。
2の補数は、上の角度の例みたいなものなので、円をイメージしているとわかりやすいかもしれない。
私の頭の中にはそのイメージがある。
トランジスタ技術SPECIAL No.48 作れば解るCPU に近い説明があったはず。
---- 以上説明終わり ----
投稿者 Takenori : 2007年11月27日 16:03
comments powered by Disqus