myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
» Regeln
» Wie poste ich richtig?
» Forum-FAQ

Mitglieder
» Liste / Suche
» Wer ist wo online?

Ressourcen
» openbook: Visual C#
» openbook: OO
» Microsoft Docs

Team
» Kontakt
» Übersicht
» Wir über uns

» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Knowledge Base » Artikel » [Artikel] Zeichnen Optimieren / Schnelles zeichnen
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

[Artikel] Zeichnen Optimieren / Schnelles zeichnen

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
dr4g0n76
myCSharp.de-Poweruser/ Experte

avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.880
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland


dr4g0n76 ist offline

[Artikel] Zeichnen Optimieren / Schnelles zeichnen

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ich habe hier im Forum jetzt einige Stunden verbracht, um zu sehen, wie man eigentlich in .NET einen Zeichenvorgang optimieren kann.

Es gibt hier einfach zu viele Threads zu diesem Thema. Aber nirgendwo gebündelt, wie man etwas optimiert. Oder ich hab einfach die falschen Stichworte benutzt. Man möge es mir hiermit nachsehen.

Da ich auch gerade dieses Problem habe, kam ich auf die Idee, erst mal alle Methoden von

Graphics

und

Control

durchzusuchen.

Wenn man die APIs hinzunimmt sind natürlich auch BitBlt und Co mit von der Partie.

Natürlich sind auch die Implementierungen der Algorithmen nicht zu verachten.
Bei z.B. 1000 Shapes kann es sinnvoll (ist es sicher auch) sein, zu überprüfen, was und ob überhaupt neu gezeichnet werden muss.
Auf die Algorithmen möchte ich später eingehen.

Was ich jetzt möchte ist hier diskutieren, wie man Zeichenvorgänge optimieren kann.

Dabei gilt folgende Bedingung:

Es wird nicht mit OpenGL oder DirectX gezeichnet, sondern nur mit GDI.

Folgende Möglichkeiten habe ich bisher gefunden:

Methoden von Graphics:

IsVisible: hiermit kann geprüft werden, ob der Punkt/Rechteck usw. des Objekts überhaupt sichtbar ist.
IsVisibleClipEmpty: Überprüfen, ob im sichtbaren Clipbereich überhaupt was zu sehen ist
IsClipEmpty: Wie Clip-Empty auch für unsichtbaren Bereich
Clip: Region in der überhaupt gezeichnet wird
ClipBounds: Region umschliessendes Rechteck (Begrenzungen)
CompositingMode: enum SourceCopy/SourceOver
CompositingQuality: Qualitätsniveau während des Zusammensetzens
BeginContainer: Speichert GrafikContainer mit aktuellem Zustand
EndContainer: GrafikContainer der wieder hergestellt werden soll
Flush: Erzwingt zeichnen aller anstehenden Grafikoperationen, es wird nicht gewartet bis alles gezeichnet wurde.
InterpolationMode: Gibt an, wie z.B. Grafik beim verkleinern neu berechnet wird.
PageScale: Skalierung zwischen globalen Einheiten und Seiteneinheiten
PageUnit: Maßeinheiten (Page)
PixelOffsetMode: Der Pixeloffsetmodus bestimmt den Offset von Pixeln bei der Darstellung
TextRenderingHint: Angeben ob Text z.B. mit Antialiasing wiedergegeben wird.

Wie ihr sicher gemerkt habt, hab ich dabei versucht diese Befehle so weit wie möglich mit meinen eigenen Worten zu beschreiben.
Ansonsten hab ich einfach die Beschreibung von Microsoft übernommen.

Ich will einfach dahin kommen z.B. 1000 eigens definierte Objekte relativ schnell zu zeichnen.

Wenn sich der Algorithmus und die Implementation darum kümmern was neu gezeichnet werden muss und ggf. einige Optimierungsschalter gesetzt werden, sollte
es doch nicht so schwierig sein das ganze im Millisekunden Bereich zu zeichnen, oder was meint ihr?

Ich versuche also 1000 Grafik-Objekte zu zeichnen.

Diese sind von einer ganz einfachen Klasse CShape abgeleitet.

C#-Code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace Test
{
    class CShape
    {
        private Point m_ptLocation = Point.Empty;

        private bool m_bNeedsUpdate = false;
        public bool NeedsUpdate
        {
            get { return this.m_bNeedsUpdate; }
            set { this.m_bNeedsUpdate = value; }
        }

        public virtual Point Location
        {
            get { return this.m_ptLocation; }
            set { this.m_ptLocation = value; }
        }

        public virtual void Draw(Graphics g)
        {
        }

    }
}

Ob die Methode NeedsUpdate benötigt wird, ist noch fraglich. Lassen wir die einfach mal drin.


Und so sieht die Methode CRectangle aus:

C#-Code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace Test
{
    class CRectangle:CShape
    {
        Random m_Random = new Random(DateTime.Now.Millisecond);
        private Rectangle m_Rectangle = Rectangle.Empty;
        public Size Size = new Size(150, 40);
        private Color m_Color = Color.Blue;
        private Brush m_Shadow = new SolidBrush(Color.FromArgb(128,0,0,0));
        private Brush m_ColorBrush = null;
        private Pen m_BorderPen = new Pen(new SolidBrush(Color.Black),2);
        private Brush m_BlackBrush = new SolidBrush(Color.Black);
        private SizeF m_szTextSize = SizeF.Empty;
        private Font m_Font = new Font("Arial", 15);

        private string m_sText = "Das ist ein test";
        public CRectangle()
        {
            this.m_Random       = new Random(this.GetHashCode());
            this.m_Rectangle    = new Rectangle(this.Location, this.Size);
            int red             = m_Random.Next(0, 255);
            int green           = m_Random.Next(0, 255);
            int blue            = m_Random.Next(0, 255);
            this.m_Color        = Color.FromArgb(red, green, blue);
            this.m_ColorBrush   = new SolidBrush(this.m_Color);
        }

        public override Point Location
        {
            get{ return this.m_Rectangle.Location; }
            set{ this.m_Rectangle.Location = value; }
        }

        public override void Draw(Graphics g)
        {
            Rectangle rectShadow = new Rectangle(new Point(this.Location.X + 15, this.Location.Y + 15),this.Size);
            g.FillRectangle(m_Shadow, rectShadow);
            g.FillRectangle(m_ColorBrush, this.m_Rectangle);
            g.DrawRectangle(m_BorderPen, this.m_Rectangle);

            if (m_szTextSize.IsEmpty)
            {
                m_szTextSize = g.MeasureString(this.m_sText, this.m_Font);
            }
            g.DrawString(this.m_sText, this.m_Font, this.m_BlackBrush, new PointF(this.Location.X + this.Size.Width / 2 - m_szTextSize.Width / 2, this.Location.Y + this.Size.Height / 2 - m_szTextSize.Height / 2));
        }

        public Rectangle Rectangle
        {
            get { return this.m_Rectangle; }
        }

        public bool Hit(Point pt)
        {
            return this.m_Rectangle.Contains(pt);
        }


        public Color Color
        {
            get { return this.m_Color; }
            set { this.m_Color = value; }
        }
    }
}

PS: Weitere Hinweise gibt es in  "Gezieltes OwnerDrawing" - schnelles Zeichnen bewegter Objekte und  Schnelle GDI(+) Grafik - wie? [Parallax Scrolling].

Dieser Beitrag wurde 7 mal editiert, zum letzten Mal von dr4g0n76 am 23.11.2006 23:14.

21.11.2006 23:15 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.880
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland

Themenstarter Thema begonnen von dr4g0n76

dr4g0n76 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ich habe hier mal das Testprojekt angehängt.

Wichtig ist in diesem Falle nicht, die Interna von CRectangle oder CShape wesentlich zu verändern, sondern vorrangig zu prüfen, was muss neu gezeichnet werden und wann.

Hupps. Die Stoppuhr muss ja noch resetted werden.


Dateianhang:
unknown Test.rar (210,32 KB, 1.312 mal heruntergeladen)

Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von dr4g0n76 am 22.11.2006 02:22.

22.11.2006 02:05 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.880
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland

Themenstarter Thema begonnen von dr4g0n76

dr4g0n76 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Mal so zwischendrin:

Nein, es muss niemand hierauf antworten. Ich werde hier einfach versuchen mit der Zeit alles zu optimieren und hier meine Ergebnisse aufzuschreiben.

Erwähnenswert wäre vielleicht noch, dass in die Zeichenroutine eine Stopwatch eingebaut ist. Wer möchte kann die Messdaten auch eine Datei schreiben.
22.11.2006 02:10 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
VizOne VizOne ist männlich
myCSharp.de-Mitglied

avatar-1563.gif


Dabei seit: 26.05.2004
Beiträge: 1.373
Entwicklungsumgebung: VS 2010


VizOne ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo,

Ich habe mich zwar nicht im Detail mit deiner Implementierung beschäftigt, aber ich möchte darauf hinweisen, dass es sehr gute bestehende Techniken gibt, effizient nicht sichtbare Teile beim zeichnen auszuschließen.

Dieses Konzept wird im Snippet  "Gezieltes OwnerDrawing" - schnelles Zeichnen bewegter Objekte von ErfinderDesRades umgesetzt.

Als weiteres Beispiel so etwas umzusetzen sein hier Quadtrees genannt.

Viele Grüße,
Andre
22.11.2006 08:38 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
norman_timo norman_timo ist männlich
myCSharp.de-Mitglied

avatar-1775.jpeg


Dabei seit: 13.07.2004
Beiträge: 4.506
Entwicklungsumgebung: .NET 2.0/3.5 und VS2005/VS2008
Herkunft: Wald-Michelbach (Odw)


norman_timo ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo dr4g0n76,

Du verwendest in Deiner Zeichenroutine

C#-Code:
public override void Draw(Graphics g)

noch viel zu viel Rechenkram (meiner Meinung nach). Folgende Instruktionen würde ich auslagern, und nur bei Bedarf rechnen lassen:

C#-Code:
Rectangle rectShadow = new Rectangle(new Point(this.Location.X + 15, this.Location.Y + 15),this.Size);

-> nur bei Größenänderungen rechnen lassen

C#-Code:
if (m_szTextSize.IsEmpty)
            {
                m_szTextSize = g.MeasureString(this.m_sText, this.m_Font);
            }

-> Nur bei Textänderungen berechnen lassen

C#-Code:
new PointF(this.Location.X + this.Size.Width / 2 - m_szTextSize.Width / 2, this.Location.Y + this.Size.Height / 2 - m_szTextSize.Height / 2)

-> Ebenfalls nur bei Größenänderung rechnen lassen.

Dann hättest Du in Deiner Zeichenroutine im Wesentlichen nur Draw Anweisungen, so wenig wie möglich new, und so wenig Berechnungen wie möglich zu tun.

Aber das ist nur meine Persönliche Meinung (bzw. Erfahrung). Eventuell lassen sich feste Bereiche (Rectangles) definieren, die dann mittels Graphics.SetClip() und mittels Graphics.ResetClip() in den Grafikoperationen einsetzen lassen. -> Aber Quadtrees (hab ich mal in grafische Datenverarbeitung gehört) sind deutlich eleganter, und wenn ichs richtig verstanden hab, auch ein wenig komplizierter. Einfache Dinge lassen sich aber sicherlich mit Rectangle-Bereiche auch "begrenzen".

Viele Grüße
Norman-Timo
22.11.2006 10:07 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.880
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland

Themenstarter Thema begonnen von dr4g0n76

dr4g0n76 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

@norman_timo:

Ja, damit hast Du recht. Aber ich hatte zu Beginn noch viel mehr Berechnungen drin.

Zum Beispiel das Berechnen der Textabmessungen.
Oder die Brushes: Color color = new Color(Color.Irgendwas).

Das Auslagern der meisten Berechnungen hat schon einiges gebracht.

Ich kam aber gestern auf eine ganz andere Idee.

Und zwar:

Es wird quasi ein unsichtbares Raster (Rechtecke) über den Bildschirm gelegt.
Diese können in einem List<Rectangle> -Variable-Array gespeichert werden.

In dem Quadrant in dem sich die Maus befindet wird neu gezeichnet.

QuadrantIndex = BerechnetAusMausKoordinaten(ptMouse);

List<Rectangle> invRectQuad
QuadrantRect = List[QuadrantIndex];
Invalidate(QuadrantRect)

Das macht bei mir ziemlich genau Faktor 10 aus und dieser wäre absolut ausreichend.

Wenn nur der verschobene Bereich neu gezeichnet wird, funktioniert das nicht.

Sprich: im konkreten Fall nur der Bereich des Rechtecks das verschoben wird.

Fällt jemanden noch was anderes ein?

Bin für jeden Vorschlag dankbar.

Eine andere Möglichkeit wäre noch zu überprüfen, ob Objekte sich verdecken.
Denn was macht es für einen Sinn ein Objekt zu zeichnen, das eh nicht angezeigt werden kann, weil es durch ein anderes verdeckt wird?

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von dr4g0n76 am 22.11.2006 12:43.

22.11.2006 12:09 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.880
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland

Themenstarter Thema begonnen von dr4g0n76

dr4g0n76 ist offline

QuadInvalidate v0.9

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hier die erste Version des Quadinvalidate.

Wie man sieht wird bei überspringen eines Quadranten nicht alles korrekt neu gezeichnet.

Was wird hier gemacht? Wie vorhin beschrieben wurde der Index des upzudatenden Quadranten bestimmt.

Hat aber den Nachteil wenn das zu bewegende grafische Element größer ist als eines der Quadrate müssen alle Quadrate in denen das Element sich befindet neu gezeichnet werden. Dies resultiert in Grafikfehlern und das Update funktioniert auch überhaupt nicht so wie gedacht.

Jetzt wird deswegen überprüft, in welchen upzudatenden Quadranten sich das/die zu bewegende(n) Element(e) befindet(befinden).

Funktioniert nicht schlecht. Aber es gibt immer noch Darstellungsfehler. Warum?

Ich denke das ist ganz einfach: weil das Element z.B. nach oben bewegt wird.
Sobald es die unteren Quadrate verlassen hat, werden diese nicht mehr gezeichnet. obwohl dies vielleicht passieren sollte.

Ich denke jetzt wäre es wichtig zu wissen, in welchen Quadraten sich das Element befunden hat um diese zusätzlich neu zu zeichnen. Dies sollte das Problem lösen.


Dateianhang:
unknown QuadInvalidate.rar (38 KB, 972 mal heruntergeladen)

Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von dr4g0n76 am 22.11.2006 14:38.

22.11.2006 14:31 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.880
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland

Themenstarter Thema begonnen von dr4g0n76

dr4g0n76 ist offline

QuadInvalidate v1.0

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Jetzt funktioniert der Algorithmus.


Dateianhang:
unknown QuadInvalidate.rar (41,17 KB, 1.022 mal heruntergeladen)
22.11.2006 15:44 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.880
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland

Themenstarter Thema begonnen von dr4g0n76

dr4g0n76 ist offline

QuadInvalidate v1.1

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ich habe den Algorithmus jetzt extrahiert. Ein Aufruf von z.B.

C#-Code:
this.m_QuadInvalidator.Invalidate(this, this.m_Rectangle.Rectangle);

reicht,

um im

C#-Code:
protected override void OnMouseMove(MouseEventArgs e)
{
        base.OnMouseMove(e);
        this.m_Rectangle.Location = e.Location;
        this.m_QuadInvalidator.Invalidate(this, this.m_Rectangle.Rectangle);
}

ein optimierteres Zeichnen hinzubekommen.

Bei mir lassen sich jetzt ohne große Zeitverzögerung bis zu 3000 Objekte zeichnen.
Vorausgesetzt das Programm läuft im Debug-Mode außerhalb des Debuggers.

Ach ja, der Invalidator muss mit z.B.

C#-Code:
CQuadInvalidator m_QuadInvalidator = new CQuadInvalidator(10,150,150);

initialisiert werden.

10 gibt dabei die Dimension an (10 Rechtecke je X- und Y-Richtung).
Jedes dabei 150 lang und 150 hoch.


Dateianhang:
unknown QuadInvalidate.rar (41,31 KB, 1.023 mal heruntergeladen)

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von dr4g0n76 am 22.11.2006 16:38.

22.11.2006 16:37 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.880
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland

Themenstarter Thema begonnen von dr4g0n76

dr4g0n76 ist offline

Quadtree

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Das unten hinzugefügte Projekt zeigt wie ein Quadtree funktionieren würde, in diesem Fall wird rekursiv das zu zeichnende/bewegende Objekt eingegrenzt, bis es gut genug aufgelöst wird.


Dateianhang:
unknown QuadTree.rar (27 KB, 1.117 mal heruntergeladen)
22.11.2006 16:58 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.880
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland

Themenstarter Thema begonnen von dr4g0n76

dr4g0n76 ist offline

Versteckte Elemente

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hiermit können versteckte Elemente untersucht werden:


Dateianhang:
unknown Hidden.rar (56 KB, 1.045 mal heruntergeladen)
24.11.2006 19:40 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
dr4g0n76
myCSharp.de-Poweruser/ Experte

avatar-1768.jpg


Dabei seit: 07.07.2005
Beiträge: 2.880
Entwicklungsumgebung: SharpDevelop/VS.NET
Herkunft: Deutschland

Themenstarter Thema begonnen von dr4g0n76

dr4g0n76 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Dieses Artikel erhält nächste Woche ein Upgrade. War ja eigentlich zuerst nie als Artikel gedacht. ;-)

edit: Nächste Woche? schon lange her. Muss halt jetzt sagen: When it's done...

Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von dr4g0n76 am 11.12.2006 23:11.

24.11.2006 19:40 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 13 Jahre.
Der letzte Beitrag ist älter als 12 Jahre.
Antwort erstellen


© Copyright 2003-2019 myCSharp.de-Team | Impressum | Datenschutz | Alle Rechte vorbehalten. | Dieses Portal verwendet zum korrekten Betrieb Cookies. 22.11.2019 16:15