C#は電気羊の夢を見るか?(引っ越し中)

dtiブログが閉鎖とのことでhttp://tanocs.blog.fc2.com/へ引っ越します。

FXAAをXNA4.0で使う

シェーダーコードをボチボチ最適化して、日本語コメントをつけたので公開。
HLSLのSM2.0に対応しているので、他にも使い道があるように思う。

FXAA.fx


使い方は


後はスプライトバッチで全画面描画してくりゃれ。
このエントリーをはてなブックマークに追加
LINEで送る

シェーダー高速化の肝

今まではXNAのサンプルそのままに
ガウシアンブラーで右方向と下方向をサンプリングしていたのだが、
これを右下、左上に変更するだけで、なぜか高速化されたので覚書き。

TexCoordのサンプラオフセット値の指定を
texcoord + float2(x,0);
texcoord + float2(0,y);
から
texcoord + float2(x,y);
texcoord - float2(x,y);
と変えるだけで780->920fpsという、となかなか大き変化があった。

時間に直せば0.0002秒の差だが、
低速なマシンではボチボチ効果がある差なのではないかと思う。

一体何がどうしてこの速度の差に現れたのか、すごく気になる。
そこで、あれこれ試してみたところ、
少しコードの書き方を変えるだけで30fpsくらいは、ぴょこぴょこと簡単に変化が出る。

シェーダにおいてはコンパイラさんが気に入るコードを書くことが、
高速化の肝なのだなぁと身を持って感じた。
このエントリーをはてなブックマークに追加
LINEで送る

とうとう来た

待ちに待ったKinect for Windows SDK Betaがようやく登場。
StillDesign.PhysX.NetもPhysx3.0の対応がボチボチと進んでいるようで、
この二つで何かおもちゃ作りたいなぁ・・


しかしながら、KinectにはWindows7しばりがあって、しかも両方共XNAは4.0のみ・・・
C#のAndroidアプリ開発環境MonodroidもVS2010のプラグインだったし、
WinXP+VS2008はもう潮時をとうに超えたんだなぁという実感が湧いてきた。


Win7マシンはもう既にあるから、あとはVS2010か・・・
バージョンアップかぁ・・・高いんだよなぁ(´・ω・)
このエントリーをはてなブックマークに追加
LINEで送る

事の顛末

今回騒ぎに騒いだRadeon問題にようやく決着。

まず結論から言うと、シェーダーのバージョンを変えるとだめになる問題は
Spritebatchで使うエフェクトのコンパイラの指定が、
ピクセルシェーダーにしか適応できないことに起因しているように思う。

というのもコンパイラで3.0のシェーダーを利用するためには
ピクセル、バーテックスシェーダーの両方のバージョンを3.0に揃える必要がある。

これに違反するため、Radeonでは素直にバグとして発症し、
Geforceではドライバにより補正されたという事のようだ。


あと、ポリゴンが伸びる問題があるんだけど、
これはソフトとかいう以前の問題だったっぽくて、
公式のテクスチャスキニングのサンプルでも同様の結果を得た。
(これはちゃんと最新版のXNA4.0で検証した結果)

そういえばFFをやってる時も時折チラツキがあったが、これが原因だったように思う。



SSでは、かかとやつま先が少々伸びている程度だが、
ひどいときは画面いっぱいにポリゴンが伸びることもある。

○飽くまで傾向として
・何故か伸びるのは足元が多い(腕が伸びることもあるがまれにしかない)
・伸びるアニメーションがだいたい一緒
・だいたい2,3ループに一回発症する
・一回伸びると何フレームか連鎖してダメな事が稀にある


たぶん、他のPCゲームやってる所とかでドライバ設定のノウハウありそうなので、
解決法はそっちを当たってみる事にする。

とりあえず、ソフト的にはバグではないと考えてよさそうなので、
この件はこれにてクローズとしたい。


=6/16追記=
ドライバの設定についてはこの現象の解消方法として
BIOSでAdvancedタブ→JumperFree Configurationを選択
PCIE Spread SpectrumをAuto→Disbledに変更といったノウハウがあったが、
自分のマザーボードではこの設定ができなんだ(´・ω・)

さて、どうしたものか・・・
このエントリーをはてなブックマークに追加
LINEで送る

誰か教えてOTL

大ハマリしていたRadeon問題にまた新たな展開が。

自分の理解ではHLSLは完全な下位互換があるのだと思っていた。

だから一つのプログラム内のHLSLによって
バージョンががたがたなのは望ましくないものだと考えた私は
コンパイラの指定を全て3.0に書き換えたのだ。


これを
PixelShader = compile ps_2_0 PixelShader();
こんな感じ
PixelShader = compile ps_3_0 PixelShader();


たったこれだけで、Radeonで描画に失敗するようになる。
つまりXNAでは画面が紫色のままになる。

HLSL内部を詳細にステップ実行したわけではないが、
どうも描画済みレンダーターゲットのテクスチャを取得できず、
その後の処理に失敗してるっぽい。


ハード依存なので人によっては再現しないかもしれないが
公式のブルームサンプルでもシェーダーのバージョンを
全て3.0に変えたところ同様の問題が確認できた。

ちなみに2.0指定では問題なく動作する。


コンパイラが別物になるから、吐き出されるバイナリが変わるなんてのは
頭じゃ理解できるが、動作しなくなる仕組みが解らない・・

なんのなさコレ。
ダレか内部仕様に詳しい偉い人、教えて・・・OTL
このエントリーをはてなブックマークに追加
LINEで送る

土下座もの

前回の問題解決法でサラリと
「対処法は公式のブルームサンプルにあるDrawFullScreenQuadで代用が効くので、
こちらに移行することを強くオススメする。」
等とのたもうたが、よくよくみると、海外の別のサンプルでした。

そんな訳で、その海外のサンプルも良く良く見るとイマイチだったので書きなおした。
お詫び的な意味になるかどうか分からないけど、それを公開したいとおもいます。


公式のブルームサンプルみて「これもスプライトバッチじゃねーかボケ!」と思った方は
こちらをご使用ください。

すんませんでした _○/|_

例によってXNA3.1です。悪しからず。

using System;

using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class FullScreenQuad
{
//vertexBuffer.SetData用の構造体
private struct QuadVertex
{
public Vector3 position;
public Vector3 texCoordAndCornerIndex;
}

GraphicsDevice GraphicsDevice;

VertexBuffer vertexBuffer;
VertexDeclaration vertexDeclaration;

public FullScreenQuad(GraphicsDevice graphicsDevice) {
this.GraphicsDevice = graphicsDevice;
CreateVertexDeclaration();
CreateFullScreenQuad();
}

private void CreateVertexDeclaration() {
VertexElement[] declElements = new VertexElement[2];

declElements[0].Offset = 0;
declElements[0].Stream = 0;
declElements[0].UsageIndex = 0;
declElements[0].VertexElementFormat = VertexElementFormat.Vector3;
declElements[0].VertexElementMethod = VertexElementMethod.Default;
declElements[0].VertexElementUsage = VertexElementUsage.Position;

declElements[1].Offset = sizeof(float) * 3;
declElements[1].Stream = 0;
declElements[1].UsageIndex = 0;
declElements[1].VertexElementFormat = VertexElementFormat.Vector3;
declElements[1].VertexElementMethod = VertexElementMethod.Default;
declElements[1].VertexElementUsage = VertexElementUsage.TextureCoordinate;

vertexDeclaration = new VertexDeclaration(this.GraphicsDevice, declElements);
}

private void CreateFullScreenQuad() {
vertexBuffer = new VertexBuffer(this.GraphicsDevice, typeof(QuadVertex), vertexDeclaration.GetVertexStrideSize(0) * 4, BufferUsage.None);
QuadVertex[] vbData = new QuadVertex[4];

// 右上
vbData[0].position = new Vector3(1, 1, 1);
vbData[0].texCoordAndCornerIndex = new Vector3(1, 0, 1);

// 右下
vbData[1].position = new Vector3(1, -1, 1);
vbData[1].texCoordAndCornerIndex = new Vector3(1, 1, 2);

// 左上
vbData[2].position = new Vector3(-1, 1, 1);
vbData[2].texCoordAndCornerIndex = new Vector3(0, 0, 0);

// 左下
vbData[3].position = new Vector3(-1, -1, 1);
vbData[3].texCoordAndCornerIndex = new Vector3(0, 1, 3);


vertexBuffer.SetData(vbData);
}

public void Draw(Texture2D texture) {
GraphicsDevice.VertexDeclaration = vertexDeclaration;
GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, vertexDeclaration.GetVertexStrideSize(0));

GraphicsDevice.Textures[0] = texture;
GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);

GraphicsDevice.VertexDeclaration = null;
GraphicsDevice.Vertices[0].SetSource(null, 0, 0);
}

}
このエントリーをはてなブックマークに追加
LINEで送る

Radeonの問題点

非常に悔しい思いをした。

出張先で携帯のバッテリーが切れたとき、
コンビニで充電器を買わねばならぬあの悔しさと同じ感覚だ。

そんなわけで、とうとうRadeonをデバッグ環境として購入しました。
”そんなわけ”というのは、公開したゲームが一部のRadeonで動かないんじゃボケというご報告が多かったから

HD4850とかもう型落ちも良い所の不良在庫品を
8000円弱の値を払って買うハメになるとは・・トホホ。

生まれてはじめて買ったRadeonが、まさかこんな形になろうとは思いよらなんだ。


閑話休題


Windows向けにXNAのゲームを公開している人が
一体どれくらいこの世の中に存在するのか知らないが、
私と同じようにRadeonを買わされる人が居なくなるように、
今回手に入れたノウハウを、非常に稀有な人にむけて発信する。


SpriteBatchの挙動
これはもう、どこに文句を言ったらいいか解らないので愚痴っておく。

SpriteBatchでエフェクトを使う、みたいな解説を見ると
大抵レジストリ0番に値があるのを期待してエフェクトを書くような解説になっているのだが、
残念ながらこれが超危険な行為であることがわかった。

RadeonのHD4xxx系とHD6xxx系は、どうやらこれをやると正常に動作しない模様。
原因や、挙動を予測しようとすると、夜に寝られなくなるくらい不思議な現象だが、
事実そうなるんだから仕方ない。


対処法は公式のブルームサンプルにある
DrawFullScreenQuadで代用が効くので、こちらに移行することを強くオススメする。


▲謄スチャの寿命
たしかXNA4.0ではこの心配は不要になったはずなので、XNA3.1で開発してる人向け。

texture2d.SetData時にテクスチャの寿命を明示的に指定してやらないと
Radeonはこれを即効でぶち壊す。

特にTexSkinningSampleのテクスチャアニメーション使ってる人は要注意。


こういった挙動を見て思うんだが、Radeonってのは一体どんな設計になってるんだろうか(;;
多くのゲームがRadeonに不具合を抱えている原因も、こういった変態的な動作の性だともう。
このエントリーをはてなブックマークに追加
LINEで送る

シェーダについて

先日ピクセルシェーダでは遅いというような記事を書いたが、
もう少し掘り下げて調査をしてみると、どうやらその考え方はかなり古いようです。

現在のGPUは統合シェーダという設計思想に基づいてハードが設計されており、
〜シェーダ用をN基とかそういう実装では無いそうで・・・


プログラマ側からしてみると、バーテックスシェーダやピクセルシェーダと言う区切りで各処理を実装していくと思う。

しかし、そういう処理のステップでその処理を行うというだけで、
物理的に処理を行うチップがどうこうという訳ではなくなっていたというわけ。
(※とりわけ最新事情というわけではなく、この設計思想は5年以上昔の話です)


結局、長い間DirectX9がソフトのシェアを握りっぱなしになっていて、
そのへんの情報に鈍感になっていたのだなぁと思う。

しかしながら、XNAはまだDirectX9系のままな訳で、
ジオメトリシェーダは使用できないままというのも現状なんだよなぁ
・・・

実際のところ、OSレベルでの切り替えが進まないとDirectX10以降でゲームを開発しても
結局それが販売に繋がらなくなってしまう。

じゃぁ、一体いつになればジオメトリシェーダを大手を振って使えるようになるかと言えば、
それはまだ3年以上先の話なんじゃないだろうか・・・


ピクセルシェーダでの実装に話を戻すと、統合シェーダという設計思想や、ピクセルシェーダ後期の状態を見ればあながち間違いではないような気がする。(と言うよりむしろ、そちらのほうが主流のように思う)


しかし、データの転送にテクスチャを用意しなければならないし、
その使用制限もきつい、さらに言えば処理済みの結果を使うチャンスが残されていない。

何よりループ処理に処理を食われてしまう問題から逃げるのに労力を取られる気がする・・


しかしながら、ピクセルシェーダのほうが負荷数が非常に大きいことから、並列化、高速化、最適化はピクセルシェーダのほうが進んでいるように書かれている文献も多く見られた。

これは統合シェーダとしてみた時にも通用するアーキテクチャなのか不明なんだけど、
その流れがあったことを考えれば、最適化はされているような気がする。


個人的にはプログラムのしやすいバーテックスシェーダ優秀説を唱えたいのだが、
実際のところはどうなんじゃろうか・・
このエントリーをはてなブックマークに追加
LINEで送る

GPGPUの現状

結構とXNAでGPGPUをやりたい方が多いようで、
[XNA GPGPU]をキーにgoogleからの来訪者が多い。

そこで、まぁいいかと放ったらかしにしていた現状について話さざるをえない・・・。

というのも、じつは公開中のGPGPUのソースははっきり言って全然使いものにならない。
なぜならピクセルシェーダーを使ってるから。

ピクセルシェーダーはほとんど処理が並列化されないので、
GPGPUの利点を生かしきれない。と思う。
GPGPUとしてピクセルシェーダーは遅いと思っていた要因が別にある可能性があり、
厳密な計測や比較が出来ていないから断定はできない。

ただ、応用の基礎として読み取るには調度良いわかりやすさがあるので、
そのままにしているのだが、実際はそのままでは実用には耐えないと思われる。


GPGPUとして本格的に使用したい場合はバーテクスシェーダーを用いる必要がある。
バーテクスシェーダーを使うにはいくつかの”コツ”がいる。

そのコツは以下の通り
・VertexElementを自前で定義する
 色情報や法線情報として自分が計算したいと思っている大量のデータを
 バーテクスシェーダーに渡すようにする。
 その際、位置情報は出力するレンダーターゲットの座標を表すようにする。

 ちなみにその座標は左上が(-1,1,0),右下が(1,-1,0)として指定する。
 取り得る値の範囲が-1〜1で、何故かXとYが反転する点に注意が必要

例:1024 * 1024のレンダーターゲットの場合
   (1024, 1024) → (1, -1, 0)
   (512, 256) → (0, 0.5, 0)
   (256, 0) → (-0.5, 1, 0)
   

・頂点を点として描画する
 graphics.RenderState.FillMode = FillMode.Point;
 graphics.RenderState.PointSize = 1;
 上記のようにレンダーステートを点描画モードにしてやる。

 通常のGPGPUでは必要ないと思うけど、
 計算結果を3Dモデルで表示したいとかで、通常の描画に戻したいときは
 graphics.RenderState.FillMode = FillMode.Solid;
 とすればいい


具体的なソースの提示はいつかしたいと思っているが、
いかんせん、高速化の名目で現在作成中のプログラムに密結合しているので
引き剥がしてサンプル化するのに手間がかかってしまうのでとりあえずで勘弁して下さい。

このエントリーをはてなブックマークに追加
LINEで送る

GPGPUの高速化

今回の実装で高速化が程度できたっぽいので効果のあった方法をメモ


まず、以前より改善を続けていたtexture2d.getdata()は
もってくるテクスチャサイズは小さければ小さいほどよい。
少々計算が複雑になってもサイズを小さくするほうが効果がある。


そして意外と盲点だったのは、メモリの管理。
ループ内で呼び出している外部関数で巨大なメモリを使用している場合、
引数にrefキーワードを用いるだけで劇的な改善が見られた。

これに気を良くして、そんなに効果があるならと
newキーワードの対象となる配列の宣言を極力ループの外に放り出すと
それだけでかなり速度が改善された。


例:
for(int i = 0; i < src.length; i++){

int[] hoge = new src.getData();
}
上記ソースを
int[] hoge;

for(int i = 0; i < src.length; i++){
hoge = new src.getData();
}
とするだけ。

このsrc.getData()のサイズが数メガに及ぶような場合、メモリや可読性が許せば
高速化のためにグローバル化(メンバ化)してしまうのも良いかもしれない。


本来ローカル変数はスコープが外れると無効にはなるものの、
ガベージコレクタさんが動くまでは、事実上はメモリを使用してる。

いつ動くかよくわからないガベージコレクタさん。
気がつくと多くの未使用メモリが増えることが多いのではないだろうか。
そしてガベージコレクタさんのお仕事の量が大きくなると処理もそれなりに重くなるっぽい。

特に描画処理は最大で秒間に60回近く呼ばれている計算になり、
そいつをforで2重ループなんかしている日にゃ、なかなかときつい仕事になるわけだ。

上記の例のように露骨にnewされている場合は気が付きやすいけど、
関数の引数もメモリを食っているので注意が必要。


あくまで一例だが、私の場合、5万近くの要素数になる配列の宣言を
メンバ化して計算用関数の引数をref化した結果15fps→60fpsと劇的な改善が見られた。

ただ、ゲームクラスのメンバにモデルの頂点データがいるのは実に気持ちが悪い。
計算用のグローバルだと思おう・・・


ここの記事によるとtexture2dをフリップ化することで
GPUとCPUで並列処理が可能になるとのことで、
もう少し処理の高速化が必要になったら試してみたい。

実は今回結局MRTで2回テクスチャの取得処理を発行しているが、
テクスチャフリップの際は2パスへの変更が必要だろうなと思う。
このエントリーをはてなブックマークに追加
LINEで送る

結果。

モデルインスタンシングを試した結果・・・ダメでした。

勢いよく実装して、さぁHLSLを実装という段階でようやく問題点に気がつき
結局実行することもありませんでした。

というか物理的に実装不可能な方法で実装しようとしていたので、
「あ・・」という絶句の後、実装を断念しました。



そして、今朝・・・・
HDDがお亡くなりになったぽい・・・・・ショック(つд ;`)


自分ルールであったはずの2年縛りを無視して
なぁなぁでその倍の期間利用したのだから、当然の報いか・・・
しかし、高校時代からのデータのほとんどが消えた計算になる訳で・・・

まぁ、死に方がクシャミじゃなくてROM死にっぽいので、
いつか業者に出すのもいいかもしれない。
このエントリーをはてなブックマークに追加
LINEで送る

この手があった

前回の続き。

どうにも2パスで書くのが納得行かなかったので、
なんとかいい方法はないかと頭をひねっていたのだが、
ふと同じモデルを大量に描画する場合の高速化手法で
モデルインスタンシングってのがあったのを思い出した。

たぶん、これなら2パスじゃなくても行けそう。
DRAWの発行は一回、描画も一回、メモリ転送は一モデル分+α程度なのでこっちのが速いんじゃないかと思いたい。

比較する余裕が無いので、2パスとどっちのが早いかは他の人に任せて、
メンテナンス性を優先して自分はこっちで実装する。
このエントリーをはてなブックマークに追加
LINEで送る

まだ遅い

texture2d.getdataを2重ループから放り出してもなお遅かった・・

「くそっ、どこかに答えがあるはずだ!」とグーグル先生に聞いてみると
自分のサイトがヒットしたりして少し凹んだ。


んでよく良く調べてみると、やはり矩形のサイズによっても取得速度に変化がある事が分かった。
そこで、現在テクスチャの縦幅を1パーツ32固定で作成していたのを可変として
取得処理の絶対的な負荷を下げてみるつもり。

また取得処理そのものが遅いので、現在2つのレンダリングターゲットに、
それぞれ、ポジション、ノーマルの描画をし、texture2d.getdataを二回発行しているのを
一つにまとめる方法を試してみたいともう。

ただ、これってMRTなら一度の描画で済むのを2パスで2回書くことになるので、
そのあたりのうまく高速化できるかどうかを見極めたいところ。


しかしながら、本当にGPUの処理結果をもう一度CPUで処理しようというのは、
どうしようもなく低速になってしまうんだなぁとつくづく思った。

そういう意味では、悪者はtexture2d.getdataではなくアーキテクチャなのだなぁと。

いやしかし、それはよくよく考えれば設計者の選択の結果であって、
本当に問題なのはそれを生かせぬ己か。
このエントリーをはてなブックマークに追加
LINEで送る

XNAでのGPGPU処理時の注意点

念願のGPGPUをどうにか完成させ、
「これでありあまっていたGPUパワーをフルに使えるぜ!」と喜んだのもつかの間、
ほとんどの処理をGPUに回してコーディングを行ったはずなのにCPUの負荷が下がらない
・・・はて?


当初、メモリへの型変換の処理が遅いのかと思って調べを進めていたが、どうも違う。
原因はたったひとつの関数で

RenderTarget2d.GetTexture().GetData()

こいつがとにかく遅い。
その遅さたるや、代車で借りた1リッターマーチが、東海北陸道の上り坂で、
アクセルべた踏みしても80km出なかった、あの衝撃に近いものがある。

これを呼び出す回数はとにかく抑える必要が有る。
2重ループの中に放り込む等というのは絶対にしてはならない。


これの対策法としては、処理の遅さがテクスチャサイズにほとんど比例しないので、
出来る限り一枚のレンダーターゲットに情報をまとめ、
GPUで行った処理結果の取得回数を減らす必要が有る。

当初は汎用的に作っていたが、
GPUで処理する際にはテクスチャと言う名のメモリを管理する為に
パラメータを与える必要がありそう・・
このエントリーをはてなブックマークに追加
LINEで送る

変換とか

SM3.0で使える最大テクスチャサイズは本当は2048pxまでで、
それ以上はハードによってはオプション的に対応している。
これはMRTについても同様のことが言える。

よってSM3.0に対応しているかどうかだけのチェックではハードの対応状態の判断がつかない。


あとCOLOR0、COLOR1とかセマンティクスをつけた場合COLOR0を未使用のままCOLOR1が使用できないとのこと。

バーテックスエレメントの指定とセマンティクスのサイズ差による型変換は公式に書かれていた。(URL忘れた・・)


Vector3.TransformNormalをHLSLで実装したい人がいるか怪しいが、
結構苦戦したのでメモ


struct VS_INPUT
{
float4 Normal : NORMAL0;
}

struct VS_OUTPUT
{
float4 ColNormal : COLOR1;
}

VS_OUTPUT output = (VS_OUTPUT)0;
float4x4 skinTransform = CreateTransformFromBoneTexture(input.BoneIndices, input.BoneWeights);
output.ColNormal = float4(normalize(mul(input.Normal.xyz, skinTransform).xyz), 1.0f); //TranceformNormal


出力先がfloat3の場合 float4( と ,1.0f) を消してやれば良い。
"ミソ"はノーマライズした結果の.xyzを利用すること。
このエントリーをはてなブックマークに追加
LINEで送る

セマンティクスの罠

セマンティクスのポジションを指定するときXNA側でVector3を指定したとき、
HLSL側での指定をfloat3とかやってしまうと、座標系がどこぞへとぶっ飛んでしまう模様。
この事実に気がつかず、モデルが一切表示されない状態に一週間陥っていた・・

分かってしまえばなんてことはないんだけど、
分からないとHLSLはどうにもハマることが多くていけない。

こう、もっとサラリと使えるデバッガがあればいいのに・・


ちなみに今回問題となったVertexElementは下記の通り

VertexElement[] {
new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
new VertexElement(0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0),
new VertexElement(0, sizeof(float) * (3 + 3), VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Color, 0),
new VertexElement(0, sizeof(float) * (3 + 3 + 3), VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.BlendIndices, 0),
new VertexElement(0, sizeof(float) * (3 + 3 + 3 + 4), VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.BlendWeight, 0),
new VertexElement(0, sizeof(float) * (3 + 3 + 3 + 4 + 4), VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0)
}


んで、HLSLのポジションのセマンティクスが
float3 Position : POSITION0;
って感じだとダメで
float4 Position : POSITION0;
とする必要がある。


普段気にせずにいたけど、勝手に型の変換とかしてくれてるんだと思う。
やれやれ。
このエントリーをはてなブックマークに追加
LINEで送る

お蔵入り

よし!forを自作してやる!と無茶苦茶なことを考え、一つの関数を作ってみた。

自然数N
希望するループ回数X

i= N - X + 0.4f; //0Divを避けるために小数を下駄として使用 0.4なのは浮動小数点での扱いが有利だから。
i = (i * abs(i) + pow(i, 2)) / (pow(i, 2) * 2); //0〜Nの時1を出力し、負の値を入力すると0が出力される関数
result += [iを利用した希望する式] * i;

あとはXを気合で手打ちするだけ。

i = InputWidth - 31.6f;
i = (i * abs(i) + pow(i, 2)) / (pow(i, 2) * 2); // 1 or 0
result += tex2D(SceneSampler, texCoord + float2((InputWidth - 32.0f) / InputWidth, 0)) * i;

i = InputWidth - 30.6f;
i = (i * abs(i) + pow(i, 2)) / (pow(i, 2) * 2); // 1 or 0
result += tex2D(SceneSampler, texCoord + float2((InputWidth - 31.0f) / InputWidth, 0)) * i;

i = InputWidth - 29.6f;
i = (i * abs(i) + pow(i, 2)) / (pow(i, 2) * 2); // 1 or 0
result += tex2D(SceneSampler, texCoord + float2((InputWidth - 30.0f) / InputWidth, 0)) * i * 100;

    ・
    ・
    ・

i = InputWidth - 1.6f;
i = (i * abs(i) + pow(i, 2)) / (pow(i, 2) * 2); // 1 or 0
result += tex2D(SceneSampler, texCoord + float2((InputWidth - 2.0f) / InputWidth, 0)) * i * 100;

i = InputWidth - 0.6f;
i = (i * abs(i) + pow(i, 2)) / (pow(i, 2) * 2); // 1 or 0
result += tex2D(SceneSampler, texCoord + float2((InputWidth - 1.0f) / InputWidth, 0)) * i * 100;


ちなみに、サンプルは前回からやってるあれだったりするのだが、
見事にHLSLのなんかの上限にぶち当たったため断念。

これで128*128がやりたかったんだけど、どうやら駄目だった模様。
なんかの一次元ループにゃ使い道が・・・ないか(´・ω・)


動かなかったソースは続きに一応置いときます。
このエントリーをはてなブックマークに追加
LINEで送る

続きを読む »

GPUを使って計算した結果をCPUに戻す[ちょっと便利で速い版]

二日前の記事のHLSLのみ書き換えた版

これで前回255回が上限だったループが4095回まで拡張できた。
ただ、もしかするとグラボによっては動かない可能性もあるので注意が必要。


このソースの検証時にようやく気がついたのだが、
GPUさんは浮動小数点の誤差に結構弱いことを思い出した。
0.1fを512個とか加算してくと顕著に現れてくる。

この辺はゲームだったら、
このぐらいの誤差はそれほど気にならないから的な理由だったと思う。

利用目的次第だが、誤差が気になる場合は計算する前の値に*10とか*100とかしてやり
0.1fとか苦手な小数を1.0fなどの整数にかえて計算してやってから/10してやると誤差はなくなる。

*10とか*100とかの下駄のはかせ具合は利用する際の目的や予想される数値次第なので、
使う人それぞれでご対応くだされ。


■HLSL

float InputWidth;
float4 result;

texture SceneTexture;
sampler SceneSampler : register(s0) = sampler_state
{
Texture = (SceneTexture);
};

float4 PixelShaderFunction(float2 texCoord : TEXCOORD0) : COLOR0
{
InputWidth %= 4096;
int i;
int j;
int Loop1 = InputWidth / 64;
Loop1 %= 64;
result = 0;

for (j = 0; j < Loop1; j++) {
for (i = 0; i < 64; i++) {
result += tex2D(SceneSampler, texCoord + float2(1.0f / InputWidth * (j * 64 + i), 0)) * 100.0f;
}
}

for (j = 0; j < InputWidth % 64; j++){
result += tex2D(SceneSampler, texCoord + float2(1.0f / InputWidth * (Loop1 * 64 + j), 0)) * 100.0f;
}

return result / 100.0f;
}

technique CulculateTechnique
{
pass Pass1
{
PixelShader = compile ps_3_0 PixelShaderFunction();
}
}
このエントリーをはてなブックマークに追加
LINEで送る

GPUを使って計算した結果をCPUに戻す[便利で遅い版]

とりあえずで完成した物を配置


これなら簡単にforが上限なしで使える!しかもPS2.0で良い!
と思ったものの、この実装だとあまりGPUを活用出来ていない気がする・・・


次回は高速な処理のままforの上限をもう少し伸ばしたバージョンを作成予定


■メインソース

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace WindowsGame1 {
public class Game1 : Microsoft.Xna.Framework.Game {
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;

Texture2D inputTexture;
Texture2D initTexture;
RenderTarget2D result;
Effect additionCalculator;

float[] culcurate;
float[] resultBuffer = new float[1];

public Game1() {
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}

protected override void Initialize() {
culcurate = new float[] { 1.0f, 0.1f, 0.2f, 0.3f };
base.Initialize();
}

protected override void LoadContent() {
spriteBatch = new SpriteBatch(this.GraphicsDevice);

inputTexture = new Texture2D(GraphicsDevice, culcurate.Length, 1, 0, TextureUsage.None, SurfaceFormat.Single);
inputTexture.SetData(culcurate);

initTexture = new Texture2D(GraphicsDevice, 1, 1, 0, TextureUsage.None, SurfaceFormat.Single);
initTexture.SetData(new float[]{0.0f});

result = new RenderTarget2D(GraphicsDevice, 1, 1, 0, SurfaceFormat.Single);


additionCalculator = Content.Load("AdditionCalculator");
}

protected override void UnloadContent() {
inputTexture.Dispose();
result.Dispose();
}

protected override void Update(GameTime gameTime) {
Window.Title = resultBuffer[0].ToString();
base.Update(gameTime);
}

protected override void Draw(GameTime gameTime) {
GraphicsDevice.SetRenderTarget(0, result);
spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
spriteBatch.Draw(initTexture, Vector2.Zero, Color.White);
spriteBatch.End();

additionCalculator.CurrentTechnique = additionCalculator.Techniques["CulculateTechnique"];

for (int i = 0; i < culcurate.Length; i++){
GraphicsDevice.SetRenderTarget(0, null);
additionCalculator.Parameters["SrcMap"].SetValue(result.GetTexture());
additionCalculator.Parameters["InputWidth"].SetValue(1.0f / culcurate.Length * i);

GraphicsDevice.SetRenderTarget(0, result);

spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
additionCalculator.Begin();
additionCalculator.CurrentTechnique.Passes[0].Begin();

spriteBatch.Draw(inputTexture, Vector2.Zero, Color.White);

additionCalculator.CurrentTechnique.Passes[0].End();
additionCalculator.End();
spriteBatch.End();
}

GraphicsDevice.SetRenderTarget(0, null);

result.GetTexture().GetData(resultBuffer);

base.Draw(gameTime);
}
}
}


■HLSL

float InputWidth;

texture SceneTexture;
sampler SceneSampler : register(s0) = sampler_state
{
Texture = (SceneTexture);
};

texture SrcMap;
sampler SrcSamp = sampler_state
{
Texture = ;
};

float4 PixelShaderFunction(float2 texCoord : TEXCOORD0) : COLOR0
{
return tex2D(SrcSamp, float2(0.5f, 0.5f)) + tex2D(SceneSampler, texCoord + float2(InputWidth, 0));
}

technique CulculateTechnique
{
pass Pass1
{
PixelShader = compile ps_2_0 PixelShaderFunction();
}
}
このエントリーをはてなブックマークに追加
LINEで送る

GPUを使って計算した結果をCPUに戻す[かってに改蔵]

GPUを利用した計算はおそらくCUDAの方が優れてるんじゃないかと思う。
しかしながら、今までさんざんHLSLでやっている訳で、
さらにそれほど時間もないのでHLSLにて実装してみる。


元はMemeplexesさんところの以下の記事
[XNA] GPUを使って計算した結果をCPUに戻す


改造点としては
・とりあえず配列で渡せるようにしてみた。
・GPUから値を戻す時の余分なバッファを削除
・ソース省略の目的でVertexPositionTextureを使わずにスプライトバッチで描画する方法を採用

問題点としては
・スプライトバッチでの処理速度への影響はプラスなのかマイナスなのか良く解らない。
・hlslなのにforとか使っていてかなりカッチョ悪い
・ピクセルシェーダー3.0じゃないとバッファ食いつぶす問題がある
・配列数が256を超えられない(これについては後日改訂予定)

昔、GPUで2D処理は遅いようなことをチラッと聞いたことがあるが、
内部的にはどうせ3Dだろ?と思うと、ソースコードが読みやすい方を正義と見たい。

全ソースコードは以下のとおり

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace WindowsGame1 {
public class Game1 : Microsoft.Xna.Framework.Game {
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;

Texture2D inputTexture;
RenderTarget2D result;
Effect additionCalculator;

float[] culcurate;
float[] resultBuffer = new float[1];

public Game1() {
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}

protected override void Initialize() {
culcurate = new float[] { 0.3f, 0.4f, 0.7f, 0.5f };
base.Initialize();
}

protected override void LoadContent() {
spriteBatch = new SpriteBatch(this.GraphicsDevice);

inputTexture = new Texture2D(GraphicsDevice, culcurate.Length, 1, 0, TextureUsage.None, SurfaceFormat.Single);
inputTexture.SetData(culcurate);

result = new RenderTarget2D(GraphicsDevice, 1, 1, 0, SurfaceFormat.Single);

additionCalculator = Content.Load("AdditionCalculator");
}

protected override void UnloadContent() {
inputTexture.Dispose();
result.Dispose();
}

protected override void Update(GameTime gameTime) {
Window.Title = resultBuffer[0].ToString();
base.Update(gameTime);
}

protected override void Draw(GameTime gameTime) {
GraphicsDevice.SetRenderTarget(0, result);

additionCalculator.CurrentTechnique = additionCalculator.Techniques["CulculateTechnique"];
additionCalculator.Parameters["InputWidth"].SetValue(culcurate.Length);

spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
additionCalculator.Begin();
additionCalculator.CurrentTechnique.Passes[0].Begin();

spriteBatch.Draw(inputTexture, Vector2.Zero, Color.White);

additionCalculator.CurrentTechnique.Passes[0].End();
additionCalculator.End();
spriteBatch.End();

GraphicsDevice.SetRenderTarget(0, null);

result.GetTexture().GetData(resultBuffer);

base.Draw(gameTime);
}
}
}



んでHLSLが以下のとおり

float InputWidth;

texture SceneTexture;
sampler SceneSampler : register(s0) = sampler_state
{
Texture = (SceneTexture);
};

float4 PixelShaderFunction(float2 texCoord : TEXCOORD0) : COLOR0
{
int limit = InputWidth % 256;
float4 result = 0;
for(int i = 0; i < limit; i++) {
result += tex2D(SceneSampler, texCoord + float2(1.0f / InputWidth * i, 0));
}
return result;
}

technique CulculateTechnique
{
pass Pass1
{
PixelShader = compile ps_3_0 PixelShaderFunction();
}
}



なんていうか、ここまでコメントがないソースも久しぶりに書いた・・

動作概要としては
culcurate = new float[] { 0.3f, 0.4f, 0.7f, 0.5f };
にて計算したい値をセット
Window.Title = resultBuffer[0].ToString();
にて計算結果をウィンドウのタイトルに設定しているだけ。

HLSL部はもっと綺麗に書く方法があると思うんだけどなぁ・・
動的な配列ってどう扱ったらいいんじゃろうねぇ。
このエントリーをはてなブックマークに追加
LINEで送る

XNAで動的コンテツを扱うときの注意点 その2

動的にインポートするコンテンツ事態は前回の方法でうまく行ったのだが、
読み込んだモデルをアニメーションさせるために
"SkinnedModelProcessor"を使用したいと思うとかなり苦労したのでメモ


ContentBuilder.csを配置しているプロジェクトにて
"Miscrosoft.Xna.Framework.Content.Pipeline.Processors"を参照設定に追加して

ContentBuilder.cs 内のメソッド CreateBuildProject() へ以下のように追記してやれば良い

// 任意のカスタム インポーターまたはプロセッサを登録します。
foreach(string pipelineAssembly in pipelineAssemblies) {
msBuildProject.AddNewItem("Reference", pipelineAssembly);
}

BuildItem item = msBuildProject.AddNewItem("Reference", typeof(SkinnedModelProcessor).AssemblyQualifiedName);
item.SetMetadata("SpecificVersion", "False");
item.SetMetadata("HintPath", typeof(SkinnedModelProcessor).Assembly.Location);


あとは用意したLoadModelメソッドにて

//contentBuilder.Add(fileName, "Model", null, "ModelProcessor");
contentBuilder.Add(fileName, "Model", null, "SkinnedModelProcessor");
とかって変更してやればOK


動的コンテンツの情報が日本に全然ない中、MMDXのソースはかなり参考になりました。
特に typeof(SkinnedModelProcessor) の記述は
海外のサンプルをいくらあさっても見つからなかったので、かなり助けられました。
このエントリーをはてなブックマークに追加
LINEで送る

XNAで動的コンテツを扱うときの注意点 その1

クリエータズクラブの奴はXNAというよりWinform側から作られていたので、
こいつをXNA側から作る時に、はまった内容のメモ。

利点は使い勝手が変わらないこと。
WinformからだとUpdate周りの処理をごっそり実装しなおす必要があって結構手間なので、
過去資産を活かす目的でXNA側からアプローチ

欠点としては、ちょうど昨年頭頃から課題として宙に浮いている入力周りが使えないこと。

まぁ、今回はゲームなのでデメリットは十分は無視できる。


んで、気をつける点。
まずファイルダイアログを呼ぶために『program.cs』に[STAThread]を記述する必要がある。

こんな感じ↓

using System;

namespace DaminzModelViewer {
static class Program {
[STAThread]
static void Main(string[] args) {
using(Game1 game = new Game1()) {
game.Run();
}
}
}
}


つぎに

"BuildContent" タスクは、アセンブリ "Microsoft.Xna.Framework.Content.Pipeline, Version=3.1.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" からインスタンス化できませんでした。タスク アセンブリが、このコンピュータにインストールされているバージョンと同じバージョンの Microsoft.Build.Framework アセンブリを使用してビルドされたことを確認して下さい。型'Microsoft.Xna.Framework.ContentPipeline.Tasks.BuildContent' のオブジェクトを型'Microsoft.Build.Framework.ITask' にキャストできません。
"BuildContent"タスクが宣言されたか、正しく使用されなかったか、または作成中に失敗しました。タスク名とアセンブリ名のスペルを確認して下さい。

とかって糞長いエラーメッセージの解消方法。


ソリューションエクスプローラーからプロジェクトを右クリック
追加→アプリケーション構成ファイル
とするとApp.configが追加されるのでその内容を以下のようにする。


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build.Framework" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-99.9.9.9" newVersion="3.5.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>


あとは、参照設定に追加するMicrosoft.Build.EngineとMicrosoft.Build.Frameworkのバージョンを
3.5に欲張ると何故かうまくいかないので2.0を使うことぐらいかな。


あとは普通に参照設定にSystem.Windows.Fromsを追加して

using System.IO;
using System.Windows.Forms;


あたりを追加すればできるはず。
他に何かはまったらここに追記していくつもり。
このエントリーをはてなブックマークに追加
LINEで送る

ClickOnceでDLLとかを含めて配布する方法

忘れないようにメモ

http://social.msdn.microsoft.com/Forums/ja-JP/csharpexpressja/thread/c779b300-fe89-4dd8-811b-85f311db0057

* DllImport を利用して sample.dll を使用しているアプリケーションを作成した
* ClickOnce の配布に sample.dll を追加したい

sample.dll をアプリケーションの使用ファイルに追加する必要があるので、
発行の「アプリケーションファイル」に sample.dll を追加。

「アプリケーションファイル」の一覧に sample.dll が表示されない場合は、
プロジェクトに sample.dll が含まれていないので、プロジェクトに sample.dll を追加し、
「出力ディレクトリにコピー」を「コピーする」に設定することで表示される。

この時、プロジェクトフォルダにローカルコピーされるのが困る場合は
リンクファイルとしてプロジェクトに追加する
このエントリーをはてなブックマークに追加
LINEで送る

1×1のビットマップを読み込まずテクスチャ2Dを作成する方法

スプライトで線やら四角やら描くのに
いちいち1×1のビットマップをコンテンツから
ロードするのが鬱陶しくなったので海外サイトを参考に作成

Texture2D myTex = new Texture2D(graphics.GraphicsDevice, 1, 1, 1, TextureUsage.None, SurfaceFormat.Color);
myTex.SetData(new Color[]{new Color(255, 255, 255, 255)});

多分透過情報も含めてうまくやれば丸やら三角やらも作れるんじゃないかなとおもう。
このエントリーをはてなブックマークに追加
LINEで送る

ながーいContentのコンパイルを避ける方法

いよいよゲームも完成が見え始めてくるとコンテンツのコンパイル時間がやたらと長くなってくる。

ちなみに、2行の修正でコンパイルに40分待ちとか、正直やっていられない。
しかもContentのコンパイルが走る条件がいまいち不明で、ランダムにやってるんじゃないかってくらい気まぐれだったりする。

そんな天に祈る気持ちでコンパイルボタンを押さずに済む方法があった。
簡単にいえばゲームライブラリを別に用意して、そいつにコンテンツをごっそり移動してやるだけ。

詳しくは、このサイトが解説している。

タイトルが
『第5回 ゲーム開発者にとってのビルド作業とは? ― Content Pipelineを使ってみる』
とかなってて分かりにくいが、このテクニックが紹介されてるのはここだけだったりする。

これで、引き当てると見事一回40分のハズレクジ付きのくじ引きをせずに済むようになる
このエントリーをはてなブックマークに追加
LINEで送る

 | HOME |  NEXT »

PAGE TOP ▲

Appendix

■春条

■春条

生息地は愛知
車と甘い物が好きな31歳
特技は無限昼寝

MONOからSharpDXを使う
変態的な手法で、
.NET Frameworkを使わずに
ゲーム作りやってます。

Search

Calender

« | 2017-06 | »
S M T W T F S
- - - - 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 -

Twitter

Recent Entries

DTIブログポータルへ
このブログを通報
Report Abuse

利用規約