Double buffering - WinAPI

lfp

Grünschnabel
Hallo liebe Community!
Ich programmiere schreibe zur Zeit an einem Projekt, für das ich etwas Grafik benötige.
Deshalb hab ich angefangen, mich in die WinAPI einzuarbeiten, und bin auf folgendes Problem gestoßen:
Wenn ich viel Grafik in meinem Fenster hab, und es öfters aktualisiere, beginnt es zu flackern.
Höchst wahrscheinlich liegt das daran, dass ich den Hintergrund immerwieder lösche und neu zeichnen lasse:

Code:
GetClientRect(hWnd,lprect);
::RedrawWindow(hWnd,lprect,NULL,RDW_ERASE|RDW_UPDATENOW|RDW_INVALIDATE);

Wenn ich das allerdings nicht mache, gibt es Probleme, beim verschieben von Objekten (wie zB. Text), da dann deren "Schleifspur" auf dem Hintergrund sichtbar bleiben, oder anders gesagt: Es wird zwar gezeichnet aber nicht wieder gelöscht, und deshalb brauche ich "RDW_ERASE", das wiederum das Flackern auslöst.

Um dieses Problem zu beheben gibt es bereits eine Lösung: Double Buffering.

Alles was ich zeichnen will, zeichne ich dann nicht direkt auf den Bildschirm, sondern in ein Bitmap und bringe das dann irgendwie (wohl mit BitBlt(...)) auf den Bildschirm.

Ich habe schon viel danach auf Google gesucht aber leider nichts gefunden, was funktioniert hat, und verstehe selbst langsam nicht mehr, was ich falsch mache, und deshalbstelle ich euch eine einfache Frage:

Was muss ich vor, und was nach die Zeichenfunktionen (zB. TextOut(...)) schreiben, um nicht auf den Bildschirm, sondern in einen Backbuffer zu zeichnen, und den dann wiederum auf den Bildschirm zu bringen?

Hier der gesamte Quellcode, in dem ich nur Text über den Bildschirm bewege, um das Flackern heraufzubeschwören:

main.cpp:
Code:
	// MAIN.CPP //

#include "header.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); // Funktion, die Messages verarbeitet
HWND hWnd;				// Handle zum Hauptfenster
HINSTANCE hInstance;    // Instanz des Programmes


int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR szCmdLine, int iCmdShow) { // Hauptfunktion
	hInstance = hInst;                 // Instanz des Programmes
	const char szClassname[] = "hWnd"; // Klassenname der Fensterklaase
	MSG msg;                           // Nachrichten, die von Windows an das Programm geschickt werden
	WNDCLASS wc;                       // Fensterklasse

	wc.cbClsExtra		= NULL;
	wc.cbWndExtra		= NULL;
	wc.hbrBackground	= (HBRUSH) (4+1);                     // Fenster-Hintergrundfarbe
	wc.hCursor			= LoadCursor(NULL, IDC_ARROW);        // Mauszeiger
	wc.hIcon			= LoadIcon(NULL, IDI_APPLICATION);    // Icon
	wc.hInstance		= hInst;                              // Instanz des Fensters
	wc.lpfnWndProc		= WndProc;                            // Funktion, die Benutzereingaben und Childfenster verwaltet
	wc.lpszClassName	= szClassname;                        // Klassenname 
	wc.lpszMenuName		= NULL;				  // Menü laden
	wc.style			= NULL;                               // Besondere Fensterstyles

	if (!RegisterClass(&wc)) { // Registrierung der Klasse
		MessageBox(NULL, "Die Klasse konnte nicht registriert werden ", "Fehler", MB_OK | MB_ICONERROR); // Falls die Klasse nicht registriert werden konnte: Fehlermeldung ...
		return 1; // ... und Beendigung des Programms
	}

	hWnd = CreateWindow(szClassname /*Klassenname*/, "Titel" /*Titel des Fensters*/,   WS_POPUP /*Art des Fensters*/, 100 /*X-Koordinate des Fensters*/, 100 /*Y-Koordinate des Fensters*/, 800 /*Breite des Fensters (ist egal, da das Fenster ja später maximiert wird)*/, 600 /*Höhe des Fensters (ist egal, da das Fenster ja später maximiert wird)*/, NULL /*Elternfenster (Da es ja das einzige Fenster ist wird hier NULL eingesetzt)*/, NULL /*Menuname (wichtig für Buttons!, da hier aber keine erwünscht sind -> NULL)*/, hInst /*Instanz des Fensters*/, NULL); // Erstellung des Hauptfernsters

	if (!hWnd) { // Wenn das Fenster nicht erstellt werden konnte
		MessageBox(NULL, "Das Fenster konnte nicht erstellt werden", "Fehler", MB_OK | MB_ICONERROR);
		return 1;
	} 

	ShowWindow(hWnd, SW_MAXIMIZE); // Fenster maximieren und anzeigen
	UpdateWindow(hWnd); // Und WM_PAINT ausführen
	
	hFont = CreateFont(25,10,0,0,FW_DONTCARE,FALSE,false,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS,
                CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY, VARIABLE_PITCH,TEXT("Arial"));

	// Messageverwaltung
	PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
	while(msg.message != WM_QUIT){
		dwTime1 = timeGetTime(); //Zeit vor dem Rendern messen und in dwTime1 speichern
		
		GetClientRect(hWnd,lprect);
		::RedrawWindow(hWnd,lprect,NULL,RDW_ERASE|RDW_UPDATENOW|RDW_INVALIDATE); //Hintergrund löschen und alles neu zeichnen (hier entsteht wohl das Flackern)
		
		while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // Während Messages von Windows ankommen
			TranslateMessage(&msg); // Messages übersetzen
			DispatchMessage(&msg); // Und an msg verschicken
		}
		Sleep(3); //PC etwas entlasten :)
		dwTime2 = timeGetTime(); //Zeit nach dem Rendern messen und in dwTime2 speichern
		fTime = (float)(dwTime2 - dwTime1) / 1000.0f; //Durch die Differenz die zum Rendern benötigte Zeit in fTime speichern, die später zum verschieben benötigt wird
		x += fTime*100; // Den Text so weit verschieben, wie er in der Zeit, die zum Rendern verbraten wurde, eig. hätte wandern müssen (Vorallem in aufwendigeren animationen wichtig um auf allen PC die gleiche Geschwindigkeit des Textes herzustellen)
		if(x > screenX){ //Sollte der Text den Bildschirmrand verlassen
			x = -50; //Setze ihn wieder an den Anfang
		}
	}
	return 0; // Funktion beenden, wenn keine Messages mehr ankommen
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Funktion, die Messages verarbeitet
	switch (msg) {
	case WM_CREATE:
		screenX = GetSystemMetrics(SM_CXSCREEN); //Bildschirmbreite (in Pixeln)
		screenY = GetSystemMetrics(SM_CYSCREEN); //Bildschirmhöhe (in Pixeln)
		return 0;
	case WM_PAINT:
		hDC = BeginPaint(hWnd, &ps);

		TextOut(hDC,x,200,"Text1",arraylaenge("Text1"));
		TextOut(hDC,x,300,"Text2",arraylaenge("Text2"));
		TextOut(hDC,x,400,"Text3",arraylaenge("Text3"));
		TextOut(hDC,x,500,"Text4",arraylaenge("Text4"));
		TextOut(hDC,x,600,"Text5",arraylaenge("Text5"));
		TextOut(hDC,x,700,"Text6",arraylaenge("Text6"));
		TextOut(hDC,x,800,"Text7",arraylaenge("Text7"));
		TextOut(hDC,x,900,"Text8",arraylaenge("Text8"));

		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY: // Wenn das Fenster geschlossen werden soll ...
		PostQuitMessage(0); // ... melden, dass keine Messages mehr empfangen werden soll
		break;
	case WM_LBUTTONDOWN:
		SetFocus(hWnd);
		break;
	case WM_KEYDOWN:
		switch(wParam){
			case VK_ESCAPE:
				PostQuitMessage(0);
				break;
			case VK_RETURN :
				SetFocus(hWnd);
				return 0;
		}
	default:  // Wenn keine der vorherigen Messages zutrifft ...
		return DefWindowProc(hWnd, msg, wParam, lParam);
		break;
	}
}


int arraylaenge (char name[]){
	int laenge = 0;
	while(name[laenge]!='\0'){
		laenge++;
	}
	return laenge;
}

header.h:
Code:
#include <Windows.h>     // Bibliothek, die unbedingt zur Erstellung eines Fensters nötig ist
#include <iostream>
#include <conio.h>
#pragma comment(lib,"Winmm.lib")

HDC hDC;
HFONT hFont;
PAINTSTRUCT ps;

int screenX;
int screenY;
int nCharacters;

LPRECT lprect;
 
DWORD dwTime1, dwTime2;
float fTime = 0.002f;
float x = -50; //x-Koordinate des zu animierenden Text

int arraylaenge (char name[]);

Ich hoffe, dass jemand eine einfache Lösung dazu findet, und danke euch schonmal im Vorraus für eure Mühe.

MfG
LFP
 
Zuletzt bearbeitet:
Du musst im Paintereignis dein Fenster übermalen und dann erst die Texte zeichnen. Ich weiß nicht mehr genau wie das hieß. Stichwort Brush WinApi.
 
Eine relativ einfache Lösung wäre CMemDC. Das ist kein WinAPI-Bestandteil, sondern ein kleines Stück freier Code (siehe http://www.codeproject.com/Articles/33/Flicker-Free-Drawing-In-MFC). CMemDC setzt zwar auf MFC auf, der eigentliche Code ist aber problemlos konvertierbar.

Es wird ein MemoryDC erstellt, in den man dann alles hineinmalt (anderes HDC). Am Ende ein BitBlt aus dem MemoryDC in den Fenster-HDC und noch alles aufräumen. Fertig.

Damit das auch flackerfrei bleibt, solltest du evtl. WM_ERASEBKGND behandeln (return 1), dafür mußt du aber das komplette Zeichnen innerhalb von WM_PAINT abhandeln.
 
Hallo,

Beispielcode habe ich gerade nicht parat, aber prinzipiell läuft es so:

  • Erzeugen eines Speicher-Device-Kontext -> CreateCompatibleDC()
  • Erzeugen eines Bitmaps -> CreateCompatibleBitmap()
  • Auswahl des Bitmaps im Speicherkontext -> SelectObject()
  • Alle deine Zeichenoperationen im Speicherkontext
  • Das Bitmap vom Speicherkontext in den Gerätekontext kopieren -> BitBlt()
  • Alle zum Zeichnen benutzen Objekte, das Bitmap und den Speicherkontext aufräumen -> DeleteObject(), DeleteDC()

Gruß
MCoder
 
@MCoder: hmmm...aber innerhalb einer case-marke (case WM_PAINT) kann man ja nichts deklarieren. Das wird vom Compiler übersprungen, und gibt Fehler. Ich werd da mal bisschen rum probieren...
@Endurion: hört sich vielversprechend an, ich seh mir das mal an!
@MSVCPLUSPLUS: auch ne gute Idee, mal sehen ob das klappt!

erstmal danke euch allen!
 
Hallo Ifp,

je nachdem, wie komplex deine Zeichenoperationen ausfallen, sollte evt. schon der letzte Tipp von Endurion was bringen, nämlich WM_ERASEBKGND behandeln und den Hintergrund selber zeichnen.
Das müsstest du sowieso machen (auch bei Double Buffering), sonst bekommst du das Flackern kaum weg.

Gruß
MCoder
 
Den Code fürs Double Buffering kann ich dir schon geben.
Aber du wirst noch weitere Probleme haben, z.B. beim Verschieben des Fensters oder Änderung der Grösse.

Code:
LRESULT CALLBACK MyWindow(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{

switch(msg)
{

case WM_ERASEBKGND:
{

return 1;

}
break;

case WM_PAINT:
{

PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd,&ps);

// DC samt Bitmap im Speicher erstellen

HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP MemBitmap = CreateCompatibleBitmap(hdc,400,400); // Breite und Höhe musst du anpassen
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem,MemBitmap);

// Hier kannst du BitBlt(), TextOut(), usw. machen, z.B.

TextOut(hdcMem,x,200,"Text1",arraylaenge("Text1"));

// Grafik im Speicher fertig
// kopieren auf Anzeige

BitBlt(hdc,ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom,hdcMem,ps.rcPaint.left,ps.rcPaint.top,SRCCOPY);

// Speicher freigeben

SelectObject(hdcMem,hbmOld);
DeleteDC(hdcMem);
DeleteObject(MemBitmap);

EndPaint(hWnd,&ps);

return 0;

}
break;

}

return DefWindowProc(hWnd,msg,wParam,lParam);

}
 
  • Gefällt mir
Reaktionen: lfp
Ich habe hier auch noch den Code von dem "Rohling", den ich bei grafischen Anwendungen immer benutze:

Code:
#include <windows.h>
#include <math.h>

LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);


char szClassName[ ] = "Doppelpufferung";

#define WM_ZEICHNE_NEU              1001

static long t = 0;

int WINAPI WinMain (HINSTANCE hThisInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpszArgument,
                     int nCmdShow)
{
    HWND hwnd;
    MSG messages;
    WNDCLASSEX wincl;


    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;
    wincl.style = CS_DBLCLKS;
    wincl.cbSize = sizeof (WNDCLASSEX);


    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;
    wincl.cbClsExtra = 0;
    wincl.cbWndExtra = 0;

    wincl.hbrBackground = (HBRUSH)COLOR_3DSHADOW;


    if (!RegisterClassEx (&wincl))
        return 0;

    int cxScreen, cyScreen;
    cxScreen = GetSystemMetrics(SM_CXSCREEN);
    cyScreen = GetSystemMetrics(SM_CYSCREEN);


    hwnd = CreateWindowEx (
           0,
           szClassName,
           "Doppelpufferung",
           WS_OVERLAPPEDWINDOW,
           (cxScreen - 800) / 2,
           (cyScreen - 600) / 2,
           800,
           600,
           HWND_DESKTOP,
           NULL,
           hThisInstance,
           NULL
           );


    ShowWindow (hwnd, nCmdShow);


    while (GetMessage (&messages, NULL, 0, 0))
    {

        TranslateMessage(&messages);

        DispatchMessage(&messages);
    }


    return messages.wParam;
}


DWORD WINAPI Zeichenalgorhitmus(LPVOID lpParams)
{
    HWND hwnd = (HWND)lpParams;
    HDC hdc = GetDC(hwnd);
    HDC hdcMemory;
    HBITMAP hBitmap;
    RECT rc;
    GetClientRect(hwnd, &rc);
    hdcMemory = CreateCompatibleDC(hdc);
    hBitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
    SelectObject(hdcMemory, hBitmap);
    PatBlt(hdcMemory, rc.left, rc.top, rc.right, rc.bottom, WHITENESS);
    //starte zeichnen
    SelectObject(hdcMemory, CreateSolidBrush(RGB(200,100,100)));
    RoundRect(hdcMemory, 100 + 50 * cos(t/40.0 - M_PI/4.0), 100 + 30 * sin(t/40.0 - M_PI/4.0), 300 + 100 * cos(t/40.0), 300 + 100 * sin(t/40.0), 10, 10);
    //ende zeichnen
    BitBlt(hdc, rc.left, rc.top, rc.right, rc.bottom, hdcMemory, 0, 0, SRCCOPY);
    ReleaseDC(hwnd, hdc);
    DeleteObject(hBitmap);
    DeleteDC(hdcMemory);
    Sleep(10);
    t += 1;
    SendMessage(hwnd, WM_ZEICHNE_NEU, NULL, NULL);
}


LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
    switch (message)
    {
        case WM_CREATE:
            HANDLE hStartThread;
            hStartThread = CreateThread(NULL, NULL, Zeichenalgorhitmus, hwnd, NULL, NULL);
            break;
        case WM_COMMAND:
            break;
        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
            EndPaint(hwnd, &ps);
            break;
        case WM_ZEICHNE_NEU:
            HANDLE hZeichenalgorhitmus;
            hZeichenalgorhitmus = CreateThread(NULL, NULL, Zeichenalgorhitmus, hwnd, NULL, NULL);
            break;
        case WM_KEYDOWN:
            switch(wParam)
            {
                case VK_ESCAPE:
                    DestroyWindow(hwnd);
                    break;
            }
            break;
        case WM_DESTROY:
            PostQuitMessage (0);
            break;
        default:
            return DefWindowProc (hwnd, message, wParam, lParam);
    }

    return 0;
}

Ich denke er ist größtenteils selbsterklärend.

Hoffentlich konnte ich dir helfen.
Gruß Technipion
 

Neue Beiträge

Zurück