Reputation: 317
In my project I display a lot of spheres.
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
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
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:
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
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