Ik hobby een particle engine in elkaar. Momenteel is het uploaden van de data naar de GPU een redelijke bottleneck.
Mijn particle struct welke naar de GPU wordt verzonden bestaat uit de volgende datastructuur:
Mijn GPU ondersteunt geen geometry shader. Dus ik genereer 4 van deze particles waarbij de positie identiek is, en de rotatie telkens 90° afwijkt. Op de GPU genereert hij vervolgens de uiteindelijke coördinaten met wat sin() en cos(). Hierdoor hoeft de CPU bij het genereren van de quads niet veel meer te doen dan wat data kopiëren. Wat overigens best zwaar is. ( ~50ms per frame @ 1mil. particles ). Dit kan natuurlijk asynchroon gebeuren, al is dat momenteel niet het geval.
We maken een testcase met 1 mil. particles. Een particle is 4*15 bytes, dus ~57MiB data totaal. We tekenen alle particles met een enkele glDrawArrays( GL_QUADS, 0, _itemCount ); call. Ik weet dat GL_QUADS officieel deprecated is. Maar als ik ooit een GPU tegen kom waar GL_QUADS niet werkt... dan wordt het hoog tijd om over te stappen op de geometry shader. De test voeren we uit met de CPU op een fixed kloksnelheid ( 2.0Ghz ) met het window op lage resolutie, zodat we zeker weten dat de GPU geen bottleneck is. Bij het testen bleef de GPU utilization onder de 10%.
Dit levert 16-18 fps op. De profiler toont dat het uploaden van de data (de effectieve glBufferData call) 10 ms duurt. Dit is alleen zo als de groote van het buffer elke frame hetzelfde is. Wanneer het aantal items elke frame met één stijgt keldert de framerate naar 5 fps. De profiler toont dat de call naar glBufferData dan tussen de 190 en 200ms duurt.
Ergo: glBufferData is baggertraag als het formaat van het VBO regelmatig verandert.
Waar resizeBuffer een enkele glBufferData( GL_ARRAY_BUFFER, sizeof( Particle ) * newSize, 0, GL_STREAM_DRAW ) call doet.
Dit levert 14-15 fps op als het buffer elke frame dezelfde grootte blijft. Het uploaden duurt telkens 25ms (al kan je in de profiler goed zien dat hij van die 25ms, eigenlijk maar 9ms echt aan het uploadden is. Het uploaden stallt om een of andere reden). De framerate komt dus in de buurt van de complete buffer vernieuwing, maar de tijd die de render thread bezig is is een stuk groter. Volgens mij is er sprake van een stall omdat de profiler dit tevoorschijn tovert als ik alleen de bewuste bufferSubData call selecteer:

Als de grootte van het buffer elke frame verandert levert dat 10-11 fps op. Bij profilen verandert er niets tov. de vorige testcase. Het tijdsverlies zal hem dan wel zitten in de aspecten van OpenGL die assynchroon gebeuren.
Concrete vraag: weet iemand snellere manieren om VBO's te 'streamen'? Bij setup #2 heb ik ook nog double buffering met geprobeerd, maar dat maakte geen enkel verschil. De GPU heeft geen enkele moeite om 1 mil. particles te renderen op postzegel formaat, maar de CPU of memory bus bottleneckt gewoon hard.
Ik vermoed dat het grootste probleem de stall bij het gebruiken van bufferSubData is. Maar de mogelijkheden die ik online kan vinden om het buffer te 'invalideren' hebben geen effect op de performance. Ik doel hierbij met name op het gebruiken van glBufferData met een nullptr als data ptr.
main loop:
i7-2820QM sandy bridge
Intel HD graphics 3000, OpenGL 3.1
Ik heb ook een nvidia NVS 4200 beschikbaar, maar die levert bij mijn testcases altijd lagere performance. Sowieso wordt nvidia optimus niet blij van buffersubdata... ik heb een half jaar terug een factor 7x memory overhead gemeten tussen nvidia optimus aan en uit bij het gebruik van bufferSubData.
Mijn particle struct welke naar de GPU wordt verzonden bestaat uit de volgende datastructuur:
C++:
1
2
3
4
5
6
| struct Particle { float x, y, z; unsigned char s, t; // uv coord unsigned char rotation; // rotatie 0-1 geschaald }; // = 15 bytes per stuk |
Mijn GPU ondersteunt geen geometry shader. Dus ik genereer 4 van deze particles waarbij de positie identiek is, en de rotatie telkens 90° afwijkt. Op de GPU genereert hij vervolgens de uiteindelijke coördinaten met wat sin() en cos(). Hierdoor hoeft de CPU bij het genereren van de quads niet veel meer te doen dan wat data kopiëren. Wat overigens best zwaar is. ( ~50ms per frame @ 1mil. particles ). Dit kan natuurlijk asynchroon gebeuren, al is dat momenteel niet het geval.
We maken een testcase met 1 mil. particles. Een particle is 4*15 bytes, dus ~57MiB data totaal. We tekenen alle particles met een enkele glDrawArrays( GL_QUADS, 0, _itemCount ); call. Ik weet dat GL_QUADS officieel deprecated is. Maar als ik ooit een GPU tegen kom waar GL_QUADS niet werkt... dan wordt het hoog tijd om over te stappen op de geometry shader. De test voeren we uit met de CPU op een fixed kloksnelheid ( 2.0Ghz ) met het window op lage resolutie, zodat we zeker weten dat de GPU geen bottleneck is. Bij het testen bleef de GPU utilization onder de 10%.
setup 1
We uploaden de bewuste VBO met een enkele:C++:
1
| glBufferData(GL_ARRAY_BUFFER, sizeof( data[0] ) * _itemCount, data.data(), GL_STREAM_DRAW); |
Dit levert 16-18 fps op. De profiler toont dat het uploaden van de data (de effectieve glBufferData call) 10 ms duurt. Dit is alleen zo als de groote van het buffer elke frame hetzelfde is. Wanneer het aantal items elke frame met één stijgt keldert de framerate naar 5 fps. De profiler toont dat de call naar glBufferData dan tussen de 190 en 200ms duurt.
Ergo: glBufferData is baggertraag als het formaat van het VBO regelmatig verandert.
setup 2
We alloceren met glBufferData() een buffer wat N keer zo groot is als vereist. Daarna kopiëren we elke frame de data in dat buffer met glBufferSubData(). Bij deze testcase gebruiken we N = 1.2fC++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| if ( _itemCount > _bufferSize ) { // upsize buffer when buffer is too small; resizeBuffer( _itemCount * 1.2 ); } else if ( ( _itemCount * 1.4 ) < _bufferSize ) { // downsize buffer when buffer is too large resizeBuffer( _itemCount * 1.2 ); } // upload data glBindBuffer(GL_ARRAY_BUFFER, _vbo); glBufferSubData( GL_ARRAY_BUFFER, 0, sizeof( data[0] ) * _itemCount, data.data() ); |
Waar resizeBuffer een enkele glBufferData( GL_ARRAY_BUFFER, sizeof( Particle ) * newSize, 0, GL_STREAM_DRAW ) call doet.
Dit levert 14-15 fps op als het buffer elke frame dezelfde grootte blijft. Het uploaden duurt telkens 25ms (al kan je in de profiler goed zien dat hij van die 25ms, eigenlijk maar 9ms echt aan het uploadden is. Het uploaden stallt om een of andere reden). De framerate komt dus in de buurt van de complete buffer vernieuwing, maar de tijd die de render thread bezig is is een stuk groter. Volgens mij is er sprake van een stall omdat de profiler dit tevoorschijn tovert als ik alleen de bewuste bufferSubData call selecteer:

Als de grootte van het buffer elke frame verandert levert dat 10-11 fps op. Bij profilen verandert er niets tov. de vorige testcase. Het tijdsverlies zal hem dan wel zitten in de aspecten van OpenGL die assynchroon gebeuren.
Concrete vraag: weet iemand snellere manieren om VBO's te 'streamen'? Bij setup #2 heb ik ook nog double buffering met geprobeerd, maar dat maakte geen enkel verschil. De GPU heeft geen enkele moeite om 1 mil. particles te renderen op postzegel formaat, maar de CPU of memory bus bottleneckt gewoon hard.
Ik vermoed dat het grootste probleem de stall bij het gebruiken van bufferSubData is. Maar de mogelijkheden die ik online kan vinden om het buffer te 'invalideren' hebben geen effect op de performance. Ik doel hierbij met name op het gebruiken van glBufferData met een nullptr als data ptr.
psuedo code
Render loop:code:
1
2
3
4
5
6
7
8
| while( 1 )
{
wacht op signal van main thread..
render particles
upload particle VBO
signal main thread dat de render thread klaar is met alle resources
glfinish
} |
main loop:
code:
1
2
3
4
5
6
7
| while( 1 )
{
convert particles naar juiste OpenGL formaat
signal render thread
update particles
wacht op signal van render thread
} |
specs
windows 8i7-2820QM sandy bridge
Intel HD graphics 3000, OpenGL 3.1
Ik heb ook een nvidia NVS 4200 beschikbaar, maar die levert bij mijn testcases altijd lagere performance. Sowieso wordt nvidia optimus niet blij van buffersubdata... ik heb een half jaar terug een factor 7x memory overhead gemeten tussen nvidia optimus aan en uit bij het gebruik van bufferSubData.