Tartamudeo de XNA a intervalos regulares

10

Estoy tratando de hacer instancias de hardware pero me encuentro con un extraño problema de rendimiento. La velocidad de fotogramas promedio es de alrededor de 45, pero es extremadamente entrecortada.

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

La imagen a continuación muestra mi tiempo medido (con Stopwatch). El gráfico superior es el tiempo empleado en el Drawmétodo y el gráfico inferior es el tiempo desde el final Drawhasta el comienzo deUpdate Draw y xna timing

Los picos están separados casi exactamente 1 segundo y siempre son 2,3,4 o 5 veces el tiempo habitual. Los cuadros que siguen inmediatamente a la espiga no tardan en absoluto. He comprobado que no es el recolector de basura.

Actualmente estoy instalando una malla que consta de 12 triángulos y 36 vértices como una lista de triángulos (sé que no es óptima, pero es solo para probar) con 1 millón de instancias. Si agrupo las llamadas de sorteo de instancias en pequeñas partes de 250 instancias cada una, el problema se alivia, pero el uso de la CPU aumenta significativamente. La ejecución anterior es de 10000 instancias por llamada de sorteo, que es mucho más fácil en la CPU.

Si ejecuto el juego en pantalla completa, el gráfico inferior es casi inexistente, pero el mismo problema ocurre ahora en el Drawmétodo.

Aquí hay una ejecución dentro de PIX , que no tiene ningún sentido para mí. Parece que para algunos cuadros no se realiza ninguna representación ...

¿Alguna idea de lo que podría estar causando esto?

EDITAR : según lo solicitado, las partes relevantes del código de representación

A CubeBufferse crea e inicializa, luego se llena con cubos. Si la cantidad de cubos está por encima de cierto límite, CubeBufferse crea uno nuevo , y así sucesivamente. Cada búfer dibuja todas las instancias en una llamada.

La información que se necesita solo una vez es static(vértice, búfer de índice y declaración de vértice; aunque hasta ahora no hace ninguna diferencia). La textura es 512x512

Dibujar()

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

CuboBuffer

[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
fuente
No estoy seguro de si es relevante desde el final del sorteo hasta el comienzo de la actualización, ya que no están fuertemente vinculados (es decir, muchas actualizaciones pueden ocurrir entre 2 sorteos si el juego se ejecuta lentamente, lo que debe ser el caso ya que no está ejecutando a 60 fps). Incluso podrían ejecutarse en hilos separados (pero no estoy seguro de esto).
Zonko
No tengo un cajero automático real, pero si funciona con lotes más pequeños, aparentemente es un problema con su código de lote, publique el código XNA y HLSL relevante para que podamos verlo más de cerca @Zonko con IsFixedTimeStep = False hay una actualización 1: 1 / dibujar llamadas
Daniel Carlsson
Aquí hay una explicación de por qué este tartamudeo ocurre de Shawn Hargreaves (en el equipo de xna): forums.create.msdn.com/forums/p/9934/53561.aspx#53561
NexAddo

Respuestas:

3

Supongo que su rendimiento está vinculado a la GPU. Simplemente le está pidiendo a su dispositivo gráfico que haga más trabajo por unidad de tiempo del que es capaz de manejar; 36 millones de vértices por cuadro es un número bastante decente, y las instancias de hardware en realidad pueden aumentar la cantidad de trabajo de procesamiento necesario en el lado de la GPU de la ecuación. Dibuja menos polígonos.

¿Por qué reducir el tamaño del lote hace que el problema desaparezca? Porque hace que la CPU tarde más en procesar un marco, lo que significa que pasa menos tiempo sentado Present()esperando a que la GPU termine de renderizarse. Eso es lo que creo que está haciendo durante esa brecha al final de sus Draw()llamadas.

La razón detrás del momento específico de las brechas es más difícil de adivinar sin comprender todo el código, pero tampoco estoy seguro de que sea importante. Haga más trabajo en la CPU, o menos trabajo en la GPU, para que su carga de trabajo sea menos desigual.

Consulte este artículo en el blog de Shawn Hargreaves para obtener más información.

Cole Campbell
fuente
2
Definitivamente está vinculado a la GPU. La aplicación es esencialmente un punto de referencia, para explorar diferentes métodos de dibujo. Un tamaño de lote más pequeño con la misma cantidad de vértices dibujados tomaría más tiempo en la CPU, pero la carga de GPU debería ser la misma, ¿no? Al menos esperaría un tiempo constante entre fotogramas, dependiendo de la carga (que no cambia entre fotogramas en absoluto) y no intervalos regulares de retraso e instantáneos (o sin renderizado, ver PIX).
Darcara
Si interpreto sus gráficos correctamente, los marcos renderizados instantáneamente son parte de la funcionalidad de XNA Framework. Con el IsFixedTimeStepajuste a false, si el juego se ejecuta muy lentamente, XNA llamará Update()varias veces seguidas para ponerse al día, dejando caer fotogramas deliberadamente en el proceso. ¿Se IsRunningSlowlyestablece en verdadero durante estos fotogramas? En cuanto al momento extraño, me hace pensar un poco. ¿Estás ejecutando en modo ventana? ¿El comportamiento persiste en modo de pantalla completa?
Cole Campbell
las llamadas de recuperación solo ocurren el IsFixedTimeStep=true. El gráfico inferior muestra el tiempo entre el final de mi sorteo y el comienzo de la llamada de actualización del siguiente cuadro. Los marcos no se descartan, llamo a los métodos de extracción y pago el precio de la CPU por ellos (gráfico superior). Mismo comportamiento en pantalla completa y en todas las resoluciones.
Darcara
Tienes razón, mi error. Me temo que he agotado mis ideas en este momento.
Cole Campbell
2

Creo que tiene un problema de basura ... tal vez está creando / destruyendo muchos objetos y que los picos son la rutina del recolector de basura que funciona ...

asegúrese de reutilizar todas sus estructuras de memoria ... y no use 'nuevo' con demasiada frecuencia

Blau
fuente
Ya lo comprobé en ProcessExplorer y CLRProfiler, y el gc se ejecuta como una vez cada 10 segundos y no tan largo como 75 ms.
Darcara
1
Definitivamente no desea crear un nuevo RasterizerState, BlendState y DepthStencilState en cada cuadro, independientemente de si es o no la causa de la desaceleración de la representación. Definitivamente no ayudará, según este artículo blogs.msdn.com/b/shawnhar/archive/2010/04/02/… Debería crear el estado que usará una vez en carga y volver a aplicarlos cuando sea necesario.
dadoo Games