Balbuzie XNA a intervalli regolari


10

Sto cercando di installare l'istanziamento hardware ma sto riscontrando uno strano problema di prestazioni. Il framerate medio è di circa 45, ma è estremamente instabile.

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

L'immagine sotto mostra il mio tempo misurato (con Stopwatch). Il grafico più in alto è il tempo impiegato nel Drawmetodo e il grafico in basso è il tempo dalla fine di Drawfino all'inizio diUpdate Disegna e xna timing

I picchi distano quasi esattamente 1 secondo e sono sempre 2,3,4 o 5 volte il solito tempo. I frame immediatamente successivi allo spike non richiedono tempo. Ho verificato che non è il Garbage Collector.

Attualmente sto istanziando una mesh composta da 12 triangoli e 36 vertici come un elenco di triangoli (so che non è ottimale, ma è solo per il test) con 1 milione di istanze. Se misuro il richiamo dell'istanza in chiamate in piccole parti di 250 istanze ciascuna il problema viene alleviato, ma l'utilizzo della CPU aumenta in modo significativo. L'esecuzione sopra è a 10000 istanze per richiamo, che è molto più semplice sulla CPU.

Se eseguo il gioco a schermo intero, il grafico in basso è quasi inesistente, ma lo stesso problema si presenta ora nel Drawmetodo.

Ecco una corsa all'interno di PIX , che per me non ha alcun senso. Sembra che per alcuni frame non sia stato eseguito il rendering ...

Qualche idea, cosa potrebbe causare questo?

EDIT : come richiesto, le parti pertinenti del codice di rendering

A CubeBufferviene creato e inizializzato, quindi riempito con cubetti. Se la quantità di cubi supera un determinato limite, ne CubeBufferviene creato uno nuovo e così via. Ogni buffer disegna tutte le istanze in una chiamata.

Le informazioni necessarie una sola volta sono static(vertice, buffer dell'indice e dichiarazione dei vertici; sebbene finora non faccia alcuna differenza). La trama è 512x512

Disegnare()

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

Non sono sicuro che sia rilevante per il tempo che intercorre tra la fine del sorteggio e l'inizio dell'aggiornamento, poiché non sono fortemente collegati (vale a dire che molti aggiornamenti possono avvenire tra 2 sorteggi se il gioco procede lentamente, il che deve essere il caso in cui non si è in esecuzione a 60 fps). Potrebbero anche funzionare in thread separati (ma non ne sono sicuro).
Zonko,

Non ho un vero indizio, ma se funziona con il batch più piccolo sembra essere un problema con il tuo codice di batch, pubblica il codice XNA e HLSL pertinente in modo da poterlo osservare più da vicino @Zonko con IsFixedTimeStep = False c'è un aggiornamento 1: 1 / draw call
Daniel Carlsson,

Ecco una spiegazione del perché questa balbuzie si verifica da Shawn Hargreaves (nella squadra di xna): forums.create.msdn.com/forums/p/9934/53561.aspx#53561
NexAddo

Risposte:


3

Immagino che le tue prestazioni siano legate alla GPU. Stai semplicemente chiedendo al tuo dispositivo grafico di fare più lavoro per unità di tempo di quanto sia in grado di gestire; 36 milioni di vertici per frame sono un numero abbastanza decente e l'istanziazione hardware può effettivamente aumentare la quantità di lavoro di elaborazione necessaria sul lato GPU dell'equazione. Disegna meno poligoni.

Perché la riduzione della dimensione del lotto risolve il problema? Perché la CPU impiega più tempo a elaborare un frame, il che significa che sta trascorrendo meno tempo a rimanere in Present()attesa che la GPU finisca il rendering. Questo è quello che penso stia facendo durante quella lacuna alla fine delle tue Draw()chiamate.

La ragione dietro il tempismo specifico delle lacune è più difficile da divinare senza comprendere l'intero codice, ma non sono nemmeno sicuro che sia importante. Fai più lavoro sulla CPU o meno sulla GPU, in modo che il tuo carico di lavoro sia meno irregolare.

Vedi questo articolo sul blog di Shawn Hargreaves per maggiori informazioni.


2
È sicuramente legato alla GPU. L'app è essenzialmente un punto di riferimento, per esplorare diversi metodi di disegno. Una dimensione batch inferiore con lo stesso numero di vertici disegnati richiederebbe più tempo sulla CPU, ma il carico della GPU dovrebbe essere lo stesso, no? Almeno mi aspetterei un tempo coerente tra i fotogrammi, a seconda del carico (che non cambia affatto tra i fotogrammi) e non di tali intervalli regolari di ritardo e istantaneo (o nessun rendering, vedi PIX).
Darcara,

Se sto interpretando correttamente i tuoi grafici, i frame resi istantaneamente fanno parte della funzionalità di XNA Framework. Con IsFixedTimeStepimpostato su false, Se il gioco funziona troppo lentamente, XNA chiamerà Update()più volte di seguito per recuperare, lasciando deliberatamente cadere i fotogrammi nel processo. È IsRunningSlowlyimpostato su true durante questi frame? Per quanto riguarda lo strano tempismo, mi fa meravigliare un po '. Stai correndo in modalità finestra? Il comportamento persiste in modalità schermo intero?
Cole Campbell,

le chiamate catchup avvengono solo su IsFixedTimeStep=true. Il grafico in basso mostra il tempo tra la fine del mio disegno e l'inizio della chiamata di aggiornamento del fotogramma successivo. I frame non vengono eliminati, chiamo i metodi di disegno e pago il prezzo della CPU per essi (grafico in alto). Stesso comportamento a schermo intero e tra risoluzioni.
Darcara,

Hai ragione, errore mio. Temo di aver esaurito le mie idee a questo punto.
Cole Campbell,

2

Penso che tu abbia un problema di immondizia ... forse stai creando / distruggendo molti oggetti e che i picchi sono la routine del garbage collector che funziona ...

assicurati di riutilizzare tutte le tue strutture di memoria ... e non usare 'nuovo' troppo spesso


Già verificato che in ProcessExplorer e CLRProfiler, e il gc funziona come una volta ogni 10 secondi e non fino a 75ms.
Darcara,

1
Sicuramente non vuoi creare un nuovo RasterizerState, BlendState e DepthStencilState ogni frame indipendentemente dal fatto che sia o meno la causa del rallentamento del rendering. Sicuramente non aiuterà, come da questo articolo blogs.msdn.com/b/shawnhar/archive/2010/04/02/… Dovresti creare lo stato che userai una volta al caricamento e riapplicarli quando necessario.
dadoo Games,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.