Performanz beim Verschieben eines Fensters



  • Hey,

    ich zeichne gerade die Mandelbrotmenge mit einem WinAPI Programm, und verstehe etwas bzgl der Performanz nicht.

    Inizialisiere ich die Brush mit "(HBRUSH)CreateSolidBrush(COLORREF RGB(240,240,240));", wird der Inhalt des Fensters beim Verschieben des Fensters grau. Initialisiere ich die Brush mit "(HBRUSH)(WHITE_BRUSH);", bleibt meine Mandelbrotmenge gezeichnet und wird verschoben. Generell unterscheiden sich beide Varianten ja nur durch die Initialisierung des hbrBackgrounds, ich dachte immer, dass das nur die Farbe des Fensters ist. Warum ergibt das Ändern der Zeile denn so große Performanzeinbrüche beim Verschieben?

    Die Mandelbrotmenge wird natürlich im WM_PAINT gezeichnet

    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
    {
    
        wchar_t appname[] = L"mandelbrot6396";
        WNDCLASS wnd;
        HWND hWnd;
        MSG msg;
    
        wnd.cbClsExtra = 0;
        wnd.cbWndExtra = 0;
        //wnd.hbrBackground = (HBRUSH)CreateSolidBrush(COLORREF RGB(240,240,240));
        wnd.hbrBackground = (HBRUSH)(WHITE_BRUSH);
        wnd.hCursor = LoadCursor(NULL,IDC_ARROW);
        wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wnd.hInstance = hInstance;
        wnd.lpfnWndProc = WndProc;
        wnd.lpszClassName = appname;
        wnd.lpszMenuName = NULL;
        wnd.style = CS_VREDRAW | CS_HREDRAW;
    
        RegisterClass(&wnd);
    
        hWnd = CreateWindowEx(WS_EX_WINDOWEDGE, appname, L"Mandelbrotmenge", WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU, GetSystemMetrics(SM_CXSCREEN)/2-width/2, GetSystemMetrics(SM_CYSCREEN)/2- height/2, width, height,0,0,hInstance,0);
        ShowWindow(hWnd, iCmdShow);
        UpdateWindow(hWnd);
    
        while (GetMessage(&msg,hWnd,0,0)){
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    
        return msg.wParam;
    }
    

    [code="cpp"]



  • Meines Wissens sollte WHITE_BRUSH als Parameter von GetStockObject ein gültiges GDI-Handle zurückgeben. Eventuell produziert dein Code mit (HBRUSH)WHITE_BRUSH einen Fehler in der Fensterklasse, der das Löschen fehlschlagen lässt und ist nur deshalb scheinbar performanter.

    Um ein Fenster neu zu zeichnen wird zuerst WM_ERASEBKGND gesendet und die DefWindowProc löscht dann das Fenster, erst dann folgt WM_PAINT.
    Wenn dein Programm ohnehin das gesamte Fenster in WM_PAINT übermalt, kannst du WM_ERASEBKGND mit return TRUE enden lassen und damit das unnötige Löschen überspringen.
    Am sinnvollsten empfand ich immer das Zeichnen auf einem kompatiblen Gerätekontext und in WM_PAINT wird dieser nur per BitBlt auf das Fenster übertragen. Auch sollte man in WM_PAINT nur die Bereiche übermalen, die GetUpdateRect() zurückgibt, damit das Zeichnen auf das nötige Minimum reduziert wird.

    Aber welche Variante die performanteste ist, hängt auch davon ab, was du sonst noch mit dem Fenster machst ...
    😉



  • Vielen Dank für die Antwort! Ich kannte diese Message nicht und werde sie mal testen 🙂

    Ich habe bisher immer die Erfahrung gemacht, dass bei einem Fehler in der Fensterklasse das Fenster garnicht erst angezeigt wird. Der Hintergrund wird mit der einen Zeile auch weiß o:

    Naja ich teste das jetzt mal mit der Message! Vielen Dank!

    PS: Was ist ein "kompatibler Gerätekontext" ? 😃



  • xor schrieb:

    Am sinnvollsten empfand ich immer das Zeichnen auf einem kompatiblen Gerätekontext und in WM_PAINT wird dieser nur per BitBlt auf das Fenster übertragen. Auch sollte man in WM_PAINT nur die Bereiche übermalen, die GetUpdateRect() zurückgibt, damit das Zeichnen auf das nötige Minimum reduziert wird.

    Bei WM_PAINT wird üblicherweise BeginPaint verwendet, was GetUpdateRect unnötig macht.

    Zitat: "The update rectangle retrieved by the BeginPaint function is identical to that retrieved by GetUpdateRect."

    In welchen Fällen macht GetUpdateRect Sinn ?



  • Frolo schrieb:

    PS: Was ist ein "kompatibler Gerätekontext" ? 😃

    Ist deine Suchmaschine kaputt ?

    http://www.c-plusplus.net/forum/39389-full

    oder auch:

    http://msdn.microsoft.com/de-de/library/vstudio/azz5wt61.aspx

    (Mann sollte besser die englische Version verwenden. Die deutsche Übersetzung ist grottig ..)



  • Frolo schrieb:

    Die Mandelbrotmenge wird natürlich im WM_PAINT gezeichnet

    Hoffentlich nur gezeichnet, und nicht auch berechnet 😉

    ps: Wenn man die zu zeichnenden Dinge nicht "vorbereiten" muss, und in den von BeginPaint() zurückgelieferten DC zeichnet, dann kann man einfach BeginPaint() verwenden, und so tun als ob man das ganze Fenster neu zeichnen würde. Weil die nicht-zu-zeichnenden Bereiche dann sowieso über den DC "weggeclippt" werden.

    MSDN schrieb:

    The BeginPaint function automatically sets the clipping region of the device context to exclude any area outside the update region.



  • @Frolo:
    Probiere mal "CreateCompatibleDC", "CreateCompatibleBitmap" und "double buffering" in der Suche 😉

    @merano:
    In der WM_PAINT Doku steht es beschrieben. Es kann WM_PAINTs ohne Update-Region geben. BeginPaint "sollte" laut MS dann nicht aufgerufen werden und wenn ich mich recht erinnere kann es dann zu einer schlechteren Performance kommen. Ich habe vor 4 Jahren deshalb alle meine Codes darauf umgestellt. Mir fällt aber das konkrete Problem jetzt auch nicht mehr ein. Ich glaube es waren bestimmte Controls und der Style WS_CLIPCHILDREN, die ansonsten in WM_PAINT flackerten oder falsch gezeichnet wurden ...
    Aber, OK, wenns nur ums gelegentliche Zeichnen geht, braucht man diese Vorkehrung nicht zu treffen und kann immer alles auf den BeginPaint-DC übertragen. Wenn du aber immer 1600 x 1200 große Bitmaps beim Resizen des Fensters übertragen musst, wirst du feststellen, dass es verdammt viel schneller geht, wenn du nur den notwendigen Bereich BitBlt-est 😉



  • MSDN schrieb:

    A window may receive internal paint messages as a result of calling RedrawWindow with the RDW_INTERNALPAINT flag set. In this case, the window may not have an update region. An application should call the GetUpdateRect function to determine whether the window has an update region. If GetUpdateRect returns zero, the application should not call the BeginPaint and EndPaint functions.

    War mir auch neu...
    Hab' auch noch kein Programm gesehen dass sich darum schert 😉



  • Hey,

    danke erstmal! Ich habe bisher diesen Code im WM_PAINT. Ich google mal die 3 Begriffe, die ihr genannt habt, bis dahin könnt ihr ja mal schaun, was man optimieren kann 🙂

    Bin halt relativ neu auf dem Gebiet. Hatte vorher mein TCanvas und ne Form bei der ich DoubleBuffered auf true gesetzt hab 😃

    case WM_PAINT:
            {
    
                HBITMAP bmp;
                bmp = CreateDIBSection(NULL, bmpInfo, DIB_RGB_COLORS, (void**) &rgbValues, NULL, 0);
                for (int y = 0; y < width; y++)
                {
                    const int lineStart = y * rowLength ;
                    for (int x = 0; x < height; x++)
                    {
                        int mandelValue = mandelbrot((double)(x - width / 2) * zoom + dx, (double)(y - height / 2) * zoom - dy, 255);
                        int pixelIndex = lineStart + x * 3;
    
                        rgbValues[pixelIndex] = mandelValue;
                        rgbValues[pixelIndex + 1] = mandelValue;
                        rgbValues[pixelIndex + 2] = mandelValue;
                    }
                }
    
                HDC hdc = GetDC(hWnd);
    
                SetDIBitsToDevice(hdc, 0, 0, width, height, 0, 0, 0, height, rgbValues, bmpInfo, DIB_RGB_COLORS);
    
                ReleaseDC(hWnd, hdc);
                DeleteObject(bmp);
                return 0;
            }
    

    PS: Der Code stammt von einem Freund, ich selbst habe nie mit CreateDIBSection() und SetDIBitsToDevice() gearbeitet. Ist aber jedenfalls schneller als SetPixel


  • Mod

    Dieser Code gehört in die Sektion: FALSCH!

    Wenn dies ein WM_PAINT Handler sein soll, dann Fehlen hier die grundsätzlichen Funktionen BeginPaint und EndPaint. Dadurch wird auch der falsche DC verwendet.

    Ein Fall für: Lies bitte mal die Grundlagen zur Windows Programmierung...



  • hustbaer schrieb:

    Frolo schrieb:

    Die Mandelbrotmenge wird natürlich im WM_PAINT gezeichnet

    Hoffentlich nur gezeichnet, und nicht auch berechnet 😉



  • Erstmal kenne ich die Grundlagen, dieser Code war eher eine Antwort auf "Wie mache ich es schneller" und so nebenbei macht der Code genau das, was er soll also verstehe ich nicht, was das Problem ist. Wie sollte das denn eurer Meinung nach aussehen?

    Achja und wieso sollte der falsche DC verwendet werden? Ich bekomm genau den richtigen...

    Zweitens stimmt das mit dem Berechnen. Ich berechne derzeit die Mandelbrotmenge komplett im WM_PAINT Event. Das Problem ist nur, dass ich nicht weiß, wo ich sie sonst berechnen soll. Ich meine ich könnte alle paar Millisekunden die Bitmap berechnen lassen, aber sollte man das so machen?


  • Mod

    Frolo schrieb:

    Erstmal kenne ich die Grundlagen, dieser Code war eher eine Antwort auf "Wie mache ich es schneller" und so nebenbei macht der Code genau das, was er soll also verstehe ich nicht, was das Problem ist. Wie sollte das denn eurer Meinung nach aussehen?

    Achja und wieso sollte der falsche DC verwendet werden? Ich bekomm genau den richtigen...

    Der Code macht nicht was er soll, und es ist der falsche DC.
    Deine Ansicht wird nie valide, d.h. WM_PAINT wird immer gefeuert, obwohl evtl. nichts neu zu berechnen ist.

    Und ja. Berechnen, würde ich in einem Workerthread und "bei Bedarf", zeichnen...



  • Okay danke dir für die Antwort (die viel konstruktiver ist). Verstehe ich das richtig, das WM_PAINT immer ausgeführt wird, wenn ich es nicht ordnungsgemäß mit BeginPaint und EndPaint ausführe? Ich dachte immer ich kann das genauseo mit GetDC() machen.

    Und deiner Meinung nach soll die Bitmap in einem Thread immer wieder neu berechnet werden. Geht das nicht verdammit auf die PC Leistung?



  • Die Bitmap sollte nur dann neuberechnet werden, wenn sich auch etwas daran ändert.
    Und bei der WM_PAINT Nachricht solltest du daher nur diese Bitmap anzeigen (anstatt jedesmal die Berechnung auszuführen).
    Ob du die Berechnung wirklich in einen eigenen Thread auslagern musst, kommt drauf an, ob die Berechnung länger als ca. 50ms dauert.



  • Martin Richter schrieb:

    Und ja. Berechnen, würde ich in einem Workerthread und "bei Bedarf", zeichnen...

    Idealerweise zeichnet man das Apfelmännchen in ein Offscreen-Image und blittet das dann bei WM_PAINT ins Fenster.

    BeginPaint
    BitBlt
    EndPaint

    ... mehr muss im WM_PAINT-Handler nicht stehen.



  • Frolo schrieb:

    Hey,

    danke erstmal! Ich habe bisher diesen Code im WM_PAINT. Ich google mal die 3 Begriffe, die ihr genannt habt, bis dahin könnt ihr ja mal schaun, was man optimieren kann 🙂

    Bin halt relativ neu auf dem Gebiet. Hatte vorher mein TCanvas und ne Form bei der ich DoubleBuffered auf true gesetzt hab 😃

    [code="cpp"]case WM_PAINT:
    {

    HBITMAP bmp;
    bmp = CreateDIBSection(NULL, bmpInfo, DIB_RGB_COLORS, (void**) &rgbValues, NULL, 0);
    ...

    Leider fehlen essentielle Teile um das ganze auszuprobieren ...

    Wenn ich das richtig sehe wird CreateDIBSection() verwendet um Speicher für die Pixel zu besorgen.
    Dafür müsste aber in bmpinfo die Groesse der Bitmap stehen.

    - Woher kommen width und height ? Sind die konstant ?
    ->Im Programm werden die Begriffe vertauscht verwendet 🤡
    - bmpInfo müsste dann damit initialisiert werden

    - Wenn das (vermutlich) alles konstant ist, wieso wird dann bei jedem
    Zeichnen immer wieder eine neue BitMap erzeugt und gelöscht ?

    Das Erzeugen der Bitmap sowie das Berechnen der Grafik gehört definitiv nicht nach WM_PAINT.
    Wenn sich weder Grösse noch Berechnung ändern könnte man das in InitInstance oder WM_CREATE
    erledigen. Sollte sich die Grafik mit der Fenstergroesse aendern koennte man es mit der entsprechenden
    Message neu machen.

    Es wäre nett wenn Du die fehlenden Variablen sowie die Funktion mandelbrot() noch referenzieren koenntest.


Anmelden zum Antworten