Jąkanie się XNA w regularnych odstępach czasu


10

Próbuję wykonać instancję sprzętową, ale napotykam dziwny problem z wydajnością. Średnia liczba klatek na sekundę wynosi około 45, ale jest bardzo niestabilna.

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

Poniższy obraz pokazuje mój zmierzony czas (z Stopwatch). Najwyższy wykres to czas spędzony w Drawmetodzie, a dolny wykres to czas od końca Drawdo początkuUpdate Rysowanie i czas xna

Skoki są prawie dokładnie w odległości 1 sekundy i zawsze wynoszą 2,3,4 lub 5 razy więcej niż zwykle. Ramki bezpośrednio po kolcu nie zajmują w ogóle czasu. Sprawdziłem, że to nie jest śmieciarz.

Obecnie tworzę siatkę składającą się z 12 trójkątów i 36 wierzchołków jako listę trójkątów (wiem, że nie jest optymalna, ale służy tylko do testowania) z 1 milionem wystąpień. Jeśli wsadowo wywołam losowanie instancji w małych częściach po 250 wystąpień, problem zostanie złagodzony, ale użycie procesora znacznie wzrośnie. Przebieg powyżej wynosi 10000 instancji na każde wywołanie losowania, co jest znacznie łatwiejsze na jednostce centralnej.

Jeśli uruchomię grę na pełnym ekranie, dolny wykres prawie nie istnieje, ale ten sam problem występuje teraz w Drawmetodzie.

Oto przegląd wewnątrz PIX , który nie ma dla mnie żadnego sensu. Wydaje się, że w przypadku niektórych klatek renderowanie nie jest wykonywane ...

Masz pomysł, co może być tego przyczyną?

EDYCJA : zgodnie z żądaniem, odpowiednie części kodu renderowania

A CubeBufferjest tworzony i inicjowany, a następnie wypełniany kostkami. Jeśli liczba kostek przekracza określony limit, CubeBuffertworzona jest nowa i tak dalej. Każdy bufor rysuje wszystkie wystąpienia w jednym wywołaniu.

Informacje potrzebne tylko raz to static(wierzchołek, bufor indeksu i deklaracja wierzchołków; jak dotąd nie ma to znaczenia). Tekstura ma wymiary 512 x 512

Remis()

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

Nie jestem pewien, czy dotyczy to czasu od zakończenia losowania do początku aktualizacji, ponieważ nie są one silnie powiązane (tzn. Wiele aktualizacji może nastąpić między 2 losowaniami, jeśli gra działa wolno, co musi być prawdą, ponieważ nie jest uruchomiony przy 60 fps). Mogą nawet działać w osobnych wątkach (ale nie jestem tego pewien).
Zonko

Nie mam prawdziwej wskazówki bankomatu, ale jeśli działa z mniejszym pakietowaniem, to najwyraźniej problem z twoim kodem wsadowym, opublikuj odpowiedni kod XNA i HLSL, abyśmy mogli przyjrzeć mu się bliżej @Zonko z IsFixedTimeStep = Fałsz jest aktualizacja 1: 1 / losowanie połączeń
Daniel Carlsson

Oto wyjaśnienie, dlaczego to jąkanie występuje od Shawna Hargreavesa (w zespole xna): forums.create.msdn.com/forums/p/9934/53561.aspx#53561
NexAddo

Odpowiedzi:


3

Zgaduję, że twoja wydajność jest związana z GPU. Po prostu prosisz urządzenie graficzne, aby wykonało więcej pracy na jednostkę czasu niż jest w stanie obsłużyć; 36 milionów wierzchołków na ramkę to całkiem przyzwoita liczba, a instancja sprzętowa może faktycznie zwiększyć ilość pracy przetwarzania niezbędną po stronie układu GPU równania. Narysuj mniej wielokątów.

Dlaczego zmniejszenie wielkości partii rozwiązuje problem? Ponieważ powoduje to, że procesor przetwarza ramkę dłużej, co oznacza, że ​​spędza mniej czasu siedząc w środku, Present()czekając, aż GPU zakończy renderowanie. Tak myślę, że robi to podczas przerwy na końcu twoich Draw()połączeń.

Powód określonego czasu luk jest trudniejszy do odgadnięcia bez zrozumienia całego kodu, ale nie jestem pewien, czy to ważne. Wykonuj więcej pracy na procesorze lub mniej na GPU, aby obciążenie było mniej nierówne.

Więcej informacji można znaleźć w tym artykule na blogu Shawna Hargreavesa.


2
Jest to z pewnością związane z GPU. Aplikacja jest zasadniczo punktem odniesienia do odkrywania różnych metod rysowania. Mniejszy rozmiar partii z taką samą liczbą narysowanych wierzchołków zajęłoby dłużej procesor, ale obciążenie GPU powinno być takie samo, nie? Przynajmniej spodziewałbym się spójnego czasu między ramkami, w zależności od obciążenia (który wcale nie zmienia się między ramkami), a nie takich regularnych odstępów między opóźnieniami i natychmiastowymi (lub brak renderowania, patrz PIX).
Darcara,

Jeśli poprawnie interpretuję twoje wykresy, natychmiast renderowane ramki są częścią funkcjonalności XNA Framework. Przy IsFixedTimeStepustawieniu na false, jeśli gra działa zbyt wolno, XNA będzie dzwonić Update()wiele razy z rzędu, aby nadrobić zaległości, celowo odrzucając ramki w tym procesie. Czy IsRunningSlowlyw tych ramkach jest ustawiona wartość true? Co do dziwnego wyczucia czasu - zastanawiam się trochę. Czy pracujesz w trybie okienkowym? Czy zachowanie utrzymuje się w trybie pełnoekranowym?
Cole Campbell

połączenia wychodzące odbywają się tylko w dniu IsFixedTimeStep=true. Dolny wykres pokazuje czas między końcem mojego losowania a początkiem wywołania aktualizacji następnej ramki. Ramki nie są upuszczane, wywołuję metody losowania i płacę za nie cenę procesora (górny wykres). To samo zachowanie na pełnym ekranie i we wszystkich rozdzielczościach.
Darcara,

Masz rację, mój błąd. Obawiam się, że w tym momencie wyczerpałem swoje pomysły.
Cole Campbell

2

Myślę, że masz problem ze śmieciami ... może tworzysz / niszczysz wiele obiektów i że skoki są rutynową funkcją śmieciarek ...

pamiętaj, aby ponownie użyć wszystkich struktur pamięci ... i nie używaj zbyt często nowych


Sprawdziłem już to w ProcessExplorer i CLRProfiler, a gc działa jak co 10 sekund i nie prawie tak długo, jak 75ms.
Darcara,

1
Na pewno nie chcesz tworzyć nowego RasterizerState, BlendState i DepthStencilState dla każdej klatki, niezależnie od tego, czy jest to przyczyną spowolnienia renderowania. To na pewno nie pomoże, zgodnie z tym artykułem blogs.msdn.com/b/shawnhar/archive/2010/04/02/ ... Powinieneś stworzyć stan, którego będziesz używał raz przy ładowaniu i zastosować go ponownie, jeśli zajdzie taka potrzeba.
dadoo Games
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.