Ich möchte eine Liste von Werten an einen Fragment-Shader senden. Es handelt sich um eine möglicherweise große Liste (einige Tausend Elemente lang) von Floats mit einfacher Genauigkeit. Der Fragment-Shader benötigt zufälligen Zugriff auf diese Liste und ich möchte die Werte der CPU in jedem Frame aktualisieren.
Ich denke über meine Möglichkeiten nach, wie dies getan werden könnte:
Als einheitliche Variable vom Array-Typ ("uniform float x [10];"). Aber hier scheint es Grenzen zu geben, da das Senden von mehr als ein paar hundert Werten auf meiner GPU sehr langsam ist und ich auch die Obergrenze im Shader fest codieren müsste, wenn ich dies lieber zur Laufzeit ändern möchte.
Aktualisieren Sie dann als Textur mit Höhe 1 und Breite meiner Liste die Daten mit glCopyTexSubImage2D.
Andere Methoden? Ich habe in letzter Zeit nicht mit allen Änderungen in der GL-Spezifikation Schritt gehalten. Vielleicht gibt es eine andere Methode, die speziell für diesen Zweck entwickelt wurde?
Antworten:
Derzeit gibt es 4 Möglichkeiten, dies zu tun: Standard-1D-Texturen, Puffertexturen, einheitliche Puffer und Shader-Speicherpuffer.
1D Texturen
Mit dieser Methode
glTex(Sub)Image1D
füllen Sie eine 1D-Textur mit Ihren Daten. Da Ihre Daten nur ein Array von Floats sind, sollte Ihr Bildformat seinGL_R32F
. Sie können dann mit einem einfachentexelFetch
Aufruf im Shader darauf zugreifen .texelFetch
Nimmt Texelkoordinaten (daher der Name) und schaltet alle Filter aus. Sie erhalten also genau ein Texel.Hinweis:
texelFetch
ist 3.0+. Wenn Sie frühere GL-Versionen verwenden möchten, müssen Sie die Größe an den Shader übergeben und die Texturkoordinate manuell normalisieren.Die Hauptvorteile sind hier Kompatibilität und Kompaktheit. Dies funktioniert auf GL 2.1-Hardware (unter Verwendung der Notation). Und Sie nicht haben zu verwenden
GL_R32F
Formate; Sie könntenGL_R16F
halbe Schwimmer verwenden. OderGL_R8
wenn Ihre Daten für ein normalisiertes Byte angemessen sind. Größe kann viel für die Gesamtleistung bedeuten.Der Hauptnachteil ist die Größenbeschränkung. Sie können nur eine 1D-Textur mit der maximalen Texturgröße verwenden. Auf Hardware der GL 3.x-Klasse sind dies rund 8.192, aber garantiert nicht weniger als 4.096.
Einheitliche Pufferobjekte
Dies funktioniert so, dass Sie in Ihrem Shader einen einheitlichen Block deklarieren:
layout(std140) uniform MyBlock { float myDataArray[size]; };
Sie greifen dann wie ein Array im Shader auf diese Daten zu.
Zurück im C / C ++ / etc-Code erstellen Sie ein Pufferobjekt und füllen es mit Gleitkommadaten. Anschließend können Sie dieses Pufferobjekt dem
MyBlock
einheitlichen Block zuordnen. Weitere Details finden Sie hier.Die Hauptvorteile dieser Technik sind Geschwindigkeit und Semantik. Die Geschwindigkeit hängt davon ab, wie Implementierungen einheitliche Puffer im Vergleich zu Texturen behandeln. Texturabrufe sind globale Speicherzugriffe. Einheitliche Pufferzugriffe sind in der Regel nicht; Die einheitlichen Pufferdaten werden normalerweise in den Shader geladen, wenn der Shader bei seiner Verwendung beim Rendern initialisiert wird. Von dort ist es ein lokaler Zugang, der viel schneller ist.
Semantisch ist dies besser, da es sich nicht nur um ein flaches Array handelt. Für Ihre spezifischen Bedürfnisse
float[]
spielt es keine Rolle , ob Sie nur eine benötigen . Wenn Sie jedoch eine komplexere Datenstruktur haben, kann die Semantik wichtig sein. Betrachten Sie beispielsweise eine Reihe von Lichtern. Lichter haben eine Position und eine Farbe. Wenn Sie eine Textur verwenden, sieht Ihr Code zum Abrufen der Position und Farbe für ein bestimmtes Licht folgendermaßen aus:vec4 position = texelFetch(myDataArray, 2*index); vec4 color = texelFetch(myDataArray, 2*index + 1);
Mit einheitlichen Puffern sieht es genauso aus wie jeder andere einheitliche Zugriff. Sie haben Mitglieder benannt, die aufgerufen werden können
position
undcolor
. Alle semantischen Informationen sind also da; Es ist einfacher zu verstehen, was los ist.Auch hierfür gibt es Größenbeschränkungen. OpenGL erfordert, dass Implementierungen mindestens 16.384 Bytes für die maximale Größe einheitlicher Blöcke bereitstellen. Das heißt, für Float-Arrays erhalten Sie nur 4.096 Elemente. Beachten Sie erneut, dass dies das Minimum ist, das für Implementierungen erforderlich ist. Einige Hardware bietet viel größere Puffer. AMD bietet beispielsweise 65.536 Geräte für die Hardware der DX10-Klasse an.
Puffertexturen
Dies ist eine Art "Super 1D Textur". Sie ermöglichen Ihnen effektiv den Zugriff auf ein Pufferobjekt von einer Textureinheit aus . Obwohl sie eindimensional sind, handelt es sich nicht um 1D-Texturen.
Sie können sie nur ab GL 3.0 oder höher verwenden. Und Sie können nur über die
texelFetch
Funktion darauf zugreifen .Der Hauptvorteil ist hier die Größe. Puffertexturen können im Allgemeinen ziemlich gigantisch sein. Während die Spezifikation im Allgemeinen konservativ ist und mindestens 65.536 Bytes für Puffertexturen vorschreibt, erlauben die meisten GL-Implementierungen, dass sie in der Größe der Mega- Bytes liegen. In der Tat wird die maximale Größe normalerweise durch den verfügbaren GPU-Speicher und nicht durch Hardwarebeschränkungen begrenzt.
Außerdem werden Puffertexturen in Pufferobjekten gespeichert, nicht in undurchsichtigeren Texturobjekten wie 1D-Texturen. Dies bedeutet, dass Sie einige Pufferobjekt-Streaming-Techniken verwenden können , um sie zu aktualisieren.
Der Hauptnachteil ist hier die Leistung, genau wie bei 1D-Texturen. Puffertexturen sind wahrscheinlich nicht langsamer als 1D-Texturen, aber sie sind auch nicht so schnell wie UBOs. Wenn Sie nur einen Schwimmer von ihnen ziehen, sollte dies kein Problem sein. Wenn Sie jedoch viele Daten daraus abrufen, sollten Sie stattdessen ein UBO verwenden.
Shader-Speicherpufferobjekte
OpenGL 4.3 bietet eine andere Möglichkeit, dies zu handhaben: Shader-Speicherpuffer . Sie ähneln einheitlichen Puffern. Sie geben sie mit einer Syntax an, die fast identisch mit der von einheitlichen Blöcken ist. Der Hauptunterschied besteht darin, dass Sie ihnen schreiben können. Natürlich ist das für Ihre Bedürfnisse nicht nützlich, aber es gibt andere Unterschiede.
Shader-Speicherpuffer sind konzeptionell eine alternative Form der Puffertextur. Daher sind die Größenbeschränkungen für Shader-Speicherpuffer viel größer als für einheitliche Puffer. Das OpenGL-Minimum für die maximale UBO-Größe beträgt 16 KB. Das OpenGL-Minimum für die maximale SSBO-Größe beträgt 16 MB . Wenn Sie also über die Hardware verfügen, sind diese eine interessante Alternative zu UBOs.
Stellen Sie nur sicher, dass Sie sie als deklarieren
readonly
, da Sie ihnen nicht schreiben.Der potenzielle Nachteil ist hier wiederum die Leistung im Vergleich zu UBOs. SSBOs funktionieren wie eine Bildlade- / Speicheroperation über Puffertexturen. Im Grunde ist es (sehr schöner) syntaktischer Zucker um einen
imageBuffer
Bildtyp. Daher werden Lesevorgänge von diesen wahrscheinlich mit der Geschwindigkeit von Lesevorgängen von a ausgeführtreadonly imageBuffer
.Ob das Lesen über das Laden / Speichern von Bildern durch Pufferbilder schneller oder langsamer als Puffertexturen ist, ist derzeit unklar.
Ein weiteres potenzielles Problem besteht darin, dass Sie die Regeln für den nicht synchronen Speicherzugriff einhalten müssen . Diese sind komplex und können Sie sehr leicht stolpern.
quelle
Dies klingt nach einem guten Anwendungsfall für Texturpufferobjekte . Diese haben nicht viel mit normalen Texturen zu tun und ermöglichen Ihnen grundsätzlich den Zugriff auf den Speicher eines Pufferobjekts in einem Shader als einfaches lineares Array. Sie ähneln 1D-Texturen, werden jedoch nicht gefiltert und nur über einen Ganzzahlindex aufgerufen. Dies klingt nach dem, was Sie tun müssen, wenn Sie es als Werteliste bezeichnen. Und sie unterstützen auch viel größere Größen als 1D-Texturen. Denn es ist die Aktualisierung können Sie dann die Standardpufferobjektmethoden verwenden (
glBufferData
,glMapBuffer
, ...).Andererseits benötigen sie GL3 / DX10-Hardware, um verwendet zu werden, und ich denke, sie wurden sogar in OpenGL 3.1 zum Kern gemacht. Wenn Ihre Hardware / Ihr Treiber dies nicht unterstützt, ist Ihre zweite Lösung die Methode Ihrer Wahl, verwenden Sie jedoch eher eine 1D-Textur als eine Breite x 1 2D-Textur. In diesem Fall können Sie auch eine nicht flache 2D-Textur und etwas Indexmagie verwenden, um Listen zu unterstützen, die größer als die maximale Texturgröße sind.
Aber Texturpuffer passen perfekt zu Ihrem Problem, denke ich. Für genauere Einblicke können Sie auch in die entsprechende Erweiterungsspezifikation schauen .
EDIT: Als Antwort auf Nicol's Kommentar zu einheitlichen Pufferobjekten können Sie hier auch einen kleinen Vergleich der beiden suchen . Ich tendiere immer noch zu TBOs, kann aber nicht wirklich begründen, warum, nur weil ich sehe, dass es konzeptionell besser passt. Aber vielleicht kann Nicol einer Antwort mehr Einblick in die Sache geben.
quelle
texture...
durch...[...]
)? Sind sie wirklich schneller als TBOs? Einige Einblicke wären schön, da ich mit keinem der beiden Erfahrungen habe. Vielleicht können Sie eine Antwort hinzufügen, die UBOs als Alternative erklärt?Eine Möglichkeit wäre, einheitliche Arrays zu verwenden, wie Sie bereits erwähnt haben. Eine andere Möglichkeit besteht darin, eine 1D- "Textur" zu verwenden. Suchen Sie nach GL_TEXTURE_1D und glTexImage1D. Ich persönlich bevorzuge diesen Weg, da Sie die Größe des Arrays im Shader-Code nicht wie gesagt fest codieren müssen und opengl bereits über integrierte Funktionen zum Hochladen / Zugreifen auf 1D-Daten auf der GPU verfügt.
quelle
Ich würde sagen, wahrscheinlich nicht Nummer 1. Sie haben eine begrenzte Anzahl von Registern für Shader-Uniformen, die je nach Karte variieren. Sie können GL_MAX_FRAGMENT_UNIFORM_COMPONENTS abfragen, um Ihr Limit herauszufinden. Auf neueren Karten läuft es zu Tausenden, zB hat ein Quadro FX 5500 anscheinend 2048. (http://www.nvnews.net/vbulletin/showthread.php?t=85925). Es hängt davon ab, auf welcher Hardware es ausgeführt werden soll und welche anderen Uniformen Sie möglicherweise auch an den Shader senden möchten.
Nummer 2 könnte je nach Ihren Anforderungen funktionieren. Entschuldigen Sie die Unbestimmtheit hier, hoffentlich kann Ihnen jemand anderes eine genauere Antwort geben, aber Sie müssen genau angeben, wie viele Texturaufrufe Sie in älteren Shader-Modellkarten tätigen. Es hängt auch davon ab, wie viele Texturlesevorgänge Sie pro Fragment ausführen möchten. Abhängig von Ihrem Shader-Modell und den Leistungsanforderungen möchten Sie wahrscheinlich nicht erneut versuchen, Tausende von Elementen pro Fragment zu lesen. Sie können Werte in RGBAs einer Textur packen und so 4 Lesevorgänge pro Texturaufruf erhalten. Bei wahlfreiem Zugriff ist dies jedoch möglicherweise nicht hilfreich.
Ich bin mir bei Nummer 3 nicht sicher, aber ich würde vorschlagen, vielleicht UAV (ungeordnete Zugriffsansichten) zu betrachten, obwohl ich denke, dass dies nur DirectX ist, ohne anständiges openGL-Äquivalent. Ich denke, es gibt eine nVidia-Erweiterung für openGL, aber Sie beschränken sich dann wieder auf eine ziemlich strenge Mindestspezifikation.
Es ist unwahrscheinlich, dass die Übergabe von Tausenden von Datenelementen an Ihren Fragment-Shader die beste Lösung für Ihr Problem darstellt. Wenn Sie mehr Details zu dem, was Sie erreichen möchten, angegeben haben, erhalten Sie möglicherweise alternative Vorschläge.
quelle