定期的なXNAスタッター


10

ハードウェアのインスタンス化を実行しようとしていますが、奇妙なパフォーマンスの問題が発生しています。平均フレームレートは約45ですが、非常に不安定です。

  • 窓付き
  • SynchronizeWithVerticalRetrace = false
  • IsFixedTimeStep = false
  • PresentationInterval = PresentInterval.Immediate

下の画像は測定したタイミングを示しています(を使用Stopwatch)。一番上のグラフはDrawメソッドで費やされた時間で、一番下のグラフは終了からDraw開始までの時間ですUpdate 描画とXNAタイミング

スパイクはほぼ正確に1秒間隔であり、常に通常の時間の2、3、4、または5倍です。スパイクの直後のフレームにはまったく時間がかかりません。ガベージコレクタではないことを確認しました。

私は現在、100万のインスタンスを持つ12個の三角形と36個の頂点で構成されるメッシュを三角形リストとしてインスタンス化しています(最適ではないことを知っていますが、テスト用です)。インスタンス化の描画呼び出しを250インスタンスの小さな部分にバッチ処理すると、問題は軽減されますが、CPU使用率が大幅に増加します。上記の実行は、描画呼び出しごとに10000インスタンスです。これは、CPUではるかに簡単です。

全画面でゲームを実行すると、下部のグラフはほとんど存在しませんが、Drawメソッドで同じ問題が発生します。

これはPIX内での実行ですが、まったく意味がありません。一部のフレームではレンダリングが行われていないようです...

何か考え、これを引き起こしている可能性がありますか?

編集:要求に応じて、レンダーコードの関連部分

A CubeBufferが作成および初期化され、次にキューブで満たされます。キューブの量が特定の制限を超えると、新しいものCubeBufferが作成されます。各バッファは、1回の呼び出しですべてのインスタンスを描画します。

一度だけ必要な情報はstatic(頂点、インデックスバッファー、頂点宣言です。これまでのところ違いはありません)。テクスチャは512x512です

ドロー()

device.Clear(Color.DarkSlateGray);
device.RasterizerState = new RasterizerState() {  };
device.BlendState = new BlendState { };
device.DepthStencilState = new DepthStencilState() { DepthBufferEnable = true };

//samplerState=new SamplerState() { AddressU = TextureAddressMode.Mirror, AddressV = TextureAddressMode.Mirror, Filter = TextureFilter.Linear };
device.SamplerStates[0] = samplerState
effect.CurrentTechnique = effect.Techniques["InstancingTexColorLight"];
effect.Parameters["xView"].SetValue(cam.viewMatrix);
effect.Parameters["xProjection"].SetValue(projectionMatrix);
effect.Parameters["xWorld"].SetValue(worldMatrix);
effect.Parameters["cubeTexture"].SetValue(texAtlas);
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
    pass.Apply();

foreach (var buf in CubeBuffers)
    buf.Draw();
base.Draw(gameTime);

CubeBuffer

[StructLayout(LayoutKind.Sequential)]
struct InstanceInfoOpt9
    {
    public Matrix World;
    public Vector2 Texture;
    public Vector4 Light;
    };

static VertexBuffer geometryBuffer = null;
static IndexBuffer geometryIndexBuffer = null;
static VertexDeclaration instanceVertexDeclaration = null;
VertexBuffer instanceBuffer = null;
InstanceInfoOpt9[] Buffer = new InstanceInfoOpt9[MaxCubeCount];
Int32 bufferCount=0

Init()
    {
    if (geometryBuffer == null)
        {
        geometryBuffer = new VertexBuffer(Device, typeof (VertexPositionTexture), 36, BufferUsage.WriteOnly);
        geometryIndexBuffer = new IndexBuffer(Device, typeof (Int32), 36, BufferUsage.WriteOnly);
        vertices = new[]{...}
        geometryBuffer.SetData(vertices);
        indices = new[]{...}
        geometryIndexBuffer.SetData(indices);

        var instanceStreamElements = new VertexElement[6];
        instanceStreamElements[0] = new VertexElement(sizeof (float)*0, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 1);
        instanceStreamElements[1] = new VertexElement(sizeof (float)*4, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 2);
        instanceStreamElements[2] = new VertexElement(sizeof (float)*8, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 3);
        instanceStreamElements[3] = new VertexElement(sizeof (float)*12, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 4);
        instanceStreamElements[4] = new VertexElement(sizeof (float)*16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 5);
        instanceStreamElements[5] = new VertexElement(sizeof (float)*18, VertexElementFormat.Vector4, VertexElementUsage.TextureCoordinate, 6);

        instanceVertexDeclaration = new VertexDeclaration(instanceStreamElements);
        }

    instanceBuffer = new VertexBuffer(Device, instanceVertexDeclaration, MaxCubeCount, BufferUsage.WriteOnly);
    instanceBuffer.SetData(Buffer);
    bindings = new[]
        {
        new VertexBufferBinding(geometryBuffer), 
        new VertexBufferBinding(instanceBuffer, 0, 1),
            };
    }

AddRandomCube(Vector3 pos)
    {
    if(cubes.Count >= MaxCubeCount)
        return null;
    Vector2 tex = new Vector2(rnd.Next(0, 16), rnd.Next(0, 16))
    Vector4 l= new Vector4((float)rnd.Next(), (float)rnd.Next(), (float)rnd.Next(), (float)rnd.Next());
    var cube = new InstanceInfoOpt9(Matrix.CreateTranslation(pos),tex, l);

    Buffer[bufferCount++] = cube;

    return cube;
    }

Draw()
    {
    Device.Indices = geometryIndexBuffer;
    Device.SetVertexBuffers(bindings);
    Device.DrawInstancedPrimitives(PrimitiveType.TriangleList, 0, 0, 36, 0, 12, bufferCount);
    }

シェーダー

float4x4 xView;
float4x4 xProjection;
float4x4 xWorld;
texture cubeTexture;

sampler TexColorLightSampler = sampler_state
{
texture = <cubeTexture>;
mipfilter = LINEAR;
minfilter = LINEAR;
magfilter = LINEAR;
};

struct InstancingVSTexColorLightInput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
};

struct InstancingVSTexColorLightOutput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
float4 Light : TEXCOORD1;
};

InstancingVSTexColorLightOutput InstancingVSTexColorLight(InstancingVSTexColorLightInput input, float4x4 instanceTransform : TEXCOORD1, float2 instanceTex : TEXCOORD5, float4 instanceLight : TEXCOORD6)
{
float4x4 preViewProjection = mul (xView, xProjection);
float4x4 preWorldViewProjection = mul (xWorld, preViewProjection);

InstancingVSTexColorLightOutput output;
float4 pos = input.Position;

pos = mul(pos, transpose(instanceTransform));
pos = mul(pos, preWorldViewProjection);

output.Position = pos;
output.Light = instanceLight;
output.TexCoord = float2((input.TexCoord.x / 16.0f) + (1.0f / 16.0f * instanceTex.x), 
                         (input.TexCoord.y / 16.0f) + (1.0f / 16.0f * instanceTex.y));

return output;
}

float4 InstancingPSTexColorLight(InstancingVSTexColorLightOutput input) : COLOR0
{
float4 color = tex2D(TexColorLightSampler, input.TexCoord);

color.r = color.r * input.Light.r;
color.g = color.g * input.Light.g;
color.b = color.b * input.Light.b;
color.a = color.a * input.Light.a;

return color;
}

technique InstancingTexColorLight
{
 pass Pass0
 {
 VertexShader = compile vs_3_0 InstancingVSTexColorLight();
 PixelShader = compile ps_3_0 InstancingPSTexColorLight();
 }
}

描画の終わりから更新の開始までの時間に関連しているかどうかはわかりません。それらは強くリンクされていないためです(つまり、ゲームの実行が遅い場合、2つの描画の間に多くの更新が発生する可能性があります。 60 fps)。それらは別のスレッドで実行されることさえあります(しかし、私はこれについて確信がありません)。
Zonko

私には本当の手掛かりはありませんが、それが小さなバッチ処理で機能する場合、バッチ処理コードに明らかに問題がある場合は、関連するXNAおよびHLSLコードを投稿して、IsFixedTimeStep = Falseで@Zonkoをより詳しく確認できるようにします1:1の更新があります/ drawの呼び出し
Daniel Carlsson

ここでは、この吃音は(XNAチームの)ショーンハーグリーブスから起こる理由の説明は次のとおりです。forums.create.msdn.com/forums/p/9934/53561.aspx#53561
NexAddo

回答:


3

あなたのパフォーマンスはGPUに依存していると思います。あなたは単に、グラフィックスデバイスに処理能力を超える単位時間あたりの処理を依頼するだけです。フレームあたり3,600万の頂点はかなりまともな数であり、ハードウェアのインスタンス化は、方程式のGPU側で必要な処理作業量を実際に増加させる可能性があります。描画するポリゴンを減らします。

バッチサイズを小さくすると問題が解決するのはなぜですか?CPUがフレームを処理するのに時間がかかるPresent()ため、GPUがレンダリングを完了するのを待機する時間を節約できます。それはあなたのDraw()電話の終わりのそのギャップの間にそれがしていることだと私は思う。

ギャップの特定のタイミングの背後にある理由は、コード全体を理解しないと理解するのが困難ですが、それが重要であるかどうかもわかりません。CPUの作業量を増やすか、GPUの作業量を減らして、ワークロードの不均一性を減らします。

詳細については、Shawn Hargreavesのブログのこの記事を参照してください。


2
これは間違いなくGPUバウンドです。アプリは基本的にベンチマークであり、さまざまな描画方法を探索します。描画される頂点の数が同じでバッチサイズが小さいと、CPUでの時間が長くなりますが、GPU負荷は同じである必要がありますか?少なくとも、フレーム間で一貫した時間を期待します。これは、負荷(フレーム間でまったく変化しない)に依存し、ラグとインスタントの定期的な間隔(またはレンダリングなし、PIXを参照)ではありません。
Darcara

グラフを正しく解釈している場合、即座にレンダリングされるフレームはXNA Frameworkの機能の一部です。にIsFixedTimeStep設定するfalseと、ゲームの実行速度が遅すぎる場合、XNAはUpdate()連続して複数回呼び出して追いつき、プロセス内のフレームを意図的にドロップします。されIsRunningSlowly、これらのフレームの間、trueに設定されていますか?奇妙なタイミングに関して-それは私を少し不思議にさせます。ウィンドウモードで実行していますか?動作はフルスクリーンモードでも持続しますか?
Cole Campbell

キャッチアップコールはでのみ発生しIsFixedTimeStep=trueます。下のグラフは、描画が終了してから次のフレームの更新呼び出しが始まるまでの時間を示しています。フレームはドロップされません。描画メソッドを呼び出して、CPUの価格を支払います(上のグラフ)。全画面表示と解像度を問わず同じ動作。
Darcara

あなたは正しい、私の間違い。この時点で自分の考えを使い果たしたと思います。
Cole Campbell

2

あなたはガベージの問題があると思います...多分あなたは多くのオブジェクトを作成/破壊していて、そのスパイクはガベージコレクターのルーチンが働いている...

すべてのメモリ構造を再利用するようにしてください...そして、頻繁に「新しい」を使用しないでください


すでにProcessExplorerとCLRProfilerでそれを確認し、gcは10秒に1回のように実行されており、75msほどではありません。
Darcara

1
レンダリングの速度低下の原因であるかどうかに関係なく、フレームごとに新しいRasterizerState、BlendState、DepthStencilStateを作成することは絶対に避けたいです。この記事ごとのように、それは、間違いではないのに役立ちますblogs.msdn.com/b/shawnhar/archive/2010/04/02/...あなたが必要なときに再適用して負荷に一度使用され、状態を作成する必要があります。
dadoo Games
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.