XNA gagueira em intervalos regulares

10

Estou tentando instalar o hardware, mas estou encontrando algum problema de desempenho estranho. A taxa de quadros média é de cerca de 45, mas é extremamente instável.

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

A imagem abaixo mostra meu tempo medido (com Stopwatch). O gráfico superior é o tempo gasto no Drawmétodo e o gráfico inferior é o tempo desde o final Drawaté o início deUpdate Tempo de desenho e xna

Os picos têm quase exatamente um segundo de intervalo e sempre são 2,3,4 ou 5 vezes o tempo normal. Os quadros imediatamente após o pico não demoram nada. Eu verifiquei que não é o coletor de lixo.

Atualmente, estou instanciando uma malha que consiste em 12 triângulos e 36 vértices como uma lista de triângulos (eu sei que não é o ideal, mas é apenas para teste) com 1 milhão de instâncias. Se eu fizer um lote, as instâncias de chamadas de instanciamento em pequenas partes de 250 instâncias, cada um, o problema é aliviado, mas o uso da CPU aumenta significativamente. A execução acima é de 10000 instâncias por chamada de empate, o que é muito mais fácil na CPU.

Se eu rodar o jogo em tela cheia, o gráfico inferior é quase inexistente, mas o mesmo problema ocorre agora no Drawmétodo.

Aqui está uma corrida dentro do PIX , que não faz sentido para mim. Parece que para alguns quadros não há renderização feita ...

Alguma idéia, o que pode estar causando isso?

EDIT : conforme solicitado, as partes relevantes do código de renderização

A CubeBufferé criado e inicializado e preenchido com cubos. Se a quantidade de cubos estiver acima de um determinado limite, um novo CubeBufferserá criado e assim por diante. Cada buffer desenha todas as instâncias em uma chamada.

As informações necessárias apenas uma vez são static(vértice, buffer de índice e declaração de vértice; embora não faça diferença até agora). A textura é 512x512

Desenhar()

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
fonte
Não tenho certeza se é relevante o tempo desde o final do sorteio até o início da atualização, pois eles não estão fortemente vinculados (ou seja, muitas atualizações podem ocorrer entre dois sorteios se o jogo for lento, o que deve ser o caso, pois você não está executando a 60 qps). Eles podem até rodar em threads separados (mas não tenho certeza disso).
Zonko 29/07
Eu não tenho nenhuma pista real, mas se ele funcionar com lotes menores, aparentemente, é um problema com seu código de lote, publique o código XNA e HLSL relevante para que possamos analisá-lo mais de perto @Zonko com IsFixedTimeStep = False, há atualização 1: 1 / draw calls
Daniel Carlsson
Aqui está uma explicação para esta gagueira acontece de Shawn Hargreaves (na equipe XNA): forums.create.msdn.com/forums/p/9934/53561.aspx#53561
NexAddo

Respostas:

3

Suponho que seu desempenho seja vinculado à GPU. Você está simplesmente pedindo ao seu dispositivo gráfico que trabalhe mais por unidade de tempo do que é capaz de lidar; 36 milhões de vértices por quadro é um número bastante decente, e a instalação de hardware pode realmente aumentar a quantidade de trabalho de processamento necessário no lado da equação da GPU. Desenhe menos polígonos.

Por que reduzir o tamanho do lote faz com que o problema desapareça? Porque a CPU demora mais para processar um quadro, o que significa que está gastando menos tempo Present()aguardando a GPU concluir a renderização. É o que acho que está fazendo durante essa lacuna no final de suas Draw()ligações.

A razão por trás do momento específico das lacunas é mais difícil de adivinhar sem entender todo o código, mas também não tenho certeza de que seja importante. Faça mais trabalho na CPU ou menos na GPU, para que sua carga de trabalho seja menos desigual.

Veja este artigo no blog de Shawn Hargreaves para obter mais informações.

Cole Campbell
fonte
2
Definitivamente, é vinculado à GPU. O aplicativo é essencialmente uma referência, para explorar diferentes métodos de desenho. Um tamanho de lote menor com a mesma quantidade de vértices desenhados levaria mais tempo na CPU, mas a carga da GPU deveria ser a mesma, não? Pelo menos eu esperaria um tempo consistente entre os quadros, dependendo da carga (que não muda entre os quadros) e não intervalos regulares de atraso e instantâneo (ou nenhuma renderização, consulte PIX).
Darcara 30/07/12
Se estou interpretando seus gráficos corretamente, os quadros renderizados instantaneamente fazem parte da funcionalidade do XNA Framework. Com IsFixedTimeStepdefinido como false, se o jogo estiver rodando muito lentamente, o XNA chamará Update()várias vezes seguidas para recuperar o atraso, eliminando deliberadamente quadros no processo. É IsRunningSlowlydefinido como verdadeiro durante esses quadros? Quanto ao momento estranho - isso me faz pensar um pouco. Você está executando no modo janela? O comportamento persiste no modo de tela cheia?
Julio
as chamadas de captura acontecem apenas em IsFixedTimeStep=true. O gráfico inferior mostra o tempo entre o final do meu sorteio e o início da chamada de atualização do próximo quadro. Os quadros não são descartados, chamo os métodos de desenho e pago o preço da CPU por eles (gráfico superior). Mesmo comportamento em tela cheia e em resoluções.
Darcara 30/07/12
Você está certo, meu erro. Receio ter esgotado minhas idéias neste momento.
Cole Campbell
2

Eu acho que você tem um problema com o lixo ... talvez você esteja criando / destruindo muitos objetos e que os picos sejam a rotina do coletor de lixo funcionando ...

certifique-se de reutilizar todas as suas estruturas de memória ... e não use 'novo' com muita frequência

Blau
fonte
Já verifiquei isso no ProcessExplorer e no CLRProfiler, e o gc está sendo executado uma vez a cada 10 segundos e não tão longo quanto 75ms.
Darcara 29/07
1
Definitivamente, você não deseja criar um novo RasterizerState, BlendState e DepthStencilState a cada quadro, independentemente de ser ou não a causa do abrandamento da renderização. Definitivamente, não ajudará, de acordo com este artigo blogs.msdn.com/b/shawnhar/archive/2010/04/02/… Você deve criar o estado que usará uma vez ao carregar e reaplicá-los quando necessário.
Dadoo Games