XNA stottert in regelmäßigen Abständen

10

Ich versuche, Hardware zu instanziieren, aber ich stoße auf ein seltsames Leistungsproblem. Die durchschnittliche Framerate liegt bei 45, ist aber extrem abgehackt.

  • Fenster
  • SynchronizeWithVerticalRetrace = false
  • IsFixedTimeStep = false
  • PresentationInterval = PresentInterval.Immediate

Das Bild unten zeigt mein gemessenes Timing (mit Stopwatch). Das oberste Diagramm ist die Zeit, die in der DrawMethode verbracht wurde , und das unterste Diagramm ist die Zeit vom Ende Drawbis zum Beginn vonUpdate Zeichnen und xna Timing

Die Spitzen sind fast genau 1 Sekunde voneinander entfernt und sind immer 2,3,4 oder 5 mal die übliche Zeit. Die Frames unmittelbar nach dem Spike brauchen überhaupt keine Zeit. Ich habe überprüft, dass es nicht der Müllsammler ist.

Ich instanziiere derzeit ein Netz aus 12 Dreiecken und 36 Eckpunkten als Dreiecksliste (ich weiß, dass es nicht optimal ist, aber nur zum Testen) mit 1 Million Instanzen. Wenn ich die Instanz-Draw-Aufrufe in kleine Teile von jeweils 250 Instanzen staple, wird das Problem behoben, aber die CPU-Auslastung nimmt erheblich zu. Der obige Lauf liegt bei 10000 Instanzen pro Draw-Aufruf, was für die CPU viel einfacher ist.

Wenn ich das Spiel im Vollbildmodus ausführe, ist das untere Diagramm fast nicht vorhanden, aber das gleiche Problem tritt jetzt bei der DrawMethode auf.

Hier ist ein Lauf in PIX , der für mich überhaupt keinen Sinn ergibt . Es scheint, dass für einige Frames kein Rendering durchgeführt wird ...

Irgendeine Idee, was könnte das verursachen?

BEARBEITEN : Auf Wunsch die relevanten Teile des Rendercodes

A CubeBufferwird erstellt und initialisiert und dann mit Würfeln gefüllt. Wenn die Anzahl der Würfel einen bestimmten Grenzwert überschreitet, wird ein neuer CubeBuffererstellt und so weiter. Jeder Puffer zeichnet alle Instanzen in einem Aufruf.

Informationen, die nur einmal benötigt werden, sind static(Scheitelpunkt, Indexpuffer und Scheitelpunktdeklaration; obwohl dies bisher keinen Unterschied macht). Die Textur ist 512x512

Zeichnen()

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);
    }

Shader

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();
 }
}
Darcara
quelle
Ich bin mir nicht sicher, ob es für die Zeit vom Ende der Auslosung bis zum Beginn der Aktualisierung relevant ist, da sie nicht stark miteinander verbunden sind (dh viele Aktualisierungen können zwischen zwei Ziehungen erfolgen, wenn das Spiel langsam ausgeführt wird, was der Fall sein muss, da Sie nicht ausgeführt werden bei 60 fps). Sie können sogar in separaten Threads ausgeführt werden (aber ich bin mir nicht sicher).
Zonko
Ich habe keine wirkliche Ahnung atm, aber wenn es mit kleineren Batches funktioniert, ist es anscheinend ein Problem mit Ihrem Batching-Code, posten Sie den relevanten XNA- und HLSL-Code, damit wir ihn mit IsFixedTimeStep = False genauer betrachten können = False, es gibt ein 1: 1-Update / Draw Calls
Daniel Carlsson
Hier ist eine Erklärung, warum dieses Stottern von Shawn Hargreaves (im xna-Team) passiert
NexAddo

Antworten:

3

Ich vermute, dass Ihre Leistung GPU-gebunden ist. Sie fordern Ihr Grafikgerät lediglich auf, mehr Arbeit pro Zeiteinheit zu erledigen, als es verarbeiten kann. 36 Millionen Scheitelpunkte pro Frame sind eine recht anständige Zahl, und Hardware-Instanzen können den Verarbeitungsaufwand auf der GPU-Seite der Gleichung tatsächlich erhöhen. Zeichne weniger Polygone.

Warum verschwindet das Problem, wenn die Chargengröße reduziert wird? Weil die CPU länger braucht, um einen Frame zu verarbeiten, was bedeutet, dass sie weniger Zeit damit verbringt, darauf zu Present()warten, dass die GPU das Rendern beendet. Das ist es, was ich denke, während dieser Lücke am Ende Ihrer Draw()Anrufe.

Der Grund für das spezifische Timing der Lücken ist schwerer zu erraten, ohne den gesamten Code zu verstehen, aber ich bin mir auch nicht sicher, ob es wichtig ist. Arbeiten Sie mehr an der CPU oder weniger an der GPU, damit Ihre Arbeitslast weniger ungleichmäßig ist.

Weitere Informationen finden Sie in diesem Artikel auf Shawn Hargreaves 'Blog.

Cole Campbell
quelle
2
Es ist definitiv GPU-gebunden. Die App ist im Wesentlichen ein Benchmark, um verschiedene Zeichenmethoden zu erkunden. Eine kleinere Stapelgröße mit der gleichen Anzahl gezeichneter Scheitelpunkte würde auf der CPU länger dauern, aber die GPU-Last sollte gleich sein, nicht wahr? Zumindest würde ich eine konsistente Zeit zwischen Frames erwarten, abhängig von der Last (die sich zwischen den Frames überhaupt nicht ändert) und nicht von solchen regelmäßigen Intervallen für Verzögerung und Sofort (oder kein Rendering, siehe PIX).
Darcara
Wenn ich Ihre Grafiken richtig interpretiere, sind die sofort gerenderten Frames Teil der Funktionalität des XNA Frameworks. Mit der IsFixedTimeStepEinstellung false: Wenn das Spiel zu langsam läuft, ruft XNA Update()mehrmals hintereinander auf, um aufzuholen, und lässt dabei absichtlich Frames fallen. Wird IsRunningSlowlywährend dieser Frames auf true gesetzt? Was das seltsame Timing angeht - ich wundere mich ein bisschen. Laufen Sie im Fenstermodus? Bleibt das Verhalten im Vollbildmodus bestehen?
Cole Campbell
Nachholanrufe finden nur am statt IsFixedTimeStep=true. Das untere Diagramm zeigt die Zeit zwischen dem Ende meiner Ziehung und dem Beginn des Aktualisierungsaufrufs des nächsten Frames. Die Frames werden nicht gelöscht, ich rufe die Draw-Methoden auf und bezahle den CPU-Preis dafür (oberes Diagramm). Gleiches Verhalten im Vollbildmodus und bei allen Auflösungen.
Darcara
Du hast recht, mein Fehler. Ich fürchte, ich habe meine Ideen zu diesem Zeitpunkt erschöpft.
Cole Campbell
2

Ich denke, Sie haben ein Müllproblem ... Vielleicht erstellen / zerstören Sie viele Objekte und diese Spitzen sind die Routine für die Müllsammler-Arbeit ...

Stellen Sie sicher, dass Sie alle Ihre Speicherstrukturen wiederverwenden ... und verwenden Sie "neu" nicht zu oft

Blau
quelle
Bereits in ProcessExplorer und CLRProfiler überprüft, und der gc läuft wie alle 10 Sekunden und nicht annähernd 75 ms lang.
Darcara
1
Sie möchten definitiv nicht in jedem Frame einen neuen RasterizerState, BlendState und DepthStencilState erstellen, unabhängig davon, ob dies die Ursache für Ihre Rendering-Verlangsamung ist oder nicht. Es wird definitiv nicht helfen, wie in diesem Artikel beschrieben blogs.msdn.com/b/shawnhar/archive/2010/04/02/… Sie sollten den Status erstellen, den Sie einmal beim Laden verwenden werden, und ihn bei Bedarf erneut anwenden .
Dadoo Games