Laden...

OpenGL für WinForms mit Intel HD Grafik

Erstellt von Limnfo vor 5 Jahren Letzter Beitrag vor 5 Jahren 2.658 Views
L
Limnfo Themenstarter:in
20 Beiträge seit 2017
vor 5 Jahren
OpenGL für WinForms mit Intel HD Grafik

Hallo!

Ich habe ein Programm geschrieben, das wiederholt komplexe 2D-Bilder auf einem WinForms-Panel zeichnet.
Soweit ich weiß, verwendet WinForms nur die CPU zum Zeichnen.
Da das Zeichnen (abhängig von der Komplexität des Bildes) aber einige Zeit in Anspruch nimmt, habe ich überlegt z.B. OpenGL zu verwenden. Ich muss aber dazu sagen, dass ich vorher noch nie mit OpenGL gearbeitet habe...
Außerdem hat mein PC keine Grafikkarte sondern nur Intel HD Graphics ...

Bringt es überhaupt etwas, OpenGL zu verwenden, wenn man keine Grafikkarte hat?
Kann man pauschal sagen, dass das Zeichnen z.B. doppelt so schnell ist, oder hängt das von der Komplexität des Bildes bzw. vom Grafikchip ab?

Falls es von Relevanz sein sollte, hier meine CPU bzw. die Grafik:
i7-5600U
Intel HD Graphics 5500

Limnfo

G
154 Beiträge seit 2015
vor 5 Jahren

Was meinst Du denn mit "komplex"? Die Größe des Bildes oder wird der Inhalt des Bildes erst berechnet und das dauert?

16.806 Beiträge seit 2008
vor 5 Jahren

WinForms selbst zeichnet gar nicht; sondern ist nur ein Wrapper für GDI+
Und GDI+ hat keine hardware-acceleration für 3D, aber für 2D. Dafür wird der Legacy Driver verwendet.

Es kommt jedoch auf den Video Treiber an, ob dieser Legacy Driver auch implementiert ist.

Kann man pauschal sagen, dass das Zeichnen z.B. doppelt so schnell ist, oder hängt das von der Komplexität des Bildes bzw. vom Grafikchip ab?

Natürlich kann man keine pauschalen Zeitangaben machen.

6.911 Beiträge seit 2009
vor 5 Jahren

Hallo Limnfo,

da es mehrere Möglichkeiten zum Zeichnen gibt, wäre es auch gut zu wissen ob die 2D-Bilder eher eine Pixel- od. Vektorgrafik sind.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

L
Limnfo Themenstarter:in
20 Beiträge seit 2017
vor 5 Jahren

@Gimmick
die Größe des Bildes entspricht immer der Größe des sichtbaren Panels und bleibt dementsprechend (außer bei Form_Resize) gleich groß.
das Bild zeigt im Endeffekt einen Ausschnitt aus binär-Daten. Die Daten enthalten beispielsweise die Informationen, mit denen man ein Bild von der Größe von 30000 x 1000 px zeichnen könnte. Das Panel zeigt aber nur die Daten eines z.B. 800 x 800 px großen Bereichs an. Durch Scrollbalken kann der User den jeweiligen Ausschnitt, den er sehen möchte, anpassen.
Für das Zeichnen errechne ich beim Paint-Event des Panels u.a. aufgrund der Scrollbalkenposition und des Zoomlevels die relevanten Daten und zeichne diese aufs Panel. Ich glaube nicht, dass das Daten Selektieren so lange dauert, denn man kann beim Zeichnen zusehen (das Bild wird innerhalb von 1-2 Sekunden von links nach rechts am Panel gezeichnet).

@Abt
danke für die Korrekturen/Infos.

@gfoidl
ich zeichne Pixelgrafiken. Im Wesentlichen verwende ich Graphics.FillRectangle()

Limnfo

4.931 Beiträge seit 2008
vor 5 Jahren

Zeige mal den relevanten Codeteil der Paint-Methode. Erzeugst du die GDI-Resourcen (Pen, Brush, ...) immer wieder neu?

Du könntest auch den Ausschnitt in eine Bitmap zeichnen und diese dann blitten (und nur bei Veränderung des Ausschnitts die Bitmap neu zeichnen).

L
Limnfo Themenstarter:in
20 Beiträge seit 2017
vor 5 Jahren

Es handelt sich um ein Programm, welches Daten eines Echolots darstellt.
breiteA und breiteC sind immer >0 und ≤ 1, das heißt, dass die vertikalen Striche, welche ich zeichne, nur max. 1 Pixel breit sind...

Hier meine Paint-Methode, unrelevante Bereiche habe ich rausgelöscht [...] :


private void EchoPanel_Paint(object sender, PaintEventArgs e)
{
    // [...] // Zeichenbedingungen
    
    using (Graphics g = e.Graphics)
    {
        // [...] // Datenbereich anhand Scrollbar-Positionen ausrechnen, etc.

        // Zeichenursprung um Korrektur nach unten verschieben
        double korrektur = _schwingertiefe / _aufloesung * _ansicht.ZoomVertical;
        g.TranslateTransform(0, (int)korrektur);
        
        // Datensätze durchgehen
        for (int d = startX; d < endX; d++)
        {
            // x-Koord ausrechnen
            x = d * _ansicht.ZoomHorizontal;

            if (_echoDaten[d] != null)
            {
                // iMax ausrechnen
                float iMax = endY > _echoDaten[d].Length ? _echoDaten[d].Length : endY;

                for (int i = 0; i < iMax; i++)
                {
                    // y-Koord ausrechnen
                    y = i * _ansicht.ZoomVertical;

                    if (_ansicht.DrawSchwingerkennung && i < 2)
                    {
                        // Schwingerkennung
                        g.FillRectangle(_ansicht.FarbeSchwingerkennung, x, y, _ansicht.ZoomHorizontal, _ansicht.ZoomVertical);
                    }
                    else if (_echoDaten[d][i] == 0xFF)
                    {
                        // kein Echo - nichts zeichnen
                    }
                    else if (_echoDaten[d][i] == 0x00)
                    {
                        // Echo
                        if (getBuchstabe(_echoDaten[d][0]) == 'A')
                        {
                            // Echo Typ A
                            g.FillRectangle(_ansicht.FarbeEchoA, x, y, breiteA, _ansicht.ZoomVertical);
                        }
                        else if (getBuchstabe(_echoDaten[d][0]) == 'C')
                        {
                            // Echo Typ C
                            g.FillRectangle(_ansicht.FarbeEchoC, x, y, breiteC, _ansicht.ZoomVertical);
                        }
                    }
                    else
                    {
                        // Schwingerkennung ausblenden
                        if(i < 2)
                        {
                            continue;
                        }


                        // teilweise Echo
                        int[] bits = getBits(_echoDaten[d][i]);
                        for (int bit = 0; bit < 8; bit++)
                        {
                            float ypx = (i + ((bit) / 8.0f)) * _ansicht.ZoomVertical;
                            float bithoehe = _ansicht.ZoomVertical / 8.0f;

                            if (bits[bit] == 1)
                            {
                                // kein Echo - transparent zeichnen
                                g.FillRectangle(_ansicht.FarbeTransparent, x, ypx, breiteA, bithoehe);
                            }
                            else if (bits[bit] == 0)
                            {
                                // Echo - grau zeichnen
                                if (getBuchstabe(_echoDaten[d][0]) == 'A')
                                {
                                    g.FillRectangle(_ansicht.FarbeEchoA, x, ypx, breiteA, bithoehe);
                                }
                                else if (getBuchstabe(_echoDaten[d][0]) == 'C')
                                {
                                    g.FillRectangle(_ansicht.FarbeEchoC, x, ypx, breiteC, bithoehe);
                                }
                            }
                        }
                    }
                }
            }


            // Zeichenursprung um Korrektur nach oben verschieben
            g.TranslateTransform(0, (int)-korrektur);

            // [...] // Tiefenlinien zeichnen, etc.
        }
    }
}

Für das Zeichnen des Bildes gibt es zwei Szenarien:

  1. Live-Darstellung - die Daten werden vom Lot empfangen, verarbeitet und sofort gezeichnet.
  2. Replay. Nach erfolgter Aufzeichnung werden die Daten nochmal angesehen.

Blitten habe ich noch nicht gehört, bzw. je verwendet, aber es gerade auf Wikipedia nachgelesen.
Blitten würde ich dementsprechend nur für das 2. Szenario, wo die Daten konstant bleiben, verwenden können, oder?

Die Live-Darstellung ist wie eine Laufzeile, bei der die Daten von rechts nach links durchlaufen. Die Daten kommen mit max. 25 Hz, dass heißt, ich müsste immer vom aktuellen Bild alles bis auf den ältesten Datensatz (die linkeste Pixelspalte) nehmen, um 1 px nach links versetzen und rechts daneben den aktuellen Datensatz zeichnen - und das ganze 25 mal in der Sekunde... bringt da blitten etwas?

Lässt sich von meinem Code bzw. meinen Erklärungen zu Anwendung darunter abschätzen, ob hier zB. OpenGL sinnvoll ist?

4.931 Beiträge seit 2008
vor 5 Jahren

Lösche unbedingt diese Zeile (bzw. nur das using!):


using (Graphics g = e.Graphics)

Dadurch 'disposed' du am Ende deiner Paint-Methode jedesmal das Graphics-Objekt und es muß vom System wieder erzeugt werden (du hast es ja nicht selbst erzeugt)!

Und wie groß sind deine beiden for-Schleifen, also endX - startX und iMax, d.h. in welcher Größenordnung liegt die Anzahl der Gesamtiterationen (einige Tausend, Hunderttausend oder sogar Millionen)?

Aber ansonsten sieht deine Methode gut aus.

Und bgzl. blitten: Ja, gerade dein Szenario, wo du immer nur rechts eine neue Pixelspalte einfügst.
Zeichne einfach auf ein größeres Bitmap. Dann kannst du mittels einer der Graphics.DrawImage-Überladungen ("Zeichnet den angegebenen Bereich vom angegebenen Image in der angegebenen Größe an der angegebenen Position.") den passenden Ausschnitt in der Paint-Methode zeichnen.
Du müßtest dann nur noch umsetzen, wenn die max. Größe des Bitmaps erreicht wurde, eine neue Bitmap anzulegen und dann den letzten Ausschnitt dort hin zeichnen (ebenfalls mit der selben Methode). Mittels Graphics.FromImage(Image) erzeugst du dann den Graphics-Kontext dafür (hierfür aber dann using einsetzen 😉.

Dies geht viel schneller, als jedesmal immer wieder den gesamten Ausschnitt neu zu berechnen!

PS: Lies dir auch mal die verlinkten Artikel bei [FAQ] Flackernde Controls vermeiden / Schnelles, flackerfreies Zeichnen durch.

L
Limnfo Themenstarter:in
20 Beiträge seit 2017
vor 5 Jahren

Danke für den Hinweis mit 'using'!

endX - startX hat je nach Fenstergröße einen Wert zwischen 1000 und 2000 und iMax liegt zwischen 100 und 1000.

Etwas, worüber ich mir noch Sorgen mache, ist die Maximalgröße meiner "Vorlage-Bitmap". Diese kann manchmal eine Größe von 30000 x 4000 px haben - ist das ein Problem?
Allerdings reicht mir wahrscheinlich eine Farbtiefe von 16 bit, was den RAM-Bedarf gering halten dürfte...

Werde mal in den nächsten Tagen die Paint-Methode umschreiben und mich dann wieder melden!

G
154 Beiträge seit 2015
vor 5 Jahren

So ganz klar ist mir die Methode ehrlichgesagt nicht.
Du hast in Deinen FillRectangel()-Aufrufen dem Namen nach immer eine Farbe und eine zoom-abhängige Höhe/Breite als Parameter. Heißt das, dass du für jeden Wert aus deinen Rohdaten ein einfarbiges Rechteck zeichnest?

Ein ganzes Bitmap zu zeichnen geht deutlich schneller.

Ich würde sowas machen wie:

  1. Über Scrollposition und Zoom die Größe und Position des "Datenfensters" bestimmen.

  2. Über HöheBreite3 ein 24bpp 1D-Array erstellen

  3. Die Daten aus dem Fenster Farben zuordnen, in das Array schreiben und das Array über irgendeine Methode auf die Ausgabe skalieren (nearest neighbor evtl. da findet man auch gut was zu).

  4. Die Daten aus dem Array in das Bitmap schreiben (z.B. mit Marshal.Copy/Lockbits)

  5. Bitmap Anzeigen in Picturebox oder direkt zeichnen mit drawImage.

Wenn ich das richtig verstanden habe werden nicht die gesamten Daten mit 25Hz erneuert, sondern alle 40ms kommt eine neue Zeile und die alten Daten werden nach und nach überschreiben? Man könnte dann ja auch Scheibchen-Bilder erstellen oder einen TextureBrush benutzen, müsste man wohl ausprobieren.

L
Limnfo Themenstarter:in
20 Beiträge seit 2017
vor 5 Jahren

@Th69
Habe das blitten im Programm umgesetzt und bin begeistert! Es funktioniert total schnell! Das Bitmap ist je nach größe in 1-3 Sekunden erstellt und das eigentliche Zeichnen auf das Panel passiert in Sekundenbruchteilen! vielen vielen Dank für den Hinweis!
Bin nun schon sehr gespannt, ob ich das mit Live-Daten ebenso performant umsetzen kann!

@Gimmick
Ja, im Prinzip zeichne ich für jedes Byte ein einfarbiges Rechteck. Es sei denn, das Byte hat einen Wert ungleich 0x00 bzw. 0xFF, dann zeichne ich für jedes Bit ein einfarbiges Rechteck.
Deine Anleitung klingt gut um die Daten auf das Bitmap zu zeichnen, vll kann ich das dadurch nochmals beschleunigen. Allerdings ist es mir noch nicht so ganz klar - melde mich dazu nochmal in den nächsten Tagen.

Ja genau, mit 25 Hz kommt jeweils nur ein 1 px breiter Streifen zum Bild dazu. Die Rohdaten werden regelmäßig gespeichert, das Bild sollte aber jedenfalls immer die letzten zb. 20000 Datensätze anzeigen.

Btw: wünsche euch frohe Weihnachten!

4.931 Beiträge seit 2008
vor 5 Jahren

Das freut mich (für dich) und danke für das Feedback.

Gerade bei Empfehlungen zu (größeren) Umbauten des Programmcodes durch das Forum hier, erhält man nicht immer Feedback.
Und noch viel Spaß und Erfolg beim Update der Live-Daten.

Ich hoffe aber, du hast gestern die Bescherung nicht verpasst (wenn ich so auf die Uhrzeit deines Beitrags schaue 😉?