2009-06-05 26 views
9

Ich entwickle eine beibehaltene Moduszeichnung in GDI +. Die Anwendung kann einfache Formen auf eine Arbeitsfläche zeichnen und grundlegende Bearbeitungen vornehmen. Die Mathematik, die dies tut, ist auf das letzte Byte optimiert und ist kein Problem. Ich zeichne auf einem Panel, das den eingebauten Controlstyles.DoubleBuffer verwendet.Winforms: Wie beschleunige ich Invalidate()?

Jetzt entsteht mein Problem, wenn ich meine App auf einem großen Monitor maximiert (HD in meinem Fall). Wenn ich versuche, eine Linie von einer Ecke der (großen) Leinwand zu der diagonal gegenüberliegenden Seite zu zeichnen, beginnt sie zu verzögern und die CPU geht hoch.

Jedes grafische Objekt in meiner App hat eine Boundingbox. Wenn ich also die Boundingbox einer Zeile ungültig mache, die von einer Ecke der maximierten App zur gegenüberliegenden Diagonale geht, ist diese Boundingbox virtuell so groß wie die Canvas. Wenn ein Benutzer eine Linie zeichnet, erfolgt diese Ungültigkeitserklärung der Boundingbox auf dem Ereignis mousemove, und es ist eine deutliche Verzögerung sichtbar. Diese Verzögerung ist auch vorhanden, wenn die Linie das einzige Objekt auf der Arbeitsfläche ist.

Ich habe versucht, dies in vielerlei Hinsicht zu optimieren. Wenn ich eine kürzere Linie zeichne, sinkt die CPU und der Lag. Wenn ich das Invalidate() entferne und den anderen Code behalte, ist die App schnell. Wenn ich eine Region (die nur die Figur überspannt) anstelle der Boundingbox für ungültig erklären, ist sie genauso langsam. Wenn ich die Boundingbox in eine Reihe kleinerer Boxen aufspalte, die Rücken an Rücken liegen, und somit den Invalidierungsbereich verkleinern, kann keine sichtbare Leistungssteigerung festgestellt werden.

So bin ich ratlos hier. Wie kann ich die Ungültigerklärung beschleunigen?

Nebenbei bemerkt, beide Paint.Net und Mspaint leidet unter den gleichen Shortcommings. Word und PowerPoint scheinen jedoch in der Lage zu sein, eine Zeile wie oben beschrieben ohne Verzögerung und ohne CPU-Last zu zeichnen. So ist es möglich, die gewünschten Ergebnisse zu erzielen, die Frage ist wie?

+0

Word und Powerpoint verwenden nicht GDI + ... Ich glaube nicht, GDI + wurde jemals für seine Geschwindigkeit gelobt (über Plain GDI). –

+0

Haben Sie versucht, mit einem Profiler zu sehen, wo die Verlangsamung tatsächlich auftritt? Ich habe einen Haufen dieser Art von Dingen gemacht (vor Ewigkeiten in Delphi) und habe Invalidate() nie gefunden, um eine wahrnehmbare Menge an Zeit zu nehmen. Das Malen hingegen war "Spaß", schnell genug zu laufen, um eine Linie zu gummieren. – Bevan

+0

Nun, ich glaube Delphi ist auf jeden Fall viel schneller für diese Art von Arbeit. Wie Henk sagt, GDI + ist überhaupt nicht bekannt für Geschwindigkeit, wahrscheinlicher Mangel davon. Der eigentliche Zeichencode ist jedoch nicht der Engpass. Die Ungültigkeit der großen Fläche scheint zu sein. Ich überlege mir, meinen eigenen Backbuffer zu verwenden und das Bild manuell zu blittieren, um zu sehen, ob ich einen Leistungsgewinn erzielen kann. Können Sie einen Profiler speziell für VS2005 empfehlen? – Pedery

Antwort

7

Für grundlegende Anzeigeelemente wie Linien, sollten Sie sie nach oben in ein paar Teile zu brechen, wenn Sie unbedingt ihre gesamte entkräften müssen Grenzen pro Zeichenzyklus.

Der Grund dafür ist, dass GDI + (sowie GDI selbst) Bereiche in rechteckigen Formen ungültig macht, genau wie Sie mit Ihrer Bounding Box angeben. Sie können dies selbst überprüfen, indem Sie einige horizontale und vertikale Linien im Vergleich zu Linien testen, bei denen die Neigung dem Aspekt Ihres Anzeigebereichs ähnelt.

Also sagen wir, Ihre Leinwand ist 640x480. Wenn Sie eine Linie von 0,0 bis 639 479 zeichnen; Invalidate() wird den gesamten Bereich von 0,0 bis 639,0 oben auf 0,479 bis 639,479 am unteren Rand ungültig machen. Eine horizontale Linie von beispielsweise 0,100 bis 639,100 ergibt ein Rechteck, das nur 1 Pixel hoch ist.

Regionen das gleiche Problem haben, weil Regionen als Sätze von horizontalen Ausdehnungen zusammen gruppiert behandelt werden. Bei einer großen diagonalen Linie, die von einer Ecke zur anderen verläuft, muss eine Region, um der von Ihnen festgelegten Begrenzungsbox zu entsprechen, entweder jede Gruppe von Pixeln auf jeder vertikalen Linie oder die gesamte Begrenzungsbox angeben.

So als Lösung, wenn Sie eine sehr große Linie haben, brechen sie in Viertel oder Achtel und Leistung sollte deutlich erhöhen. Wiederholen Sie das obige Beispiel, wenn Sie nur zwei Teile für zwei Teile teilen - Sie reduzieren den gesamten ungültigen Bereich auf 0,0 x 319,239 plus 320,240 x 639,479.

Hier ist ein visuelles Beispiel für eine Viertelaufteilung. Der rosa Bereich ist ungültig. Leider wird SO mir nicht erlauben, Bilder oder mehr als einen Link zu posten, aber das sollte genug sein, um alles zu erklären.

(Linie Split in Quarters, Gesamt Invalidated Fläche 1/4 der Fläche)

a 640x480 extent with 4 equal sized boxes carved behind a line drawn across the diagonal

Oder stattdessen einen Begrenzungsrahmen anzugeben, können Sie Ihr Updates, so dass Sie zu prüfen, Umschreiben Zeichnen Sie nur die Teile von Elementen, die mit der Region übereinstimmen, die aktualisiert werden muss. Es hängt wirklich davon ab, wie viele Objekte an einem gezeichneten Update teilnehmen müssen. Wenn Sie Tausende von Objekten in einem bestimmten Rahmen haben, sollten Sie alle ungültigen Bereiche ignorieren und nur die gesamte Szene neu zeichnen.

+0

Hallo, und danke für Ihre Antwort. Leider, wenn Sie meine gelesen hatten Antwort auf Antwort # 2, ich habe schon versucht, was du beschreibst und es funktioniert nicht. Das ist jedoch überraschend. Die Szene wird wahrscheinlich nicht mehr als ein paar Dutzend Elemente enthalten, aber das liegt wirklich an dem Benutzer Auf diese Weise hat es praktisch keinen Gewinn gemacht, nur ein Viertel der Zeile (als Test) zu aktualisieren, hat sich (wie erwartet) verbessert, und in diesem Fall gibt es fast eine lineare Korrelation zwischen der CPU und dem aktualisierten Bereich – Pedery

+0

Ich muss unterstreichen Das ist nur ein Problem auf meinem großen Bildschirm, 1980x1050, wenn ich das Programm maximiert. Die CPU-Last steigt auf etwa 50%, Redraws verlangsamt sich erheblich und sogar Mausereignisse werden aus der Nachrichtenschleife übersprungen. – Pedery

+0

Hmmm, I habe einen Frage, dann. Behandeln Sie die Nachricht WM_ERASEBKGND? Wenn dies nicht der Fall ist, fügen Sie in Ihrer App einen Handler dafür hinzu, und unterdrücken Sie jede Zeichnung für diese Windows-Nachricht für das Fenster, in dem sich Ihre GDI + -Leinwand befindet. Da Sie die gesamte Szene im GDI + -Leinwand zeichnen, sollte dies Ihre App einige Mühe sparen. Versuchen Sie auch, Spy ++ zu verwenden, um die Reihenfolge und die Häufigkeit von WM_PAINT-Nachrichten zu überprüfen, die an dieses Fenster gesendet werden, um zu sehen, ob doppelte oder wiederholte Neuzuordnungsanforderungen auftreten. – meklarian

1

Zur Verdeutlichung: Zeichnet der Benutzer eine gerade Linie, oder besteht Ihre Linie tatsächlich aus einer Reihe von Liniensegmenten, die Mauspunkte verbinden? Wenn die Linie von dem Ursprung zu dem aktuellen Maus Punkt eine gerade Linie ist, nicht entwerten() und stattdessen eine XOR Bürste verwenden, um eine Linie zu zeichnen undoable und dann undraw die vorhergehende Zeile, nur wenn der Benutzer Invalidierung Zeichnung erfolgt.

Wenn Sie eine Reihe von kleinen Liniensegmenten zeichnen, machen Sie nur die Begrenzungsbox des letzten Segments ungültig.

+0

Nun, dies ist eine CAD-Anwendung und ich habe die "Linie" -Metapher zur Vereinfachung verwendet. In Wirklichkeit zeichne ich ein schmales Rechteck in einem Winkel, also besteht es aus vier GDI + Zeichnungslinien-Operationen plus einer Ungültigkeitserklärung der Leinwand.Die Linien können jede Farbe haben und viele andere grafische Objekte auf der Bühne schneiden, also bin ich mir nicht sicher, ob XOR der richtige Weg ist ... – Pedery

1

Wie wäre es einen anderen Thread, „post-Updates“ auf die reale Leinwand mit.

Image paintImage; 
private readonly object paintObject = new object(); 
public _ctor() { paintImage = new Image(); } 

override OnPaint(PaintEventArgs pea) { 
    if (needUpdate) { 
     new Thread(updateImage).Start(); 
    } 
    lock(paintObject) { 
     pea.DrawImage(image, 0, 0, Width, Height); 
    } 
} 

public void updateImage() { 
    // don't draw to paintImage directly (it might cause threading issues) 
    Image img = new Image(); 
    using (Graphics g = img.GetGraphics()) { 
     foreach (PaintableObject po in renderObjects) { 
      g.DrawObject(po); 
     } 
    } 
    lock(paintObject){ 
     using (Graphics g = paintImage.GetGraphics()) { 
      g.DrawImage(img, 0, 0, g.Width, g.Height); 
     } 
    } 
    needUpdate = false; 
} 

Nur eine Idee, so habe ich den Code nicht getestet ;-)

+0

Das könnte funktionieren, aber ich bezweifle es. Zunächst einmal müsste ich von der integrierten GDI + -Doppelpufferung (die ziemlich gut funktioniert) abrücken, und zweitens scheint der Engpass die Invalidisierung selbst zu sein. Alle anderen Overhead ist vernachlässigbar. Da GDI + reine Software-Grafikmanipulation ist (keine HW-Beschleunigung), würde es lediglich bedeuten, das Problem von einem Thread zum nächsten zu verschieben. Ich bin daran interessiert zu wissen, ob jemand Erfahrung mit der Optimierung von Rendering durch Verwendung von separaten Threads obwohl. Eine andere Möglichkeit könnte darin bestehen, einen HW-beschleunigten Ansatz zu verwenden. – Pedery

+0

Hm, wenn es die "Invalidate()" ist, die langsam ist, wie wäre es nur mit OnPaint (new PaintEventArgs (CreateGraphics(), null || constRectangle)? – Patrick

0

Wenn Sie Regionen richtig verwendet haben, wie Sie beschrieben haben, sollte es schnell genug funktionieren. Es bedeutet, dass Ihr Problem durch einige Umstände oder Fehler verursacht wird, die Sie nicht beschrieben haben. Bitte machen Sie folgendes:

Erstellen Sie den kleinsten Code, der das Problem zeigt. Dies sollte immer der zweite Schritt bei der Fehlersuche seines

, wenn Sie nicht in der Lage sind, die Antwort in einigen Minuten zu finden. Wenn Sie Ihr Problem nicht mit einer kleineren Implementierung reproduzieren können, bedeutet das einfach, dass es auf einige Ihrer Fehler zurückzuführen ist, die hier nicht beschrieben wurden. Sonst können wir helfen.

1

Sie können Invalidate nicht wirklich beschleunigen. Der Grund, warum es langsam ist, ist, weil es ein WM_PAINT-Ereignis in der Nachrichtenwarteschlange veröffentlicht. Das wird gefiltert und schließlich wird OnPaint aufgerufen. Was Sie tun müssen, ist während des MouseMove-Ereignisses direkt in Ihre Steuerung zu malen.

In jedem Steuer ich das tun erfordert ein gewisses Maß an Flüssigkeit Animation meine OnPaint Ereignis eine PaintMe Funktion nur allgemein nennt. Auf diese Weise kann ich diese Funktion verwenden, um das Steuerelement jederzeit neu zu zeichnen.

+0

Sicher, und ich kann sehen, was du sagst. Du meinst, dass ich Update() mein Steuerelement anstelle von ungültig machen sollte, basierend auf einem FPS-Schema. Dies wird jedoch die CPU wahrscheinlich noch weiter belasten. Denken Sie daran, dass dies nur ein Problem ist, wenn ich große Teile eines HD-Bildschirms ungültig mache. Ich war wirklich auf der Suche nach Antworten, wie zum Beispiel, ob dieser Prozess durch manuelles Blitting beschleunigt werden könnte, oder ob das Malen auf einem GDI + bmp und das Verwenden von DX, um das Bild auf eine DX-Oberfläche zu malen, es schneller machen würde. – Pedery