Posts Tagged ‘ コラム

チップの登録機構

どうも、お久しぶりです。テストがあったり、私が所属してる情報工学科に革命がおこったりで色々ごたごたしてまして落ちつて作業できる時間が少なくて、更新できてなかったです。どんな革命が起きたかはここで語ることじゃないので、控えておきますが。

さてタイトルのことですがこれはかなり悩みに悩みました。
この前書いたとおりこのプロジェクトはプラグインに対応させるつもりです。ですのでできるだけ外から追加できる拡張性をもったコードを書かなければならず、 しかもこのチップの部分はこのプログラムの中核の部分なので手は抜きたくありませんでした。

デザインパターンをあたってみて、「あ、これ使えるじゃん!!ふむふむ、え~っとこうやって、これがこうで… で、肝心なここは……使えねぇ!!!!!」的なことの連発でした。

どうにかならないか困り果ててとつぶやいたら@haxeさんから回答がいただけました。

kassyi
@kassyi
@haxe
クラスを条件によって作り分けたいんです。例え話にすると調査員という仕事があり、Aは0歳から9歳までの朝食の食べる量を調査する、Bは14~50歳までの歩数を調査する。AとBが調査員というクラスを継承しているとすると、どうすればインスタンスを作り分けられるかって話です。
link
kassyi @kassyi @haxe 普通に考えれば
if(age >= 0 && age <= 9)
retrun new A();
else if(age >=14 && age<= 50)
retrun new B();
となると思うのですが、これだとプラグインなどで調査員を追加するのが難しくて。。
link
kassyi @haxe @kassyi 今WebRequestクラス見てるので、お勧めするのはその方法かなぁ。 http://msdn.microsoft.com/ja-jp/library/system.net.webrequest.aspx link
haxe @haxe @kassyi ちなみに標準機能はどこかで初期化するとして、利用者が派生クラスを追加する際に、app.configから登録できるようにしておくとさらに吉。 http://msdn.microsoft.com/ja-jp/library/42z9z1b9.aspx link

このやり取りのおかげ様でやっとこさ整合性のとれたコードを書くことができました。書いたコードをざっとまとめるとこんな感じです。

すべてのチップはこのCihpクラスを継承して表現されます。また継承されたクラスのコンストラクタはprotectedにして直接はインスタンスが作れないようにします。で、どこからインスタンスが作れるかというとChipクラスのCreate()です。そのCreate()の中で何をやってるかというと、登録された派生クラスのクリエイター対して「このIDのチップ扱える?」って順繰りに訪ねて行って扱えるなら「インスタンスを頂戴ね」ということでクリエイターにインスタンスを作らしています。

…ってなことで、なんとかえっちらおっちら拡張性のあるチップ登録機構を作ることができました。いやーまさかWebRequestクラスをまねできるとは思いませんでした…。.Netライブラリは当然プログラマーのプロの人が書いているので吸収できるところは吸収するようにしようと思いました。@haxeさんに、感謝。

RenderTarget2D.GetTexture()は同じアドレスを返す

RenderTarget2D.GetTexture()した結果を配列や二つの変数に代入し、あとからそのRenderTarget2Dをいじくり回すと両者が同じになるということについてです。

何のこっちゃさっぱりって人は、こちらのサンプルをどうぞ。XNA3.0で作成しています。

操作方法
左シフト->レンダリングターゲットからテクスチャを取得し左側のテクスチャに代入する。
右シフト->レンダリングターゲットからテクスチャを取得し右側のテクスチャに代入する。
そうするとソース上では片方しか描き換えてないのに、 両方書き換わってしまう。

私は最初これと同じようなことをしていて、この現象に出合い混乱して乱数クラスが狂ったのかと思いました。でもデバッガで追ってくと そうじゃなかったですね。ちゃんと乱数になってましたし、代入してるのも変数一つでした。で、ふと思いついたのが「ポインタ」って言葉。もしGetTexture()で取得できるオブジェクトのアドレスが同じならつじつまが通る…!

確かめてみようと思ってunsafeコード書いてみましたが、どうやら値型のアドレスは取得できても参照型のアドレスは取得できないようで、断念。

で、どうするか。下のような感じでディープコピーしてやればOKです。

GraphicsDevice.SetRenderTarget(0, null);
Texture2D newTex = new Texture2D(GraphicsDevice, tex1.Width, tex1.Height);
Color[] data = new Color[tex1.Width * tex1.Height];
target.GetTexture().GetData(data);
newTex.SetData(data);
tex1 = newTex;

ただひとつ問題なのがこの記事で検証されているようにXBOX360上ではGetData()/SetData()がパフォーマンスにかなり影響を与えるようです。特にGetData()が。今のところこれに対する対策は見つかってません…。描画処理をGPU上で全部やって最終的にテクスチャをもらうということぐらいしか思いつきません。ただそれだとfxファイルを書かなければならないですし、SpriteBatchなどで複雑な描画をしていた場合はとGPUには不向きかと。

うが~~~~!!!
XBOXでゲーム作りたいです…。

2009-12-16 01:18:49追記。

簡単な話でしたね。GetTexture()する数だけRenderTarget2Dを作ればいいんですね。
なぜ気がつかなかった!???


疲れてきてるな流石に…

RenderTarget2Dでエラー

バックバッファより大きいか、バックバッファより小さいRenderTarget2Dを作って、レンダーターゲットを切り替えてGraphicsDevice.Clear()もしくはspriteBatch.End()などをすると以下のようにエラーになったことがあると思います。

System.InvalidOperationExceptionエラー
アクティブなレンダー ターゲットと深度ステンシル サーフェイスは、ピクセル サイズとマルチサンプリング タイプがそれぞれ同じである必要があります。

具体的にはこんな感じのコードでエラーになるかと。

graphics.PreferredBackBufferHeight = 720;
graphics.PreferredBackBufferWidth = 1280;
var target = new RenderTarget2D(GraphicsDevice, 100, 100, 1, SurfaceFormat.Color);
//DepthStencilBuffer old = GraphicsDevice.DepthStencilBuffer;
//GraphicsDevice.DepthStencilBuffer = GfxComponent.CreateDepthStencil(target, DepthFormat.Depth24Stencil8Single);
GraphicsDevice.SetRenderTarget(0, target);
GraphicsDevice.Clear(Color.Brown);
GraphicsDevice.SetRenderTarget(0, null);
//GraphicsDevice.DepthStencilBuffer = old;
2009-12-31 追記

このままのコードだと、パフォーマンスが非常に悪くなるので、GfxComponent.CreateDepthStencilで作ったテクスチャは再利用するようにしたほうがいいです。


いままでは、このエラーを回避するにはレンダーターゲットの大きさを画面のサイズ(バックバッファ)と同じにするしかないと思ってました。でもエラーメッセージにはバックバッファなんて単語はひとつも入ってないんです。おかしいなぁと思って調べまくった結果、コメントアウトしてあることを追加すればOKでした。GfxComponentとかいうクラスはMSDNライブラリの「方法 : 深度テクスチャーを作成する」の「完全なサンプル コード」というところから落としてください。そのプロジェクトの中に入ってます。


以下、自分なりに解釈したこんなエラーになる理由です。

続きを読む

GDI+において描画処理を高速化する方法

1、ダブルバッファリングを有効にする。

//.net 1.xまで
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
//.net 2.0から
DoubleBuffered = true; 

として.Net標準のダブルバッファリングが有効にできる。比較的遅いメモリから画面に転写するのを一度だけに抑えられるので複雑な描画をしているときに有効。描画の過程を隠すこともでき、一石二鳥。

2、描画する面積を少なくする。

GPUは広い範囲を高速に更新するように設計されているため、全領域を更新ないといろいろ不具合が出るが、CPUは広い範囲には向いていない。そこで、描画する面積をできるだけ少なくする。

3、できるだけまとめて描く。

たとえば、

for (int z = 0; z < 100; z++)
{
    for (int y = 0; y < 100; y++)
    {
        for (int x = 0; x < 100; y++)
        {
            e.Graphics.DrawImage(image, new Rectangle(x, y, 1, 1));
        }
    }
}

とするのと

e.Graphics.DrawImage(image, new Rectangle(0,0 , 100, 100));

では速度にものすごい差が出る。これはCPUの場合のみではなくGPUも同じ。ただしGPUの場合はCPUに比べその差が少ない。


でも、これはあくまでも経験からであることを承知してほしい。

VS2008SDKカスタムエディタ解読

頭からぼりぼりかじってる状態なのでまだ全体が見えてませんが、忘れないうちにメモ。

C#でマネージだけれどもやってることはアンマネージに近くなってる。0/1でfalse,trueを表してたり、バイトデータで失敗・成功・詳細データを表現してる。そのため失敗してもフラグがたつだけだからエラーをスローしたいときは、ErrorHandler.ThrowOnFailureを用いる。結構めんどそう。

どうやらプログラムのメインルーチンが入ってるファイルは、EditorPane.csっぽい。この中にコピー・ペースト・Undo・Redoなどのコマンドハンドラや、編集するファイルの拡張子が定義(変数名:MyExtension)されてる。でもその変数を変更しただけだと、保存するときの拡張子がそれになるだけだった。その辺はもう少し読み進めてかないとだめ。

int IVsTextImage.Replaceという インターフェースがあった。どこから呼び出されてるのかと「すべての参照を検索」してみた。どこも呼び出してない!!気になってブレークポイントをしかけて実際に置換してみたらpublicでないのに外部コードから呼び出されてた。謎すぐる…。
まぁとりあえ書いておきゃあ呼び出してくれるんですね。

ウイザードで設定したパッケージの名前の変更の仕方:
VSPackage.resxを開くとその中に設定した文字列がある。それを変更することで変更できる。多言語でプログラムを組むときはこういう方式のほうが便利。ただ若干コードが読みづらくなる。