C++ [MFC]Schnelle Darstellungsmethode für Bilder

Fhnx

Cadet 4th Year
Registriert
Feb. 2010
Beiträge
127
Hallo zusammen!

Bin an einem etwas größeren Projekt dran und bräuchte einen kleinen Denkanstoß von euch, um mir viel gesuche und ausprobiere zu ersparen.
Mein Projekt ist, dass ich 3D-Bilder rendern möchte. Das Einlesen der Daten und das rendern funktionieren auch schon. Nun möchte ich die gerenderten Bilder aber nicht, wie bisher in BMP umwandeln und auf der Festplatte speichern, um sie ansehen zu können, sondern sie direkt in einem (eigenen) Fenster anzeigen.
Das ganze soll später annähernd Echtzeit laufen, also Anwender bewegt im Bild bei gedrückter Maustaste die Maus und das Bild verändert sich, also so ähnlich wie bei Google Maps z.B. die Karte zu bewegen. D.h. ich möchte so wenig Performance für die eigentliche Darstellung des Bildes opfern, da das rendern schon lange genug dauert.
Im Moment sieht die geplante 'Renderpipeline' so aus:
Benutzer ändert durch Eingabe die Ansicht -> Werte werden neu gesetzt -> mir neuen Werten wird Bild berechnet -> Bild wird angezeigt
Da ich ziemlich frei vom Format her bin, wollte ich wissen, ob es eine schnellere Variante gibt, als die Bilder nach BMP zu konvertieren und dann darstellen zu lassen, wie z.b. hier oder hier.
Ich bin vom Format her (Bitgenauigkeit der Farbkanäle, Anordnung, etc) relativ frei und es kostet so gut wie keine Performance dort etwas zu ändern, das Bild sollte aber ohne Verlust dargestellt werden. Es erscheint mir aber sehr umständlich den Umweg über ein BMP zu gehen.
Auf externe Bibiliotheken würde ich gerne verzichten, wenn die Performance von MFC nicht zu schlecht ist.

Danke für eure Hilfe und sorry für den langen Text.
 
Also entweder ist deine Bibliothek ein wenig beschränkt oder du hast noch nicht alle Methoden entdeckt ;)
Ein BMP enthält ja so gesehen nur Pixel für Pixel die Farbinformationen.

Aktuell liest es sich für mich so:
Renderer erzeugt BMP (im RAM) -> DU(!!) schreibst das BMP auf HDD -> DU(!!) liest es von HDD und stellst es dar.


Warum nicht einfach diesen Weg gehen, wenn du schon weißt, dass es ein valides BMP ist:
Renderer erzeugt BMP (im RAM) -> du übergibst die Daten an deine grafische Ausgabe

Es dürfte wohl einfach möglich sein ein BMP-Objekt zu erstellen und es direkt auf den Speicherbereich (per Pointer) zu setzen, den der Renderer zum Speichern benutzt hat.


Grütze,
benneque


EDIT: Noch eine Alternative wäre, wenn du das Zwischenergebnis deines Renderers einfach von Hand aus dem RAM liest (wenn es BMP ist, dürfte das ein Kinderspiel sein) und stellst dann Pixel für Pixel auf dem Bildschirm dar ;)
 
Den Link von dir kannte ich noch nicht, hatte aber auch schon etwas ähnliches gefunden. Auf der Festplatte zwischenspeichern wollte ich sowieso nicht. Aus Performancegründen macht das mal überhaupt keinen Sinn :rolleyes:. Den Header etc kann ich auch aus meiner Schreibfunktion klauen, das wäre auch nicht das Problem.
Meine eigentliche Frage war, ob es so etwas wie einen 'nativen' Bildtyp von Windows gibt, der auf die Darstellung in Fenstern optimiert ist. Da diese CBitmap-Klasse von MFC mir etwas sehr klobig vor kommt, vorallem, wenn sich die Bilddaten und die Auflösung häufig ändern (Das Bildformat bleibt das gleiche).

the_nobs schrieb:
Ganz so einfach ist das nicht mit BMP darstellen, da BMPnoch einen header enthält den du eigentlich nicht brauchst für Grafikdarstellung.

Der viel zu große Header, ist auch einer der Punkte, weswegen ich den 'Umweg' über BMP gern vermeiden würde. Aber wenn es das Performanteste ist, dann muss ich wohl das benutzen.

Was für einen Objekttyp wähle ich da am besten? CStatic? Ich will, dass die größe des Bildes automatisch an das Fenster angepasst wird, wenn ich die Größe ändere. (Bzw. dass ich die Größe abfragen kann und das Bild dementsprechend kleiner rendern kann)
Ist es auch möglich komplexere Mausfunktionen zur Steuerung, wie oben erwähnt, damit zu implementieren oder brauche ich dafür was anderes?

Vielen Dank für eure Antworten :).
 
Das mit der Fenstergröße ist wieder ne ganz andere Geschichte:
Du musst einen Listener auf die Fenstergröße setzen, und wenn der aktiviert wird, dann:
1. neue Fenstergröße merken
2. für die neue Größe rendern
3. wie gewohnt zeichnen

Drag and Drop für die Maus kannst du natürlich händisch umsetzen, aber es gibt sicherlich auch dafür Libraries.

P.S. der Header ist an einem BMP das kleinste ;-)
 
OK, wenn ich euch richtig verstanden habe, dann sollte die Variante mit der BMP also am schnellsten sein und ich verbau mir nichts, in richtung Bedienung. Dann schonmal Danke für die Hilfe und falls es Probleme gibt, werde ich sie hier posten :).
 
Also eine 24bit BMP Datei ohne Alpha-Kanal (brauchst du ja eh nicht für die Bildschirmausgabe) braucht für ein FullHD Bild ca. 6MB Speicher. Davon braucht der Header (laut Wikipedia) 54Byte, unabhängig von der Bildgröße. D.h. der Header macht 0,00087% der Gesamtgröße aus. Das sollte verschmerzbar sein ;)

BMP bringt dir sogesehen natürlich noch ein paar Vorteile: Es lässt sich schneller generieren als ein PNG oder JPEG, denn: um ein komprimiertes Bild zu erstellen muss man das gesamte Bild vorher schon kennen, d.h. es würde so oder so erst ein BMP erstellt werden und danach erst noch ein Algorithmus drüber laufen, um die Daten zu komprimieren.
Für die Anzeige auf dem Bildschirm ist BMP wieder im Vorteil: Man kann einfach jeden Pixel direkt auslesen. Für ein komprimiertes Format muss man das Bild erst decodieren (oder entpacken), d.h. hier geht Rechenzeit drauf.

Ganz einfach ausgedrückt: Um eine BMP anzuzeigen, müsste man sie ganz stumpf 1:1 zur Grafikkarte Schicken. Für komprimierte Formate müsste die CPU das Ding erst dekomprimieren und könnte dann erst schicken. ;)
 
Der Speicherplatz ist mir eher weniger wichtig, wichtiger ist die benötigte Rechenzeit.
Also ist BMP praktische dieses 'native' Format, was ich gemeint habe. Dachte, es gibt da noch was anderes.

Danke für die Antwort.
 
Naja, fast ;) Es gibt auch im BMP Format noch verschiedene Formen.
Es gibt die Möglichkeit ein BMP "rückwärts" zu schreiben (keine Ahnung wo das Verwendung findet), dann müsste man natürlich den Datenstrom rückwärts an die Grafikkarte schicken. Und scheinbar werden die Farben im 24bit Modus als BGR statt RGB gespeichert. Aber im Prinzip lassen sich diese ganzen "Fehler" durch minimalsten Rechenaufwand korrigieren.

Wenn du mehr wissen willst, schau mal hier: http://de.wikipedia.org/wiki/Windows_Bitmap#Vor-_und_Nachteile
 
Ich weiß ja nicht genau was du langfristig damit vorhast aber 3D-Bilder rendern und dann möglichste effizient auf den Bildschirm zeichnen? Das geht natürlich am Besten, wenn man die Hardware ausnutzt, die dafür auch zuständig ist: die Grafikkarte und genau das kann mit DirectX bzw. OpenGL ganz gut machen.

Du hast zwar gemeint, dass du keine externen Bibliotheken verwenden willst aber diese Beiden sind ja wirklich weit verbreitet und eben für solche Sachen da.
 
Ich rendere Volumendaten. Diese Rohdaten können gerne mal 800MB alleine im RAM fressen. Weswegen eine Veränderung dieser Daten keinen Sinn macht, da schon allein eine Iteration durch das gesamte Volumen mehrere Sekunden dauert. Da auch hohe Qualität gefragt ist, setzte ich als Renderingtechnik Raytracing ein. Ich habe auch eine (nicht optimierte) Fassung für OpenCL geschrieben, die aber das gesamte Volumen in den Grafikspeicher lädt. Da das ganze aber auch auf älteren PCs laufen soll, kann man nicht davon ausgehen, dass das möglich ist und OpenCL auf den RAM zugreifen zu lassen ist zu unperformant. Vorallem, da der Code ansich zu verzweigt ist, als dass er von kleineren Grafikkarten profitieren würde, diese reagieren darauf sehr allergisch. Dementsprechend ist eine halbwegs vernünftige CPU schneller, als eine Onboard-Intel oder Laptop-Grafikkarte.
Dementsprechend bin ich wieder klassisch bei der CPU gelandet. Mit verschiedenen Optimierungen, wie einer Bounding Box und Empty Space Skipping(eng) habe ich halbwegs akzeptable Renderzeiten. Das ganze läuft noch auf einem Kern, wird aber in Zukunft auf mehreren Threads laufen, da es sehr einfach parallelisierbar ist.
Jetzt wollte ich zuerst einmal die Oberfläche basteln, um verschiedene Sachen testen zu können und notfalls werde ich noch weitere Optimierungen versuchen, wenn die Performance zu schlecht ist.

Bibliotheken möchte ich so wenig wie möglich einsetzen, da das ganze in ein größeres Projekt integriert werden soll und außerdem die Lizenzen ein Problem werden könnten. Des weiteren sollte das Ganze so portabel wie möglich sein.
 
Ich hab auch gerad mal rumgegoogelt nach sowas wie "display image mfc" um von dem bmp format weg zu kommen aber habe nicht viel gefunden..
In was für einen Format liegt dein Bild denn vor? Du hast wahrscheinlich irgend eine Art von Datenstruktur (2D Array, Vector...) in der die RGB Werte stehen? Selbst wenn du das jedesmal in ein korrektes bmp transferierst um dann dieses darzustellen wird das performance-technisch nicht der Flaschenhals sein denke ich. Sowas geht selbst auf alten pcs in kaum messbaren ms-zeiten, solange der Speicher schon vorhanden ist. Wichtig ist, dass du die Daten hintereinander weg ließt und schreibst. Der Unterschied zwischen Blockweisen und random lesen ist gewaltig.
 
kuddlmuddl schrieb:
Ich hab auch gerad mal rumgegoogelt nach sowas wie "display image mfc" um von dem bmp format weg zu kommen aber habe nicht viel gefunden..
So gings mir auch, deswegen auch dieser Thread :).


Das Bild wir Pixel für Pixel berechnet, so wie bei Raytracing üblich. Wenn ich weiß welches Format die Pixel haben müssen, kann ich das gleich in dem Format berechnen lassen (bzw. umrechnen). Intern rechnet der Renderer mit 3 floats, je einen pro Farbkanal.

Ist der Unterschied zwischen Blockweisen und random auch bei der CPU so groß? Ich dachte das ist nur bei einer Grafikkarte so. Beim Raytracing werden sich zufällige Zugriffsmuster aber leider schlecht vermeiden lassen, da die Strahlen nicht parallel zu den Koordinatenachsen verlaufen und eine Transformation des Volumens (siehe Shear-Warp) auch nicht möglich ist, da der Rechenaufwand dafür zu groß wäre (und ich das Volumen 2x speichern müsste).
Wenn ich das ganze später noch einmal auf die Grafikkarte portieren sollte könnte ich das noch einmal probieren, indem ich das Volumen blockweise Transformiere.
 
Oh Raytracing! Spannendes Thema, mit dem ich mich bisher leider praktisch gar nicht auskenne. Aber ein paar Sachen leuchten mir hier noch nicht ein. Eigentlich geht es ja um das schnelle Darstellen von Bildern und dann kam eben noch das Thema der effizienten Berechnung auf. Aber so wie ich mir das Raytracing vorstelle sind das ja zwei komplett voneinander getrennte Themen. Oder versuchst du in Echtzeit zu rendern?

Um mal deutlicher zu machen, warum ich mich wunder:
nehmen wir mal an das Darstellen einer BMP-Datei dauert tatsächlich 100ms (was schon sehr viel wäre) und das Berechnen eines Bildes meinetwegen 1min. Dann macht die Performance der Bilddarstellung doch im Endeffekt überhaupt keinen spürbaren Unterschied.

Oder sollen Bildreihen gerendert werden, die dann anschließend als Film abgespielt werden? Das wäre durchaus auch ein interessantes Thema, wobei ich aber bisher auch eher wenig weiterhelfen kann :D

Und nochmal zur Graphikkarte: der Flaschenhals wird hier tatsächlich die Übertragungsbandbreite sein. Wenn man nicht die ganze Szene auf einmal in den Speicher laden kann, dann müsste man sie stückeln (parallelisierbar ist das Problem ja) und nach und nach berechnen. Ich denke, dass das in einigen Fällen schon Sinn machen kann. Aber das wäre ja vielleicht eine interessante Erweiterung, wenn das Gesamtprogramm schon steht. Die parallele Nutzung von Graka und CPU wäre natürlich noch besser oder sogar das verteilte Rechnen in einem Netzwerk :) *träum*

Was das Schreiben/Lesen angeht: die CPU hat ja in dem Sinne keinen Speicherplatz sondern muss auf die Festplatte zugreifen oder eben auf den RAM. Modernere Architekturen erlauben auch teilweise eine gemeinsame Nutzung vom L3-Cache zwischen CPU und Graka. Wichtig zu wissen ist auf jeden Fall: Festplattenzugriffe sind sau teuer, RAM immer noch teuer und Cache ok.
Wenn irgendwie machbar wäre es also eine gute Idee die Daten erstmal komplett in den RAM zu laden (nur einmal lesen von der Festplatte) und dann blockweise vorgehen, damit man den Cache voll ausreizt. Das ist keine Mikro-Optimierung, wie es im Internet oft behauptet wird. Dazu muss man sich nur mal die Zugriffszeiten von Festplatten und Cache anschauen und vergleichen :)
Eine Idee, wie man das umsetzen könnte, wäre zB im ersten Render-Pass einfach alle Strahlen erstmal zum Auftreffen zu rendern und alle querlaufenden Strahlen dann in einem zweiten Durchgang zu berechnen. Aber das ist jetzt nur eine erste Idee ohne tiefer mit der Materie Raytracing vertraut zu sein.

Was das Zeichnen angeht habe ich leider auch noch nichts brauchbares gefunden. Aber irgendwie müssen es dx und opengl ja auch schaffen ein Array direkt an den Bildschirm zu senden. Also müsste das ja eigtl auch gehen ohne dafür direkt nen Treiber schreiben zu müssen.
 
Fhnx schrieb:
Ist der Unterschied zwischen Blockweisen und random auch bei der CPU so groß?
Ja, ist er. Profiler ala Valgrind zeigen einem deswegen u.a. auch L1-Cache Misses an. Findet man in jedem Optimierungsguide, dass man zufällige/weitauseinanderliegende Zugriffe vermeiden soll. Allerdings hat eine CPU ziemlich viel L2 Cache, so das in vielen Fällen die Performance noch ausreicht, wenn alles drumherum um den Speicherzugriff langsam genug ist. Aber der Unterschied L1/L2 ist auch bemerkbar. Was kniffelig ist, da der L1 Cache winzig ist (64kb oder so)...
Den Unterschied kann man bspw. schon bemerken wenn man Vektoren (zusammenhängender Speicher) statt Listen (verteilter Speicher) verwendet, wenn man die Wahl dank gleicher theoretischer Laufzeitkomplexität...
 
Ich bin mir jetzt nicht ganz sicher, ob die Zahlen, die ich gefunden habe, richtig bzw. aktuell sind aber sie dürften einem zum Nachdenken anregen.

Zugriffszeit L1 Cache: etwa 1ns
Zugriffszeit Festplatte: etwa 10ms

Das ist natürlich noch eine ungefähre Größtenordnung. Jetzt kann man sich ziemlich leicht Programmbeispiele konstruieren, die Werte einlesen und mit diesen Beispielsweise 1000mal rechnen. Passt nicht sowieso das ganze Programm in den Cache, dann macht es einen riesen(!) Unterschied, ob ich die Werte 1000mal neu lade oder ob ich erst mit einem Wert neu arbeiten kann.
Natürlich liegt da noch der Arbeitsspeicher dazwischen, aber der ist auch immer langsamer als die Caches.

Aber nochmal zur Grafikausgabe: ich überlege grade, wie Programme wie der Windows Media Player oder VLC arbeiten. Die können ja nicht 25mal pro Sekunde per GDI ein Bild auf den Bildschirm zeichnen. Oder etwa doch?
 
Also das Programm soll die gerenderte Bilder in Echtzeit darstellen können. Im Moment ist die Performance noch zu schlecht, hoffe aber, dass ich mit mehreren Threads und weiteren Optimierungen etwas vernünftiges Basteln kann. Das Bild muss auch nicht durchgehend in höchster Auflösung und Qualität gerendert werden, es reicht wenn man grob erkennt, wie es sich dreht/bewegt. Dementsprechend hoffe ich, dass ich es hin bekomme.
Das Volumen liegt komplett im RAM. Wenn es nicht rein passt, wird es beim Einlesen entsprechend verkleinert und damit gerechnet.
Das Problem, wenn man das Volumen zerteilt, ist, dass man danach die einzelnen Teilergebnisse wieder zusammenführen muss und oft vllt auch unnötige Berechnungen macht. Deswegen kann ich mir vorstellen, dass der Vorteil gegenüber der 'einfachen' Methode sehr klein ausfällt. Kann man irgendwo 'erzwingen', dass etwas in den Cache geladen wird bzw. feststellen, was darin ist? In Visual Studio ist mir dazu noch nichts unter gekommen.
Im Moment ist der Raytracer noch relativ simpel gehalten. Erst wenn ich merke, dass daraus nichts wird, werde ich versuchen etwas zu verändern.
Genau so etwas wie VLC würde mich auch interessieren. VLC benutzt zwar auch die Grafikkarte zur Beschleunigung, ich glaube aber nicht, dass es mit OpenGL o.Ä. gemacht wird und wenn ja, ist für mich hauptsächlich die Darstellung der Bilder interessant.
 
@daemon777: Guck dir mal BitBlt an, damit schafft man um die 2 GB/s (da hardwarebeschleunigt).

@Fhnx:
"Erzwingen", dass etwas in die Cache kommt. _mm_prefetch http://msdn.microsoft.com/de-de/library/vstudio/84szxsww(v=vs.100).aspx
hat bei mir mal 30 % gebracht (und das bei ganz ordentlicher Cache-Locality).
Auslesen wird schwer, da der Task-Switch zu VS die Cache gleich wieder leerräumt.
 
@Fhnx

Dein Projekt hört sich langsam immer interessanter an. So etwas wollte ich eigentlich auch schon immer mal machen :)
An deiner Stelle würde ich auch erstmal eine funktionierende Version basteln, die so weit erstmal alles kann und dann hinterher optimieren. Das ist zwar nicht ganz der eleganteste Weg aber auf diesem Weg kommt man wenigstens zu einem Ergebnis, wenn man das Ganze nicht gerade beruflich macht.

Aber über eine Sache solltest du dir vorher schonmal im Klaren sein: mehr Rechnen bedeuted eben nicht unbedingt auch größere Laufzeit. Oft genug ist das Gegenteil der Fall :)
Aber wie gesagt: erstmal sollte das Grundgerüst stehen und dann kann man sich überlegen, wie man das Ganze in möglichst kleine Stücke verteilen kann.

@Hancock
Bei 20fps wären das ja tatsächlich noch 100MB pro Bild. Das dürfte definitiv ausreichen. Bei schätzungsweise 2M Pixel mit jeweils 24bit hätte man im BMP-Format ja "nur" 64Mb = 8MB pro Bild. Da könnte man ja tatsächlich auch noch lustige Dinge anstellen.

Nochmal @Fhnx
Bei OpenGL hat man unter Anderem durch Erweiterungen wie GLSL (Shader-Sprache) auch die Möglichkeit sehr effizient Shader zu benutzen, was vor Allem für Nachbearbeitung interessant sein könnte. Aber für ein "einfaches" Darstellen von Videos sollte man das eher nicht benötigen und zur Not kann man das auch noch selbst in der CPU machen. Das dürfte dann den Kohl auch nicht mehr fett machen.
Willst du das Ganze dann eigentlich auch als Video speichern, wie es herkömmliche RT-Programme machen oder geht es "nur" um die Echtzeitberechnung?

Und nochmal @Hancock
Das mit dem Prefatch leuchtet mir grade noch nicht so ganz ein und die Erklärung in der msdn ist auch etwas dürftig. Was soll denn "a location closer to the processor" bedeuten? Also vom Hauptspeicher in einen Cache? Aber wenn ich auf eine SPeicherstelle zugreifen will und diese nicht im L1-Cache ist, dann kommt es zu meinem Verständnis doch sowieso zu einem Cache-Miss und die gesamte Cache-Line wird reingeladen? Ist das also mehr eine NOP-Methode, die dafür dient eine Cache-Line auch im Cache zu halten? Ich bin grad etwas verwirrt. Mich zu verwirren schaffen diese dummen Dinger auch immer wieder :D


Und nochmal zum Auslesen des Caches: das hilft uns gar nicht so weiter. Im tatsächlich Betrieb würde uns das auslesen nur Zeit kosten und ich bin ir nicht mal sicher, ob das so wirklich gut geht. Abgesehen davon teilen wir den Cache ja mit allen anderen Programmen, die ablaufen, weswegen der Cache nie gleich aussehen wird. Was wir eben tun können ist das Lokalitätprinzip ausnutzen. Der Cache geht davon aus, dass häufig genutzte Speicherstellen auch in Zukunft häufig genutzt werden. Wenn wir ihm diesen Wunsch erfüllen, so ist er glücklich. Und er geht davon aus, dass Speicher, der gleichzeitig genutzt werden muss auch nah beieinander liegt.
Aber in der Theorie ist das immer alles so einfach. Bisher bin ich leider noch nicht so gut darin das auch umzusetzen. Aber ich denke das macht die Übung ^^
 
Nun kommt ein wenig Licht ins Dunkle... Raytracing ;-)

Ich habe vor einem Jahr einen Raytracer in Java geschrieben, der hat statisch ein paar Kugeln mit mehreren Lichtquellen beleuchtet. D.h. ohne Echtzeitanpassung der Perspektive. Bei 640x480 hat er glaub ich grob 200ms-400ms (auf einem 2GHz i7 Quadcore) gebraucht inkl. Darstellung. D.h. ein einer 2D-Schleife jeden Pixel berechnet und dann direkt dargestellt.
Die Implementierung war natürlich extrem primitiv und nicht sonderlich optimiert (bis auf die Threads), d.h. jeder Pixel wurde n mal berechnet (für jede Lichtquelle).

Wenn man sich aktuelle hochoptimierte Raytracer anschaut (z.B. der, der bei Intel entwickelt wird), dann sieht man ganz klar, dass sich eine Live-Darstellung ohne spezialisierte Hardware sehr schwer tut.

Mein Tip: Setzt einfach mal mal ein paar Timestamps:
1. vor dem Daten laden
2. nach dem Daten laden
3. vor dem Berechnen des Bildes
4. nach dem Berechnen
5. vor dem Darstellen
6. nach dem Darstellen

Dann solltest du am Ende sehen, dass die Berechnung im Vergleich zur BMP-Geschichte 100x bis 1000x so viel Zeit in Anspruch nimmt. (Das ist meist auch meine primitive Methode, um schnell auffällige Flaschenhälse zu finden und von der Schreibarbeit her sind es ja nur ein paar stupide Zeilen)
 
Zurück
Oben