Software Rasterizer vs. Hardware Shader
-
Hi,
ich lese von der Grafikkarte (meine aktuelle Konfiguration, NV GeForce 7900 GS) ca. 700 MB/s aus dem Framebuffer und ca. 300 MB/s aus dem Z-Buffer. Diese Werte decken sich ca. mit den Benchmarkergebnissen, die man so lesen kann.Ein paar weitere Eckdaten:
Eine typische Scene hat bis zu 4000 Dreiecke, die allerdings nicht immer alle im aktuellen View-Frustrum liegen. Auch ist der Bildausschnitt, der zu rendern ist im allerschlimmsten Fall 1024x1024, meist allerdings deutlich kleiner, im Mittelwert würde ich von 256x256 oder weniger ausgehen.
Ich muss allerdings sehr hochfrequent rendern. In der Hardwareversion schaffe ich Zyluszeiten bei den genannten Daten von ca. 2ms pro Render-/Auslesezyklus.
Die Koordinaten der Vertices bzw. Normalenrichtungen liegen schon vorher im Bildschirmkoordinatensystem vor, so dass keine Transformationen gerechnet werden müssen und die Koordinaten 1:1 vom Rasterizer übernommen werden können.Gruß
ulretsam
-
Mit einem Quadcore und ordentlich SSE-Einsatz könntest du es vielleicht schaffen.
Oder hast du mal was anderes probiert als glReadPixels? Aus dem Z-Buffer zu lesen ist langsam, weil z.B. viele Grafikkarten intern den Z-Buffer komprimieren.Am besten baust du dir einen eigenen "Z-Buffer", wo der Shader die Tiefe reinschreibt. An dessen Daten könntest du bestimmt schneller rankommen.
Am besten renderst du in eine Floating-Point-Textur (z.B. R16G16B16A16).
R, G, B nutzt du für den Normalenvektor und A für die Tiefe.
-
Guter Vorschlag, so könnte man tatsächlich den Bottleneck von glReadPixels aus dem Tiefenpuffer umgehen. Ich habe sowas schon probiert, da gibt es allerdings zwei Probleme:
1.) Im Moment benutze ich einen 32-Bit Framebuffer mit RGBA8 Format. Ich löse die Normalenkomponenten somit in 8 Bit auf (RGB = xyz). Reicht mir, da dies nur zur Darstellung und Featurerekonstruktion in der weiteren Verarbeitung dient. D.h., ich lese im Moment 32 Bit/Pixel zurück.
Vom Tiefenpuffer verlange ich eine hohe Genauigkeit, da sind die gebotenen 24 Bit von NVidia und ATI schon fast zu wenig, am liebsten hätte ich 32 Bit. R16G16B16A16 geht da nicht, ich bräuchte etwas wie R8G8B8A32, aber sowas existiert natürlich nicht.
Nehme ich eine RGBA32 FP Textur als Rendertarget, hab ich gleich die doppelte Readback-Datenmenge (16 Byte/Pixel) und der Vorteil ist vorn Ar..., denn ich krieg definitiv nicht mehr als 700-800 MB/s aus der Karte rausgequetscht. Mist
2.) Mit den Sonderformaten (z.B. FP16) bin ich sehr Herstellerabhängig, außerdem unterstützen ältere Karten viele Features (z.B. FP-Texturen) noch gar nicht. Alles abzuchecken und Workarounds für alle eventuell auftretenden Fälle zu implementieren ist zu aufwändig. Eine völlig Hardwareunabhängige Lösung wäre somit, auch wenn sie etwas langsamer liefe, besser als eine Lösung, die einen bestimmten Grafikkartentyp vorraussetzt.
Blöde Sache, aber trotzdem gute Idee!
-
Dieser Thread wurde von Moderator/in rüdiger aus dem Forum Rund um die Programmierung in das Forum Spiele-/Grafikprogrammierung verschoben.
Im Zweifelsfall bitte auch folgende Hinweise beachten:
C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?Dieses Posting wurde automatisch erzeugt.
-
700MB/sec und 300MB/sec sind im Schnitt 500MB/sec (wenn du aus beiden gleichviel liest -- was inetwa hinkommt).
Pro Pixel liest du dann 7 bzw. 8 Byte -- rechnen wir mal mit 8.
Das sind also etwa 65M-Pixel/sec.Angenommen dein Prozessor hat 3 GHz, dann sind das ~3000/65 Takte die du pro Pixel zur Verfügung hast (wenn's gleich schnell oder schneller sein soll). Also ~46 Takte pro Pixel -- wenn man das ganze Triangle-Setup etc. mal vernachlässigt, und davon ausgeht dass jeder Pixel genau 1x geschrieben wird. Wird auf jeden Fall knapp, ist aber nicht ganz unrealistisch.
Noch ein paar Fragen:
- Renderst du mit perspektivischer Projektion oder ohne? Ohne geht viel schneller, das würde einiges vereinfachen.
Falls du mit einer "flachen" Projektion renderst sehe ich sehr gute Chancen, da sich dann das Interpolieren der Normalen sowie des Z-Wertes auf eine einfache Addition pro Schritt in der X-Richtung beschränkt. Vor allem mit SSE (wo du mehrere Komponenten auf einmal addieren kannst) müsste das sehr schnell gehen.
- Wieviel Overdraw hast du im Schnitt, und wieviel der gerenderten Fläche bleibt ganz leer?
Je weniger Pixel wirklich gerendert werden (inklusive Overdraw eben), desto bessere Chancen hast du mit der CPU. Wie gross der Ausschnitt ist ist bei der Lösung mit der Grafikkarte interessant, bei der Lösung mit der CPU aber eher unwichtig. Dafür kostet dort eben jedes Dreieck (setup) und jeder Pixel der geschrieben/überschrieben wird viel Zeit.
---
BTW: rein aus Neugier: was wird das eigentlich? Radiosity?
Und: kannst du nicht die Daten vielleicht gleich auf der Grafikkarte irgendwie weiterverarbeiten -- so dass es halt weniger Daten werden?
-
Cool, hört sich gut an!
Zunächst erstmal die Anwendung: das Ganze ist ein Rechenkern zur Simulation des Materialabtrags bei Fertigungsprozessen (bohren, fräsen, drehen, schleifen). Das Modell des Werkstückes wird aud den Z-Buffer gemapped und das Werkzeug-Spurvolumen wird daraufgerendert, ähnlich wie die Hand mit dem Nagelbrett. Das Ganze ist nur ein wenig komplexer, da das Modell dreidimensional (Z-Map über der xy, yz und zx-Ebene) und hinterschnittfähig ist. Man kann z.B. von allen Seiten Löcher in einen Würfel bohren, so dass die jeweilige Z-Buffer Richtung unterbrochen wird. Dies ist auch der Grund, warum ich nicht vollständig auf der GPU rechnen kann: Ich bekomme die dynamische Listenstruktur, die dahintersteht nicht auf der GPU abgebildet. Soviel zur Anwendung...
Zu den Fragen:
Perspektivische Projektion ist nicht notwendig, ich transformiere die Koordinaten des Werkzeug-Meshes vorher direkt in das Framebuffer-Koordinatensystem. Dies mache ich recht effektiv mit der Intel IPP-Bibliothek. Die ganze Trafo von ca. 12000 Vertices + Normalen geht in 0.2 ms ab (2.13 Ghz Core2Duo)
Der Overdrawanteil ist schlecht abzuschätzen. Ich berechne mir den darzustellenden Szenenausschnitt vor jeder Operation anhand der Boundingbox-Durchdringung der Bounding Boxen (axis aligned) von Werkzeugmesh und Werkstückmesh. Im Allgemeinen ist nur ein Schnittvolumen beider relevant, so dass es auf jeden Fall Overdraw gibt. Wie groß der Anteil im Mittel ist, kann ich aber beim besten Willen nicht sagen.
Eine schlechte Ausfüllung der Boundingbox des Werkzeuges führt wiederum zu viel Leerfläche. Leider kann ich das auch nicht abschätzen, ich müsste es mal am jetzigen Kern analysieren. Allerdings kann ich im Moment nur die Leerflächen auswerten, Overdrawanteile kann ich leider nicht liefern.Ich hoffe, das hilft weiter...
Grüße
ulretsam
-
ulretsam schrieb:
denn ich krieg definitiv nicht mehr als 700-800 MB/s aus der Karte rausgequetscht. Mist

welche kiste ist es? mit nem normalen PCIepress bus sollten 4GB/s drinnen sein.
ein oft gemachter fehler bei OGL ist, dass man als leseformat nicht das internet format nimmt. viele graphikkarten nutzen z.b. GL_BGRA_EXT, viele user lesen aber GL_RGBA, das umtauschen dieser werte geht zwar recht flott, aber die cpu muss dafuer den ganzen buffer lesen und schreiben, also ca 2gb/s verschwendung und vielleicht sogar der grund fuer deine schlechte performance beim buffer lesen.
das gleich gilt fuer den z-buffer, sogar noch mehr, da wie hier schon gesagt wurde, der z-buffer intern in anderen (optimierten) formaten vorliegt.ch berechne mir den darzustellenden Szenenausschnitt vor jeder Operation anhand der Boundingbox-Durchdringung der Bounding Boxen (axis aligned) von Werkzeugmesh und Werkstückmesh.
darf ich daraus entnehmen dass du nicht den ganzen framebuffer bzw zbuffer rauskopierst? (ich glaube das konnte man bei ogl angeben, ist schon etwas her dass ich das nutzte). teile rauszukopieren ist oft langsammer als den ganzen, da manche treiber dann erst den ganzen in einen buffer rauskopieren und dann den gewuenschten ausschnitt per cpu. das muss nicht immer so sein, war frueher aber meist ein problem.
wenn du meinst, dass dein rendern sehr schnell geht und nur das rauskopieren ein problem ist, render im zweiten durchgang nochmals in den colorbuffer, rechner diesmal aber den tiefenwert in r g b um, in etwa
[code]
frac(Depth.w,float4(1.f,256.f,256*256.f,256*256*256.f));
[/cpp]
und dann kannst du nochmals den framebuffer als 32bit rauslesen. damit haettest du fast deinen gewuenschten r8g8b8a32
hustbaer schrieb:
Angenommen dein Prozessor hat 3 GHz, dann sind das ~3000/65 Takte die du pro Pixel zur Verfügung hast (wenn's gleich schnell oder schneller sein soll). Also ~46 Takte pro Pixel
du wirst bandbreiten eher ram-zugriffszeit limitiert sein beim rasterisieren, da du bei 12000dreiecken vermutlich sehr kleine flaechen zeichnest, als cpu-takt.
wenn man das ganze Triangle-Setup etc. mal vernachlässigt
simples transformieren dauert schon 0.2ms, da ist nichtmal ne projektion dabei. trianglesetup hat sehr viel groessere kosten, besonders wenn klipping hinzukommt. das sollte man also nicht vernachlaessigen. bei 12000 dreiecken zeichnest du vermutlich 50pixel pro triangle, dabei vielleicht 5zeilen, pro zeile ne division, pro kante ne division...
-
Das mit den 4GB/s ist richtig, funktioniert aber leider nur in Upload-Richtung so schnell. Ist ja auch die Hauptanwendung. Der übliche Entwickler nutzt den Readback vielleicht gerade mal zum Screenshot knipsen oder Filmchen aufnehmen und dafür reicht die Bandbreite locker. Leider ist der Download wesentlich langsamer. Daran ist nicht der Bus verantwortlich, sondern das Interface des Chips.
Das mit dem 2mal Farbe rendern habe ich auch schon aus nem anderen Forum entnommen und ausprobiert. Ging schneller, aber ich habe den Gewinn durch das Zurückrechnen der RGB Werte zu einer Float mehr als wieder hergeben müssen

Ich glaube ich habe die Grafikkarten-interne Lösung ausgereitzt, da bin ich auch mittlerweile seit einigen Monaten dran (natürlich nicht fulltime :p ).
Suche jetzt ne Alternative, auch um hardware-unabhängig zu sein, damit der Kern auch auf - sagen wir - Bedienfeldrechnern an Maschinen laufen kann, die haben nämlich gewöhnlich keine allzu leistungsfähige Grafikhardware.Das mit den sehr kleinen Flächen ist richtig. Allerdings sind es nur um die 4000 und das ist auch schon viel. 12000 bezog sich auf die Vertices.
Ich würde ja gern einen ersten Schritt machen, scheue mich aber vor dem Aufwand. Generell stellt sich die Frage, ob ich einen Scanline-Rasterizer oder den Half-Space Ansatz von
[url]http://www.devmaster.net/forums/showthread.php?t=1884
Wie auch immer... Mir fehlt das Wissen, wie ich die Algorithmen richtig schnell kriege und ob ich überhaupt in den Bereich kommen kann, den meine Graka-Lösung jetzt hinkriegt. Wenn ich mich da ran setze muss ich sichergehen, dass ich auch den richtigen Weg einschlage...
-
Lese gerade Benchmarks über den kommerziellen Software-Rasterizer Pixomatic auf
http://www.radgametools.com/pixofeat.htm
Triangle rate:
PIII/733 1.39 MTri/sec
Athlon/1.8 3.17 MTri/sec
P4/2.2 3.16 MTri/sec
P4/3.3 4.86 MTri/secTriangle rate test case: one 256x256 texture with texcoords generated by PIXO_TEXGEN_PROJECTED_XYZ1, Gouraud shading, 16-bit z buffer with z compare and z write enabled, drawing a model consisting of 77 indexed meshes that contain 27,296 triangles in total, 12,841 of which are front-facing with a non-zero area; culled triangles are counted for purposes of triangle rate calculations. Target window is 640x480, 32-bpp; bounding box of model covers about 5 percent of the window; only rendering time is counting, not the blt to the screen; no triangles are clipped.
4.86 MTri/s, d.h. meine 4000 rendern texturiert und schattiert (Features, die ich gar nicht brauche) unter 1 ms...
Wär doch was, da will ich auch hin

-
ulretsam schrieb:
Das mit den 4GB/s ist richtig, funktioniert aber leider nur in Upload-Richtung so schnell. Ist ja auch die Hauptanwendung. Der übliche Entwickler nutzt den Readback vielleicht gerade mal zum Screenshot knipsen oder Filmchen aufnehmen und dafür reicht die Bandbreite locker. Leider ist der Download wesentlich langsamer. Daran ist nicht der Bus verantwortlich, sondern das Interface des Chips.
ich hab schon benchmarks mit 1.5gb/s gesehen, in der praxis ist 2gb wohl peak, aber am interface alleine liegt es nicht, eher dass sich alle komponenten die rambandbreite teilen usw. aber wie gesagt, 1.5gb sollte gehen.
Das mit dem 2mal Farbe rendern habe ich auch schon aus nem anderen Forum entnommen und ausprobiert. Ging schneller, aber ich habe den Gewinn durch das Zurückrechnen der RGB Werte zu einer Float mehr als wieder hergeben müssen

dann leg einen float buffer an falls du ne relativ neue graka hast. mit ein wenig programmieraufwand kannst du auch in beide buffer zur selben zeit rendern.
Das mit den sehr kleinen Flächen ist richtig. Allerdings sind es nur um die 4000 und das ist auch schon viel. 12000 bezog sich auf die Vertices.
normalerweise hat man im schnitt ein vertex pro triangle (im schnitt), falls du pro triangle 3vertices hast und transformierst, koenntest du es jetzt also von 0.2ms auf 0.06ms bringen. meist sind ja die highlevel optimierungen effizienter als lowlevel(intel)libs

Ich würde ja gern einen ersten Schritt machen, scheue mich aber vor dem Aufwand. Generell stellt sich die Frage, ob ich einen Scanline-Rasterizer oder den Half-Space Ansatz von
[url]http://www.devmaster.net/forums/showthread.php?t=1884
Wie auch immer... Mir fehlt das Wissen, wie ich die Algorithmen richtig schnell kriege und ob ich überhaupt in den Bereich kommen kann, den meine Graka-Lösung jetzt hinkriegt. Wenn ich mich da ran setze muss ich sichergehen, dass ich auch den richtigen Weg einschlage...
mach testweise einfach ein fuellen von vierecken andie stelle der dreiecke, das ist sehr einfach mit zwei schleifen zu implementieren. schau wie die performance ist, bei dreiecken wirst du weniger pixel, dafuer komplexere berechnungen haben, somit vielleicht gleiche performance.
dann kannst du zumindestens abschaetzen wie schnell es ginge

-
Also ohne Perspektive wie gesagt schätze ich dass es mit einer halbwegs modernen CPU schneller gehen müsste als was du beschrieben hast.
Ich würde es als erstes mal mit einem ganz einfachen Rasterizer probieren der ein Dreieck auf einmal rendert, und zwar Scanline für Scanline.
Ein anderer Ansatz: du baust eine Liste mit allen Dreiecken drinnen, und trägst für jedes Dreieck Start- und Endzeile ein, und sortierst die Liste dann nach der Startzeile. Dann gehst du Zeile für Zeile durch und zeichnest einfach alle Spans für eine Zeile auf einmal. Das sollte dem Cache auch gut gefallen. Dabei verwaltest du immer eine Liste von "aktiven Dreiecken", indem du welche die "fertig" sind rauswirfst und aus der nach Startzeile sortierten Liste einfach alle dazunimmst die gerade anfangen.
Ich bin sicher dass das Verfahren einen Namen hat, aber ich weiss leider nicht welchen
Das sollte sich z.B. auch leicht parallelisieren lassen: das Setup der Liste machst du in einem einzigen Thread. Danach rendert Thread 1 Zeilen 1 bis 100, Thread 2 Zeilen 101 bis 200 etc.
----
Tips: Falls du irgendwo temporäre dynamische Speicheranforderungen brauchst verwende einen "Zeiger verschieben" Allocator. Also du reservierst dir z.B. 10MB oder 100MB Speicher, und setzt einen Zeiger auf den Anfang. Bei jeder Speicheranforderung merkst du dir den Zeiger als Rückgabewert, und verschiebst den Zeiger danach um N Bytes. Falls du über's ende des vor-reservierten Blocks kommst musst du einen 2. (3., 4. ...) Block anfordern. Nachdem das Frame fertig gerendert ist setzt du den Zeiger auf den Anfang zurück und fertig. Dynamische Speicheranforderungen sind nämlich nicht gerade schnell, mit solchen Tricks kann man u.U. einiges rausholen.
Falls du irgendwo Listen brauchst schreib dir den Code selbst - STL Listen haben den Nachteil dass für jedes Element dynamisch Speicher angefordert wird und die Daten kopiert werden. Wenn du den Code selbst schreibst kannst du die "next" und "previous" Zeiger einfach in die Elemente mit reinpacken wie man das in C-Zeiten gemacht hat. Ausserdem kannst du (wenn du einen entsprechenden Allocator verwendest, s.o.) die Liste einfach "vergessen" wenn du sie nichtmehr brauchst - ohne alle Elemente nochmal durchgehen zu müssen.
----
rapso schrieb:
hustbaer schrieb:
Angenommen dein Prozessor hat 3 GHz, dann sind das ~3000/65 Takte die du pro Pixel zur Verfügung hast (wenn's gleich schnell oder schneller sein soll). Also ~46 Takte pro Pixel
du wirst bandbreiten eher ram-zugriffszeit limitiert sein beim rasterisieren, da du bei 12000dreiecken vermutlich sehr kleine flaechen zeichnest, als cpu-takt.
Wenn die Speicherzugriffe auch nur halbwegs linear erfolgen, dann sehe ich da kein Problem. Klar darf man nicht kreuz und quer durch die Gegend adressieren, aber wenn man z.B. Scanline basiert rendert tut man das ja auch nicht.
Ich denke du hängst hier immer noch etwas "veralteten Wahrheiten" nach... heutzutage tut man sich (wie gesagt bei halbwegs linearem Zugriff auf den Speicher) schon schwer dass die CPU mithalten kann. 2GB/sec auf den Hauptspeicher sind halt nicht gerade wenig, und 4MB 2nd Level Cache helfen auch
-
hustbaer schrieb:
rapso schrieb:
hustbaer schrieb:
Angenommen dein Prozessor hat 3 GHz, dann sind das ~3000/65 Takte die du pro Pixel zur Verfügung hast (wenn's gleich schnell oder schneller sein soll). Also ~46 Takte pro Pixel
du wirst bandbreiten eher ram-zugriffszeit limitiert sein beim rasterisieren, da du bei 12000dreiecken vermutlich sehr kleine flaechen zeichnest, als cpu-takt.
Wenn die Speicherzugriffe auch nur halbwegs linear erfolgen, dann sehe ich da kein Problem. Klar darf man nicht kreuz und quer durch die Gegend adressieren, aber wenn man z.B. Scanline basiert rendert tut man das ja auch nicht.
wie ich schon sagte:
da du bei 12000dreiecken vermutlich sehr kleine flaechen zeichnest
daraus resultiert->
kreuz und quer durch die Gegend adressieren
daraus resultiert schlechte performance.
Ich denke du hängst hier immer noch etwas "veralteten Wahrheiten" nach...
ich denke da denkst du falsch, ich haenge eher AMD-Codeanalyst und Intel-VTune nach. da ich sehr oft rasterizer schreibe und optimiere, weiss ich dass deine ideen, dass speicherzugriffe linear erfolgen und triangle-setup zu vernachlaessigen ist das gegenteil der realen welt sind.
die "veraltete wahrheit" ist wahrer je mehr wir in die zukunft gehen, in relation zu cpu-performance wird speicher langsammer. speicher wird auf langes lineares lesen optimiert, cpus und programme lesen immer zufaelliger im speicher. frueher gab es diese "alte wahrheit" garnicht, da cpus und ram mit gleichem takt arbeiteten und nichtmal cache noetig war. heute bekommst du 3level vom cache, manche cpus haben 24MB davon und es reicht bei weitem nicht, im gegenteil, oft ist sogar der cache schonwieder langsam und bremst die cpu waehrend sie auf dessen daten warten.
heutzutage tut man sich (wie gesagt bei halbwegs linearem Zugriff auf den Speicher) schon schwer dass die CPU mithalten kann. 2GB/sec auf den Hauptspeicher sind halt nicht gerade wenig, und 4MB 2nd Level Cache helfen auch

die cpu kann heutzutage sehr locker mithalten. sie langweilt sich sogar die meiste zeit. selbst die "hochoptimierten" programme nutzen oft sehr wenig der moeglichen leistung, da die leute immer noch glauben dass es am code haengt und garnicht auf den speicher achten.
-
ich denke da denkst du falsch, ich haenge eher AMD-Codeanalyst und Intel-VTune nach. da ich sehr oft rasterizer schreibe und optimiere, weiss ich dass deine ideen, dass speicherzugriffe linear erfolgen und triangle-setup zu vernachlaessigen ist das gegenteil der realen welt sind.
Das meine ich, ich kann es einfach nicht einschätzen, was wirkllich performance-relevant ist. Wenn Du schon oft rasterizer geschrieben und optimiert hast, kannst Du mir nicht mit nem Codegerüst auf die Srünge helfen bzw. mir ein paar nützliche Links nennen, damit ich durchstarten kann und nicht bei Null anfangen muss? Wäre echt super!
normalerweise hat man im schnitt ein vertex pro triangle (im schnitt), falls du pro triangle 3vertices hast und transformierst, koenntest du es jetzt also von 0.2ms auf 0.06ms bringen. meist sind ja die highlevel optimierungen effizienter als lowlevel(intel)libs
Stimmt, hast Recht, ich benutze auch bereits einen Index-Array und mache keine redundanten Transformationen. Hab nicht drüber nachgedacht beim Schreiben, hatte nur die Zeitmessung im Kopf und eine Zahl dazu gesucht...
Falls du irgendwo temporäre dynamische Speicheranforderungen brauchst verwende einen "Zeiger verschieben" Allocator. ...
Eine eigene, einfache und schnelle Speicherverwaltung habe ich bereits drin. Auch eigene Listenklassen, die genau auf meine Anforderungen zugeschnitten sind und blockweise alloziieren hab ich schon.
dann leg einen float buffer an falls du ne relativ neue graka hast. mit ein wenig programmieraufwand kannst du auch in beide buffer zur selben zeit rendern.
Interessant, das mit dem beide buffer zur selben zeit rendern. Wie geht denn das? Multiple Render Targets?
Wie gesagt, suche ich aber eigentlich nach einer Lösung, die hardwareunabhängig arbeitet...