Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

11

08.04.2016, 14:57

Zitat von »[Anonymer Benutzer«

]Aber es könnte auch am Zeichnen liegen. SFML macht ja kein Spritebatching, das bedeutet dass jeder Partikel einzeln gezeichnet wird, anstatt in einem Rutsch. Dadurch gibt es halt Abertausende von einzelnen Zeichnenaufrufen.

Da sw1 ein VertexArray verwendet, werden alle Partikel auf einmal gerendert. Das Problem liegt also vermutlich woanders.

@sw1
Hast du mal versucht, die Partikel in-place zu aktualisieren (wie z.B. in diesem offiziellen Tutorial) anstatt das Array vor jedem Rendern zu leeren und neu zu befüllen? Ich könnte mir vorstellen, dass das eine Verbesserung bewirkt.

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

12

08.04.2016, 15:36

SFML macht ja kein Spritebatching, das bedeutet dass jeder Partikel einzeln gezeichnet wird, anstatt in einem Rutsch.
Ich habe Dir schon mehrfach gesagt, dass das nicht stimmt. SFML kennt sehr wohl VertexArrays und die wurden hier auch verwendet. Da wird eben nicht jeder Partikel einzeln gezeichnet, sondern alles auf einmal. Genau wie andere Frameworks das mit SpriteBatching auch machen. Ich wäre Dir also sehr dankbar, wenn Du endlich aufhören würdest solchen Unfug zu verbreiten.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

buggypixels

Treue Seele

Beiträge: 125

Wohnort: Meerbusch

Beruf: Programmierer

  • Private Nachricht senden

13

08.04.2016, 17:08

Der Code ist für diesen Anwendungsfall in vielen Bereichen äußerst ineffizient.
Als erstes würde ich für solche Fälle eher den StructOfArray Ansatz verwenden, statt
einem ArrayOfStruct (std::dequeue).
Also eher:

C-/C++-Quelltext

1
2
3
4
struct Particles {
  v2* positions;
  float* timers;
};

Das ist dann schon eher Cache friendly. Außerdem würde ich beim aktualisieren des Lebenszyklus die "toten" Partikel
nicht löschen sondern mit dem letzten lebenden in dem Array "swappen" und dann die Anzahl runtersetzen.
All das dürfte schon wesentlich mehr bringen.
Also bei der heutigen Hardware sind 10.000 Partikel ehrlich gesagt ein Kinderteller und das ist egal ob mit DiectX, openGL, SFML, SDL
oder was auch immer. Bei 1 Millionen wird es erst interessant.

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

14

08.04.2016, 22:20

std::vector anstatt std::deque verwenden. (Besonders, wenn du, wie bereits von buggypixels vorgeschlagen, die Partikel nicht mehr alle verschiebst)
Anstatt std::time direkt die Zahl vergangener Mikrosekunden verwenden.

Ansonsten bieten sich die "üblichen" Optimierungen an. Partikelsysteme lassen sich für gewöhnlich ausgesprochen gut parallelisieren und vektorisieren. Der Tipp auf Cache Effizienz ist auch sehr wichtig.

Ich kann auch bestätigen, dass 10 000 einfach bloß extrem wenig ist. Insbesondere in Hinblick auf die wirklich wenige Funktionalität. Ich denke, dass das Problem irgendwie mit SFML zusammenhängt und einige der verwendeten Funktionen sehr langsam ablaufen. Zum Beispiel sf::time. Der Tipp von Endgegner scheint auch gut. Im Zweifelsfall einfach mal profilen, das ist immer eine gute Idee um sich bei Performance-Problemen einen Überblick zu verschaffen.

David Scherfgen

Administrator

Beiträge: 10 382

Wohnort: Hildesheim

Beruf: Wissenschaftlicher Mitarbeiter

  • Private Nachricht senden

15

14.04.2016, 00:01

@Thread-Ersteller:
Hast du denn mal einen Profiler benutzt und geschaut, was genau wie viel Zeit benötigt?
Nicht dass du an der falschen Stelle optimierst.

sw1

Frischling

  • »sw1« ist der Autor dieses Themas
  • Private Nachricht senden

16

17.04.2016, 12:07

Ich habe in den letzten Tagen versucht die ganzen Vorschläge auszuprobieren.
Zunächst einmal ist das Zeichnen kein Problem, hier geht es bis ca. zwei Millionen Partikel mit 60 FPS.
Das Problem lag bei den Updates der Partikel. Ich habe jetzt versucht mein Partikel System so zu ändern wie es buggypixels / Spiele Programmierer vorgeschlagen haben. Aktuell komme ich auf 500 000 Partikel bei ca. 30 FPS, hierbei verschwinden die Partikel langsam (alpha-Wert geht gegen Null) und sie haben eine Geschwindigkeit, bewegen sich also.
Ich denke das sieht schonmal ganz gut aus.

Jetzt nochmal ein neues Problem, bzw. Frage:
Ich habe die einzelnen Werte der Partikel nun direkt in C-Arrays gespeichert (siehe den Code unten). Wenn ich stattdessen einen std::vector benutze wird alles extrem langsam. Wenn ich z.B.

C-/C++-Quelltext

1
2
3
4
for (int i = 0; i < 10000; i++)
{
   if (v[i]);
}

, mit "std::vector<bool> v", in jedem Frame durchlaufe, falle ich sofort auf unter 30 FPS. Wenn v hingegen ein C-Array ist hat dies fast keine Auswirkungen auf die Framerate, auch wenn z.B. eine Millionen Einträge durchlaufen werden.

Woran liegt es, dass std::vector im Vergleich zum C-Array so extrem langsam ist?


Hier nun mal mein neues Partikel System:

Header:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <SFML\Graphics.hpp>
#include "Resources.hpp"


#define particleNumber 100000

struct particles
{
    enum Type
    {
        energy,
        airSplit,
        smoke,
        fire,
        typeCount
    };


    particles() { for (int i = 0; i < particleNumber; i++) isActive[i] = false; }

    bool isActive[particleNumber];
    float leftLifeTime[particleNumber];
    float xVelocity[particleNumber];
    float yVelocity[particleNumber];
};


class particleSystem : public sf::Drawable
{
public:
    explicit particleSystem(particles::Type type);

    void update(sf::Time timePerFrame);
    void addParticle(float xPos, float yPos, float xDirectionVelocity, float yDirectionVelocity);
    unsigned int getParticleCount() const;


private:
    void draw(sf::RenderTarget& target, sf::RenderStates states) const override;

private:
    particles::Type mType;
    float particleLifetime;
    const sf::Texture* mTexture;
    float xHalfTextureSize;
    float yHalfTextureSize;

    unsigned int nextFreeParticle;

    particles mParticles;
    sf::Vertex vertices[4 * particleNumber];
};


cpp Datei:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#include "particleSystem.hpp"



particleSystem::particleSystem(particles::Type type)
    :
    mType(type),
    mTexture(nullptr),
    nextFreeParticle(0)
{
    switch (type)
    {
    case particles::energy:
    {
        particleLifetime = 15.f;
        mTexture = &Resources::get()->getResource(Holder::particles, Texture::energy);
        break;
    }
    case particles::fire:
    {
        particleLifetime = 0.3f;
        mTexture = &Resources::get()->getResource(Holder::particles, Texture::fire);
        break;
    }
    case particles::smoke:
    {
        particleLifetime = 1.6f;
        mTexture = &Resources::get()->getResource(Holder::particles, Texture::smoke);
        break;
    }
    case particles::airSplit:
    {
        particleLifetime = 0.8f;
        mTexture = &Resources::get()->getResource(Holder::particles, Texture::airSplit);
        break;
    }
    default:
    {
        assert(false);
    }
    }

    xHalfTextureSize = (sf::Vector2f(mTexture->getSize()) / 2.f).x;
    yHalfTextureSize = (sf::Vector2f(mTexture->getSize()) / 2.f).y;

    for (int i = 0; i < 4 * particleNumber; i += 4)
    {
        vertices[i].texCoords = sf::Vector2f(0, 0);
        vertices[i + 1].texCoords = sf::Vector2f(mTexture->getSize().x, 0);
        vertices[i + 2].texCoords = sf::Vector2f(mTexture->getSize().x, mTexture->getSize().y);
        vertices[i + 3].texCoords = sf::Vector2f(0, mTexture->getSize().y);
    }
}


void particleSystem::update(sf::Time timePerFrame)
{
    float dt = timePerFrame.asSeconds();
    float xPos = 0;
    float yPos = 0;
    float alpha = 0;

    for (int i = 0; i < particleNumber; i++)
    {
        if (mParticles.isActive[i])
        {
            mParticles.leftLifeTime[i] -= dt;
            if (mParticles.leftLifeTime[i] <= 0)
            {
                mParticles.isActive[i] = false;

                vertices[4 * i].color.a = 0;
                vertices[4 * i + 1].color.a = 0;
                vertices[4 * i + 2].color.a = 0;
                vertices[4 * i + 3].color.a = 0;
            }
            else
            {
                xPos = vertices[4 * i].position.x + xHalfTextureSize;
                yPos = vertices[4 * i].position.y + yHalfTextureSize;
                xPos += mParticles.xVelocity[i] * dt;
                yPos += mParticles.yVelocity[i] * dt;

                vertices[4 * i].position.x = xPos - xHalfTextureSize;
                vertices[4 * i].position.y = yPos - yHalfTextureSize;
                vertices[4 * i + 1].position.x = xPos + xHalfTextureSize;
                vertices[4 * i + 1].position.y = yPos - yHalfTextureSize;
                vertices[4 * i + 2].position.x = xPos + xHalfTextureSize;
                vertices[4 * i + 2].position.y = yPos + yHalfTextureSize;
                vertices[4 * i + 3].position.x = xPos - xHalfTextureSize;
                vertices[4 * i + 3].position.y = yPos + yHalfTextureSize;


                alpha = mParticles.leftLifeTime[i] / particleLifetime * 255.f;

                vertices[4 * i].color.a = alpha;
                vertices[4 * i + 1].color.a = alpha;
                vertices[4 * i + 2].color.a = alpha;
                vertices[4 * i + 3].color.a = alpha;
            }
        }
    }
}


void particleSystem::addParticle(float xPos, float yPos, float xDirectionVelocity, float yDirectionVelocity)
{
    mParticles.isActive[nextFreeParticle] = true;
    mParticles.leftLifeTime[nextFreeParticle] = particleLifetime;
    mParticles.xVelocity[nextFreeParticle] = xDirectionVelocity;
    mParticles.yVelocity[nextFreeParticle] = yDirectionVelocity;

    vertices[4 * nextFreeParticle].position.x = xPos - xHalfTextureSize;
    vertices[4 * nextFreeParticle].position.y = yPos - yHalfTextureSize;
    vertices[4 * nextFreeParticle + 1].position.x = xPos + xHalfTextureSize;
    vertices[4 * nextFreeParticle + 1].position.y = yPos - yHalfTextureSize;
    vertices[4 * nextFreeParticle + 2].position.x = xPos + xHalfTextureSize;
    vertices[4 * nextFreeParticle + 2].position.y = yPos + yHalfTextureSize;
    vertices[4 * nextFreeParticle + 3].position.x = xPos - xHalfTextureSize;
    vertices[4 * nextFreeParticle + 3].position.y = yPos + yHalfTextureSize;

    vertices[4 * nextFreeParticle].color.a = 255;
    vertices[4 * nextFreeParticle + 1].color.a = 255;
    vertices[4 * nextFreeParticle + 2].color.a = 255;
    vertices[4 * nextFreeParticle + 3].color.a = 255;

    nextFreeParticle++;
    if (nextFreeParticle >= particleNumber) nextFreeParticle = 0;
}


unsigned int particleSystem::getParticleCount() const
{
    unsigned int count = 0;
    for (int i = 0; i < particleNumber; i++) if (mParticles.isActive[i]) count++;
    return count;
}


void particleSystem::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
    states.texture = mTexture;
    target.draw(&vertices[0], 4 * particleNumber, sf::PrimitiveType::Quads, states);
}



Noch ein Paar Kommentare zu euren Vorschlägen:

Zitat

So wie ich das sehe, haben...

...die Liste dann so aus: [150, 100, 500]

Hoffe ich konnte helfen ;)


Das habe ich auch versucht, hatte auch gut funktioniert (auch mit std::vector). Das Problem ist nur, dass ich dann die Partikel ja nicht mehr durchlaufe, also kann ich die Partikel nicht langesam verschwinden lassen (alpha-Wert langsam gegen Null gehen lassen) und auch nicht bewegen. Ansonsten finde ich den Vorschlag recht schön ;) .

Zitat

@sw1
Hast du mal versucht, die Partikel in-place zu aktualisieren (wie z.B. in diesem offiziellen Tutorial) anstatt das Array vor jedem Rendern zu leeren und neu zu befüllen? Ich könnte mir vorstellen, dass das eine Verbesserung bewirkt.


Ich meine das hat nichts bewirkt, es liegt, soweit ich das gesehen habe, nur am std::vector (ich benutze jetzt auch direkt sf::Vertex vertices[4 * particleNumber] anstatt den sf::VertexArray).

Zitat


@Thread-Ersteller:
Hast du denn mal einen Profiler benutzt und geschaut, was genau wie viel Zeit benötigt?
Nicht dass du an der falschen Stelle optimierst.


Ich habe sowas bisher noch nie benutzt, werde ich mir evtl. bei Gelegenheit mal ansehen.

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »sw1« (17.04.2016, 12:16)


Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

17

17.04.2016, 13:32

std::vector<bool> ist bekanntermaßen "kaputt". Das liegt daran, dass std::vector<bool> die Daten intern ungefragt als Bitfeld ablegt. Besonders durch Interaktion mit anderen Optimierungen ist das wahrscheinlich für die schlechte Performance verantwortlich. Um das zu umgehen, könntest du einen std::vector<char> verwenden. Aus einem "normalen" vector (also alles außer bool) kannst du mit data() sogar einen direkten Zeiger auf die Daten erhalten. Das ist dann beim Zugriff das selbe wie ein Raw Array. (Von Aliasing Problemen mal abgesehen, aber das ist eine andere Geschichte und der std::vector ist da unschuldig.)

Wenn du die Performance weiter steigern willst, gibt es übrigens noch unzählige Dinge zu optimieren.
Ein paar Anmerkungen vom drüberfliegen:
  1. Anstatt Partikel zu aktivieren und deaktivieren wäre es viel besser, wenn du die "leeren" Partikel aus der Liste entfernst. Die Verzweigung kannst du dir dann sparen und wenn viele Partikel deaktiviert sind, musst du nicht die ganze Liste durchgehen. Dann lärmt der CPU Lüfter nicht ständig.
  2. Anstatt 4 Vertices zu updaten und an die Grafikkarte zu schicken, könntest du auch die Partikeldaten einfach direkt übermitteln und die Vertexdaten auf der Grafikkarte zum Beispiel mit einem Geometry Shader erzeugen. (Ich befürchte allerdings, dass SFML da wiedermal nix bietet.)
  3. Du kannst mehrere Threads zum Updaten verwenden - also parallelisieren. Für sich betrachtet ist auf einem typischen 4 Kernprozessor ziemlich genau eine Vervierfachung der Updategeschwinigkeit zu erwarten.
  4. "Mathematische Optimierungen": Divisionen sind bekanntermaßen deutlich langsamer als alle anderen Operationen. Die eine Division einschließlich Multiplikation ist außerdem total unnötig. Du kannst vor dem Eintritt in die Schleife einmal 255.0f / particleLifetime rechnen und das damit multiplizieren. Dann hast du anstatt einer Division mit anschließender Multiplikation nur noch eine Multiplikation. Ein paar Rechnungen machst du auch doppelt (xPos + xHalfTextureSize und so). Der Compiler kann das zwar in der Theorie erkennen und deduplizieren, aber in der Praxis ist das immer so eine Sache. Der Compiler ist ganz schnell mal verwirrt mit dem Aliasing und macht es dann doch nicht. Also am besten im performancekritischen Code alle Rechnungen explizit nur einmal durchführen und die Ergebnisse wiederverwenden.
  5. Die Variablen erst bei der Verwendung deklarieren: Variablen mit langer Lebenszeit verwirren manchmal den Compiler.
  6. Falls du für x86 kompilierst, aktiviere mal SSE2. (MSVC, Clang/GCC: -msse2) Das haben so gut wie alle x86 Prozessoren seit mehr als einem Jahrzehnt.
  7. Du kannst die Partikelverarbeitung vektorisieren. Prinzipiell gibt es da zwei Möglichkeiten: Du schreibst den Code so, dass ihn der Autovektorisierer (MSVC, Clang, GCC) versteht, also der Compiler ihn für dich vektorisiert - oder du verwendest Intrinsics. So wie der Code aussieht, dürfte ersteres allerdings etwas schwierig werden. Große Branches kannst du vergessen und Strukturzugriffe werden meiner Erfahrung nach von heute üblichen Compilern so gut wie nicht vektorisiert. Für x86/x64 Intrinsics gibt es eine hervorrage Übersicht von Intel:
    https://software.intel.com/sites/landing…#techs=SSE,SSE2

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Spiele Programmierer« (17.04.2016, 14:12)


TrommlBomml

Community-Fossil

Beiträge: 2 117

Wohnort: Berlin

Beruf: Software-Entwickler

  • Private Nachricht senden

18

17.04.2016, 13:42

Anstatt 4 Vertices zu updaten und an die Grafikkarte zu schicken, könntest du auch die Partikeldaten einfach direkt übermitteln und die Vertexdaten auf der Grafikkarte zum Beispiel mit einem Geometry Shader erzeugen. (Ich befürchte allerdings, dass SFML da wiedermal nix bietet.)


Hat SFML noch nicht. Allerdings müsste man hier schauen, ob dann das Bottleneck nicht auf die Grafikkarte wandert. Soweit ich an mehreren Stellen gelesen habe, sind Geometryshader ziemlich lahm, daher wird er auch AFAIK nicht so oft verwendet. Aber einen Versuch ist es definitiv wert.

Ansonsten denke ich, die ganzen Punkte vom Spiele Programmierer sind es auf jeden Fall wert, einzuarbeiten und neu zu messen.

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

19

17.04.2016, 14:04

Der Einwand ist berechtigt. Das habe ich auch schon öfter gehört.
Ich verwende den Geometry-Shader ganz gern für solche Zwecke und meiner persönlichen Erfahrung nach war der Geometry Shader durch GPU-Power aber im Vergleich immernoch ziemlich schnell.

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

20

17.04.2016, 18:02

SFML kann Geometry Shader. Sogar schon eine Weile:
https://github.com/SFML/SFML/issues/428
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Werbeanzeige