WinAPI schnell zeichnen



  • Guten,

    ich möchte möglichst schnell ein einfarbiges Rechteck zeichnen können. Meine jetzige Lösung:

    int DrawRect(HWND hwnd, RECT *rect, COLORREF color)
    {
      HDC hdc;
      HBRUSH hbrush;
      hdc = GetDC(hwnd);
      hbrush = CreateSolidBrush(color);
      FillRect(hdc, rect, hbrush);
      DeleteObject(hbrush);
      ReleaseDC(hwnd, hdc);
      return 0;
    }
    

    Das reicht zwar gerade so aus - aber mich interessiert ja immer noch ob das nicht irgendwie schneller geht. (Wohlgemerkt NUR mit WinAPI - kein DirectX usw.)

    Dass man (bei einem immer gleichen Fenster) nicht jedes mal GetDC aufrufen muss sei erstmal dahingestellt 😉


  • Mod

    1. Falsch, weil Du Dir den DC direkt erzeugst. Male mit dem gegebenen DC in WM_PAINT.
    2. Warum erzeugst Du den Brush jedesmal neu? Das kostet auch Zeit.



  • Martin Richter schrieb:

    1. Falsch, weil Du Dir den DC direkt erzeugst. Male mit dem gegebenen DC in WM_PAINT.
    2. Warum erzeugst Du den Brush jedesmal neu? Das kostet auch Zeit.

    @2. Ja, das ist in der Tat ein Problem, allerdings muss die Funktion verschiedenfarbige Rechtecke zeichnen können. Da das aber meist nur ca. 5 unterschiedliche Farben sind werde ich mir da wohl irgendetwas hinfrickeln so dass ich den Brush nicht jedes mal neu erzeugen muss.

    @1. Das verstehe ich ehrlich gesagt nicht so ganz.. wie genau meinst Du das mit dem "gegebenen DC in WM_PAINT"?



  • Du zeichnest hoffentlich nur bei WM_PAINT Nachrichten?

    Und diese Nachricht WM_PAINT liefert Dir gleich den passenden DC mit, den Du sofort für Deine Zeichenoperationen als Ausgabe nutzen kannst.

    hdc = (HDC)wParam; //the device context to draw in.
    

    HTH,
    Martin



  • Ehm.. ne ich zeichne nicht nur bei WM_PAINT 😉 Das ganze soll so eine Art Animation ergeben - da kann ich ja nicht auf WM_PAINT warten oder?^^

    Und das WM_PAINT ein HDC mitliefert wusste ich gar nicht - habe mir schon sowas gedacht aber hier ( http://msdn.microsoft.com/en-us/library/dd145213(v=vs.85).aspx ) steht dass beide Parameter ungenutzt sind?


  • Mod

    1. Man zeichnet normalerweise nur in WM_PAINT
    2. Den Paint DC erhält man mit Aufruf des BeginPaint...
    3. Selbst bei Animatonen musst Du in WM_PAINT zeichnen!



  • - benutze einen memory DC zum zeichnen (double buffering)
    - während WM_PAINT diesen nur entsprechend der Anforderung auf den Bildschirm kopieren
    - bei z.B. WM_TIMER (Animation) nur den entsprechenden Bereich neue zeichen und invalidieren.


  • Mod

    sonstiges schrieb:

    - benutze einen memory DC zum zeichnen (double buffering)
    - während WM_PAINT diesen nur entsprechend der Anforderung auf den Bildschirm kopieren
    - bei z.B. WM_TIMER (Animation) nur den entsprechenden Bereich neue zeichen und invalidieren.

    Und was soll daran schneller sein?
    Die Operationen sinddie selben nur über den Umweg eines Memory DCs und laufen im selben Thread...

    Fazit: Performance ist eher schlechter durch den Umweg über den Memory DC... 😉



  • Du hast vermutlich den Eindruck das es langsam ist, da Du NICHT in WM_PAINT malst. Wenn das Fenster wegen Resize oder Focuswechsel durch's System neugezeichnet wird, wie bekommst Du das mit? Bekommst Du dann flackern?
    Wie "sonstiges" bereits geschrieben hast, malt man bei Animationen die Bilder in einen memDC. In WM_PAINT wird dann einfach dieses Bild im Speicher in den Fenster DC "geblitted" BitBlt(). Den Zugriff auf den memDC solltest Du übrigens threadsicher machen (durch z.B. BeginPaint). Immer wenn Du ein neues Bild in den memDC gemalt hast, schickst Du nur noch ein invalidate an Dein Fenster => es kommt zu WM_PAINT und alles wird gut 😉



  • Zum Problem mit den Brushes:
    Wenn du nur für Win2000 und später (also nicht Win9X/ME) entwickelst, kannst du auch jeweils den DC-Brush verwenden und dir eine eigene kleine Funktion schreiben:

    namespace mynamespace
    {
    void FillRect(HDC hdc, const RECT* Rect, COLORREF clr)
    {
    	COLORREF clrSave = ::SetDCBrushColor(hdc, clr);
    	::FillRect(hdc, Rect, reinterpret_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
    	::SetDCBrushColor(hdc, clrSave);
    }
    } // mynamespace
    

    Ein Geschwindigkeitsvorteil dürfte das natürlich nur sein, wenn die Alternative das Erzeugen von verschiedenen Brushes im WM_PAINT-Zweig wäre. Ganz praktisch ist es aber allemal.
    Ebenso kannst du auch den DC-Pen verwenden und dir nach und nach GDI-Funktionen kapseln.

    Mmacher schrieb:

    Und diese Nachricht WM_PAINT liefert Dir gleich den passenden DC mit, den Du sofort für Deine Zeichenoperationen als Ausgabe nutzen kannst.

    hdc = (HDC)wParam; //the device context to draw in.
    

    Ja?

    MSDN schrieb:

    WM_PAINT
    wParam
    This parameter is not used.
    lParam
    This parameter is not used.
    );

    Eher doch klassisch:

    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);
    ...
    EndPaint(hwnd, &ps);
    

    Animationen mit Hilfe der GDI funktionieren zwar irgendwie, wirklich schnell und ruckelfrei wirds aber erst mit anderen Techniken, die du aber leider nicht verwenden möchtest.



  • Also über einen memDC ist das ganze wesentlich langsamer..

    Allerdings reicht die Lösung (etwas abgeändert) von oben wenn man
    1. sich nicht immer einen neuen Brush anlegt (was offensichtlich recht viel Zeit kostet?) und
    2. die Funktion "inlined" bei weitem - so kann man 100000 Rechtecke in ~500 ms zeichnen. Damit wäre die Hauptfrage eigentlich erstmal geklärt - FillRect scheint auf jeden fall hinreichend schnell zu sein.

    Was mich jetzt noch interessiert:
    - Warum soll man nur "innerhalb" von WM_PAINT zeichnen?
    - Man nutzt also weder GetDC() noch (HDC)wparam (bei WM_PAINT) sondern BeginPaint() um den Device-Context zu erhalten damit das thread sicher ist. Habe ich das richtig verstanden?
    - Ist das "manuelle" senden von WM_PAINT an ein Fenster legitim? (WM_SETFOCUS zB. darf man ja wohl nicht senden 😃 )



  • > - Warum soll man nur "innerhalb" von WM_PAINT zeichnen?
    Weil WM_PAINT immer aufgerufen wird, wenn dein Fenster neu gezeichnet werden soll. Z.B. wenn man ein anderes Fenster mal quer drüber und wieder runter schiebt. Und noch mehr Gründe, ist aber wirklich das einzig sinnvolle.

    > - Man nutzt also weder GetDC() noch (HDC)wparam (bei WM_PAINT) sondern BeginPaint() um den Device-Context zu erhalten damit das thread sicher ist. Habe ich das richtig verstanden?
    Jap. Und EndPaint nicht vergessen.

    > - Ist das "manuelle" senden von WM_PAINT an ein Fenster legitim? (WM_SETFOCUS zB. darf man ja wohl nicht senden 😃 )
    Nimm InvalidateRect, dabei NULL für den Rechteck-Parameter damit alles neu gezeichnet wird.


Anmelden zum Antworten