Maxim
Maxim

Reputation: 317

Performance problems while rendering in DirectX

In my project I display a lot of spheres.

The device

To display the spheres I load a file with some values. Thus it could be 1600 spheres. Now I get a performance problem while rendering... :(

In this part I initialize my device object:

        try
        {
            meshList = new List<Sphere>();

            // Erstellt die PresentParameters für weitere Einstellungen des Device
            PresentParameters presParams = new PresentParameters()
            {
                Windowed = true,                            // Device nur innerhalbe des Fensterhandels benutzen
                SwapEffect = SwapEffect.Discard,            // Grafikkarte entscheidet selbst wie sie den Backbuffer zur anzeige bringt
                EnableAutoDepthStencil = true,              // Boolean zum Merken der Tiefe
                AutoDepthStencilFormat = DepthFormat.D16    // Format der Tiefe
            };

            // Erzeugt eine Instanz von dem Device
            device = new Device(0,                                      // Nummer fuer den Grafikadapter der verwendet wird                  
                                DeviceType.Hardware,                    // Parameter über die Garfikkarte oder CPU ausführen
                                panel1,                 // Fensterhadel für das Device 
                                CreateFlags.HardwareVertexProcessing,   // Einstellung des Device. Gibt an, dass die Vertices nur per Software verarbeitet werden 
                                presParams);                            // Gibt die weiteren Einstellungen mit

            // Wenn das Device neupositioniert wird
            device.DeviceReset += new System.EventHandler(this.OnResetDevice);
            // Führt das Reset aus
            OnResetDevice(device, null);

            // Definiert keine Vor und Rückseite
            device.RenderState.CullMode = Cull.Clockwise;
            // Direct3D-Beleuchtung deaktivieren
            device.RenderState.Lighting = false;
            // Beschreibt einen festen Füllmodus
            device.RenderState.FillMode = FillMode.Solid;

            // Erstellt den Buffer für die Vertices (Lab Koordinatensystem)
            vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionColored),   // Typ der Vertices
                                            18,                                     // Anzahl der Vertices
                                            device,                                 // Gerätekontext unser device
                                            0,                                      // Anzahl der Flags zur Verarbeitung der Vertice
                                            CustomVertex.PositionColored.Format,    // Typ der Vertices (Weil man auch eigene Strukturen definieren kann)
                                            Pool.Default);                          // Speicherung der Vertices

            // Event welches aufgerufen wird wenn der Vertexbuffer erstellt wurde
            vertexBuffer.Created += new System.EventHandler(this.OnCreateVertexBuffer);
            // Event wird von Hand aufgerufen
            this.OnCreateVertexBuffer(vertexBuffer, null);

            return true;    // Device wurde erstellt
        }
        catch { return false; } // Device konnte nicht erstellt werden 

In this part I render all vertices:

    public void Render()
    {
        // Fragt ob das Device erstellt wurde und noch gültig ist
        if (device == null)
            return;

        // Inhalt des Backbuffers löschen und das ganze mit einer Farbe einfärben
        device.Clear(ClearFlags.Target | ClearFlags.ZBuffer,    // Die entsprechende Oberfläche
                     System.Drawing.Color.Black,                // Die Farbe 
                     1.0f,                                      // Abstand vom Betrachter, an dem die Oberfläche gelöscht wird und einen Wert, ...
                     0);                                        // ...der in jedem Stencil-Buffer-Eintrag gespeichert wird.

        // Anfang der Szene
        device.BeginScene();
        // Matrizen aufsetzen
        SetupMatrices();

        // Bindet den Buffer an das Device
        device.SetStreamSource(0,           // Nummer des Streams
                               vertexBuffer,// Der Buffer
                               0);          // StartOffset in dem Buffer

        // Teilt dem Device das Format der Vertices mit
        device.VertexFormat = CustomVertex.PositionColored.Format;
        // Zeichnet die Dreiecke
        device.DrawPrimitives(PrimitiveType.LineList,   // Typ der Primitive
                              0,                        // Eintrag des ersten Vertex
                              3);                       // Anzahl der Primetive

        // Zeichnet jedes einzelne Sphere
        foreach (Sphere mesh in meshList)
        {
            mesh.labMesh.DrawSubset(0);
        }

        // Ende der Szene
        device.EndScene();
        // Bringt die Zeichnung auf das Fensterhandle
        device.Present();
    }

And this is the class to create each sphere:

/// <summary>
/// Die Klasse Sphere
/// </summary>
public class Sphere
{
    // Radius der Kugel
    private const float radius = 4f;
    // Die Anzahl der Ebenen einer Kugel
    private const int slices = 40;
    // Die Anzalh der Flächen einer Ebene
    private const int stacks = 40;

    // Das Mesh zum Darstellen der Kugel
    private Mesh mesh = null;
    private Vector3 vec;
    public Vector3 min;
    public Vector3 max;


    /// <summary>
    /// Gibt den Mesh zurück
    /// </summary>
    public Mesh labMesh
    {
        get { return mesh; }
    }

    public Vector3 labVector 
    {
        get { return vec; }
    }

    /// <summary>
    /// Erstellt das Mesh
    /// </summary>
    /// <param name="device">Das 3D Device</param>
    /// <param name="color">Die Farbe der Kugel</param>
    /// <param name="labValues">Die Lab Werte der Kugel</param>
    public void createMesh(Device device, Color color, params float[] labValues)
    {
        // Erstellt die Kugel mit der Anbindung an das Device
        mesh = Mesh.Sphere(device, radius, slices, stacks);
        // Kopiert das Mesh zum Erstellen des VertexArrays
        Mesh tempMesh = mesh.Clone(mesh.Options.Value, Vertex.FVF_Flags, device);
        // Erstellt den VertexArray
        Vertex[] vertData = (Vertex[])tempMesh.VertexBuffer.Lock(0, typeof(Vertex), LockFlags.None, tempMesh.NumberVertices);

        // Weist jedem Vertex die Farbe und die Position zu
        for (int i = 0; i < vertData.Length; ++i)
        {
            vertData[i].color = color.ToArgb();
            vertData[i].x += labValues[1];
            vertData[i].y += labValues[0] - 50f;
            vertData[i].z += labValues[2];
        }
        min = new Vector3(labValues[1], labValues[0] + 100f, labValues[2]);
        max = new Vector3(labValues[1], labValues[0] - 100f, labValues[2]);

        // Gibt den VertexBuffer in der Kopie frei
        tempMesh.VertexBuffer.Unlock();
        // Löscht den Mesh aus dem Speicher
        mesh.Dispose();
        // Legt die Kopie in der Meshinstanz ab
        mesh = tempMesh;

        Vector3 v = new Vector3(labValues[1], labValues[0], labValues[2]);
        vec = v;
    }
}

/// <summary>
/// Vertex für die Kugel
/// </summary>
struct Vertex
{
    public float x, y, z; // Position of vertex in 3D space
    public int color;     // Diffuse color of vertex

    /// <summary>
    /// Konstruktor der Vertex
    /// </summary>
    /// <param name="_x">X(A) - Position</param>
    /// <param name="_y">Y(L) - Position</param>
    /// <param name="_z">Z(B) - Position</param>
    /// <param name="_color">Die Farbe</param>
    public Vertex(float _x, float _y, float _z, int _color)
    {
        x = _x; y = _y; z = _z;
        color = _color;
    }

    // Das Format des Vertex
    public static readonly VertexFormats FVF_Flags = VertexFormats.Position | VertexFormats.Diffuse;
}

I don't have any idea how to improve the performance by rendering 1600 spheres! I think in games must be a solution too.

I hope you have an idea to hepl me!

Upvotes: 1

Views: 1326

Answers (3)

MHGameWork
MHGameWork

Reputation: 636

In this example, your number of Draw Calls is the bottleneck, not your GPU performance. You should definitly reduce your number of draw calls. For a full high-end game, on PC the nubmer of draw calls rarely exceeds 2000, and this is in a very optimized pipeline. On laptops this is still a very high number. Aim at a maximum of 1000 draw calls when using C#.

To solve you problems there are some options.

First option is to put all your data into a single buffer. You should see your vertex buffers as chunks of data you send to the gpu, and you want to send as few things to your gpu as possible, since the overhead here is massive. You should place all the spheres into a single buffer or a few buffers. This will resolve your performance issues. Merging static objects is common practice in game engines.

Second option is, if you need movable spheres, you can use instancing. Instancing is a technique where you can render multiple times the same data, with some extra data per instance. This requires only one draw call. Instancing is now generally supported by all GPU's, so use this if you need moving objects, or parameterized objects. A quick google will surely provide more info.

Last note, like already mentioned, managed directx has been dead for years. It is slow and only dx9. You should switch to SlimDX when using c# (XNA is dead too).

Upvotes: 0

Goz
Goz

Reputation: 62323

First off I have to say that Managed DirectX is not support by Microsoft. You are far better off using something like XNA or better still SlimDX.

One method would be to only use one sphere and then set up a second vertex stream that contains matrix data. You can then render the spheres instanced with one, single, draw call. This should improve performance significantly.

Another method would be to build one giant vertex buffer with as many spheres as you can fit into it so you call DrawSubset less. This will improve performance.

That said 1600 draw calls a frame is high but isn't that significant so it should be possible to get decent performance as is.

A few things to try would be adding the following flags in your mesh.Clone call:

  1. WriteOnly
  2. OptimizeVertexCache
  3. VbShare

Also make sure alpha blending is turned off (it probably is but worth a try).

Ideally rendering your spheres in front to back order will optimise overfill (the number of times pixels are written too should be as low as possible) but this can often use more CPU than you save in GPU time.

Other things to bear in mind are how complex your spheres are. Can they have the number of tris reduced?

Beyond that using some sort of debugger as suggested by Caesar (never though i'd type that ;)) is a good way forward. It may just be that Managed DirectX is not performant enough to give you the results you are after ...

Upvotes: 2

Caesar
Caesar

Reputation: 9841

I would recommend running your code through a profiler and seeing where the bottle neck is in your code and optimize that.

Check this question out for what profiler there are out there for C#.

Upvotes: 2

Related Questions