Bégaiement XNA à intervalles réguliers

10

J'essaie de faire du matériel, mais je rencontre un problème de performance étrange. Le taux de rafraîchissement moyen est d'environ 45, mais il est extrêmement saccadé.

  • Fenêtre
  • SynchronizeWithVerticalRetrace = false
  • IsFixedTimeStep = false
  • PresentationInterval = PresentInterval.Immediate

L'image ci-dessous montre mon timing mesuré (avec Stopwatch). Le graphique supérieur représente le temps passé dans la Drawméthode et le graphique inférieur représente le temps écoulé entre la fin Drawet le début deUpdate Tirage au sort et chronométrage xna

Les pointes sont presque exactement à 1 seconde d'intervalle et sont toujours 2,3,4 ou 5 fois le temps habituel. Les images suivant immédiatement la pointe ne prennent pas de temps du tout. J'ai vérifié que ce n'est pas le garbage collector.

J'installe actuellement un maillage composé de 12 triangles et 36 sommets sous forme de liste de triangles (je sais que ce n'est pas optimal, mais c'est juste pour les tests) avec 1 million d'instances. Si je regroupe les appels de tirage instanciés en petites parties de 250 instances chacune, le problème est atténué, mais l'utilisation du processeur augmente considérablement. L'exécution ci-dessus est à 10000 instances par appel de tirage, ce qui est beaucoup plus facile sur le processeur.

Si je lance le jeu en plein écran, le graphique du bas est presque inexistant, mais le même problème se produit maintenant dans la Drawméthode.

Voici une course à l'intérieur de PIX , qui n'a aucun sens pour moi. Il semble que pour certaines images, aucun rendu ne soit effectué ...

Une idée, qu'est-ce qui pourrait causer ça?

EDIT : comme demandé, les parties pertinentes du code de rendu

A CubeBufferest créé et initialisé, puis rempli de cubes. Si le nombre de cubes est supérieur à une certaine limite, un nouveau CubeBufferest créé, etc. Chaque tampon dessine toutes les instances en un seul appel.

Les informations nécessaires une seule fois le sont static(sommet, tampon d'index et déclaration de sommet; bien que cela ne fasse aucune différence jusqu'à présent). La texture est 512x512

Dessiner()

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
la source
Je ne sais pas si c'est pertinent pour le temps de la fin du tirage au début de la mise à jour, car ils ne sont pas fortement liés (c'est-à-dire que de nombreuses mises à jour peuvent se produire entre 2 tirages si le jeu s'exécute lentement, ce qui doit être le cas puisque vous n'êtes pas en cours d'exécution). à 60 fps). Ils pourraient même fonctionner dans des threads séparés (mais je n'en suis pas sûr).
Zonko
Je n'ai pas vraiment d'indice atm, mais si cela fonctionne avec un traitement par lots plus petit, c'est apparemment un problème avec votre code de traitement par lots, postez le code XNA et HLSL pertinent afin que nous puissions le regarder de plus près @Zonko avec IsFixedTimeStep = Faux il y a une mise à jour 1: 1 / dessiner des appels
Daniel Carlsson
Voici une explication de la raison pour laquelle ce bégaiement se produit de Shawn Hargreaves (sur l'équipe xna): forums.create.msdn.com/forums/p/9934/53561.aspx#53561
NexAddo

Réponses:

3

Je suppose que vos performances sont liées au GPU. Vous demandez simplement à votre périphérique graphique de faire plus de travail par unité de temps qu'il ne peut en gérer; 36 millions de sommets par image est un nombre assez décent, et l'instanciation matérielle peut en fait augmenter la quantité de travail de traitement nécessaire du côté GPU de l'équation. Dessinez moins de polygones.

Pourquoi la réduction de la taille du lot fait-elle disparaître le problème? Parce que le processeur prend plus de temps pour traiter une image, ce qui signifie qu'il passe moins de temps à Present()attendre à l' intérieur du GPU pour terminer le rendu. C'est ce que je pense qu'il fait pendant cet écart à la fin de vos Draw()appels.

La raison derrière le timing spécifique des lacunes est plus difficile à deviner sans comprendre l'ensemble du code, mais je ne suis pas sûr que ce soit important non plus. Faites plus de travail sur le CPU, ou moins de travail sur le GPU, afin que votre charge de travail soit moins inégale.

Consultez cet article sur le blog de Shawn Hargreaves pour plus d'informations.

Cole Campbell
la source
2
Il est définitivement lié au GPU. L'application est essentiellement une référence, pour explorer différentes méthodes de dessin. Une taille de lot plus petite avec la même quantité de sommets dessinés prendrait plus de temps sur le CPU, mais la charge du GPU devrait être la même, non? Au moins, je m'attendrais à un temps cohérent entre les images, en fonction de la charge (qui ne change pas du tout entre les images) et pas de tels intervalles réguliers de décalage et instantané (ou pas de rendu, voir PIX).
Darcara
Si j'interprète correctement vos graphiques, les images rendues instantanément font partie des fonctionnalités de XNA Framework. Avec IsFixedTimeStepréglé sur false, si le jeu est trop lent, XNA appellera Update()plusieurs fois de suite pour rattraper son retard, laissant tomber délibérément cadres dans le processus. Est IsRunningSlowlydéfini sur true pendant ces images? Quant au timing étrange - cela me fait un peu me demander. Courez-vous en mode fenêtré? Le comportement persiste-t-il en mode plein écran?
Cole Campbell
les appels de rattrapage ne se produisent que le IsFixedTimeStep=true. Le graphique du bas montre le temps entre la fin de mon tirage et le début de l'appel de mise à jour de l'image suivante. Les frames ne sont pas abandonnées, j'appelle les méthodes de dessin et je paie le prix du CPU pour elles (graphique du haut). Même comportement en plein écran et dans toutes les résolutions.
Darcara
Tu as raison, mon erreur. J'ai peur d'avoir épuisé mes idées à ce stade.
Cole Campbell
2

Je pense que vous avez un problème d'ordures ... peut-être que vous créez / détruisez de nombreux objets et que les pointes sont la routine du ramasse-miettes qui fonctionne ...

assurez-vous de réutiliser toutes vos structures de mémoire ... et n'utilisez pas trop souvent de «nouveau»

Blau
la source
Déjà vérifié dans ProcessExplorer et CLRProfiler, et le gc fonctionne comme une fois toutes les 10 secondes et pas aussi longtemps que 75 ms.
Darcara
1
Vous ne voulez certainement pas créer un nouveau RasterizerState, BlendState et DepthStencilState à chaque image, que ce soit la cause ou non du ralentissement du rendu. Cela n'aidera certainement pas, selon cet article blogs.msdn.com/b/shawnhar/archive/2010/04/02/… Vous devez créer l'état que vous utiliserez une fois lors du chargement et les réappliquer si nécessaire.
dadoo Games