Performance bei GUI Drawings



  • hallo *,

    ich habe folgendes programm erstellt:

    // speed hammer.cpp : Defines the entry point for the application.
    //
    
    #include "stdafx.h"
    
    #define MAX_LOADSTRING 100
    #define IDS_APP_TITLE			103
    
    #define IDR_MAINFRAME			128
    #define IDD_SPEEDHAMMER_DIALOG	102
    #define IDD_ABOUTBOX			103
    #define IDM_ABOUT				104
    #define IDM_EXIT				105
    #define IDI_SPEEDHAMMER			107
    #define IDI_SMALL				108
    #define IDC_SPEEDHAMMER			109
    #define IDC_MYICON				2
    #ifndef IDC_STATIC
    #define IDC_STATIC				-1
    #endif
    // Nächste Standardwerte für neue Objekte
    //
    #ifdef APSTUDIO_INVOKED
    #ifndef APSTUDIO_READONLY_SYMBOLS
    
    #define _APS_NO_MFC					130
    #define _APS_NEXT_RESOURCE_VALUE	129
    #define _APS_NEXT_COMMAND_VALUE		32771
    #define _APS_NEXT_CONTROL_VALUE		1000
    #define _APS_NEXT_SYMED_VALUE		110
    #endif
    #endif
    // Global Variables:
    HINSTANCE hInst;								// current instance
    TCHAR szTitle[MAX_LOADSTRING];					// The title bar text
    TCHAR szWindowClass[MAX_LOADSTRING];			// the main window class name
    
    // Forward declarations of functions included in this code module:
    ATOM				MyRegisterClass(HINSTANCE hInstance);
    BOOL				InitInstance(HINSTANCE, int);
    LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
    INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);
    
    int APIENTRY _tWinMain(HINSTANCE hInstance,
                         HINSTANCE hPrevInstance,
                         LPTSTR    lpCmdLine,
                         int       nCmdShow)
    {
    	UNREFERENCED_PARAMETER(hPrevInstance);
    	UNREFERENCED_PARAMETER(lpCmdLine);
    
     	// TODO: Place code here.
    	MSG msg;
    	HACCEL hAccelTable;
    
    	// Initialize global strings
    	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    	LoadString(hInstance, IDC_SPEEDHAMMER, szWindowClass, MAX_LOADSTRING);
    	MyRegisterClass(hInstance);
    
    	// Perform application initialization:
    	if (!InitInstance (hInstance, nCmdShow))
    	{
    		return FALSE;
    	}
    
    	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SPEEDHAMMER));
    
    	// Main message loop:
    	while (GetMessage(&msg, NULL, 0, 0))
    	{
    		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    		{
    			TranslateMessage(&msg);
    			DispatchMessage(&msg);
    		}
    	}
    
    	return (int) msg.wParam;
    }
    
    //
    //  FUNCTION: MyRegisterClass()
    //
    //  PURPOSE: Registers the window class.
    //
    //  COMMENTS:
    //
    //    This function and its usage are only necessary if you want this code
    //    to be compatible with Win32 systems prior to the 'RegisterClassEx'
    //    function that was added to Windows 95. It is important to call this function
    //    so that the application will get 'well formed' small icons associated
    //    with it.
    //
    ATOM MyRegisterClass(HINSTANCE hInstance)
    {
    	WNDCLASSEX wcex;
    
    	wcex.cbSize		= sizeof(WNDCLASSEX);
    
    	wcex.style		= CS_HREDRAW | CS_VREDRAW;
    	wcex.lpfnWndProc	= WndProc;
    	wcex.cbClsExtra	= 0;
    	wcex.cbWndExtra	= 0;
    	wcex.hInstance		= hInstance;
    	wcex.hIcon		= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SPEEDHAMMER));
    	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
    	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
    	wcex.lpszMenuName	= NULL;
    	wcex.lpszClassName	= szWindowClass;
    	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    
    	return RegisterClassEx(&wcex);
    }
    
    //
    //   FUNCTION: InitInstance(HINSTANCE, int)
    //
    //   PURPOSE: Saves instance handle and creates main window
    //
    //   COMMENTS:
    //
    //        In this function, we save the instance handle in a global variable and
    //        create and display the main program window.
    //
    BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
    {
       HWND hWnd;
    
       hInst = hInstance; // Store instance handle in our global variable
    
       hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW | WS_MAXIMIZE,
    	   CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
    
       if (!hWnd)
       {
          return FALSE;
       }
    
       ShowWindow(hWnd, nCmdShow);
       UpdateWindow(hWnd);
    
       return TRUE;
    }
    
    //
    //  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
    //
    //  PURPOSE:  Processes messages for the main window.
    //
    //  WM_COMMAND	- process the application menu
    //  WM_PAINT	- Paint the main window
    //  WM_DESTROY	- post a quit message and return
    //
    //
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    	PAINTSTRUCT ps;
    	HDC hdc;
    
    	// create pens and brushes for the background color and drawing color
    	HPEN hpenBackground;
    	HPEN hpenDrawingColor;
    	HBRUSH hbrushFillColor;
    	HBRUSH hbrushDeleteColor;
    
    	// this is the timer id. every time we come here in, the timer is stopped with -1
    	int iTimerID = -1;				
    
    	// this is the flag to determine the status. '1' means, the drawings are done and
    	// we have to erase them, before we do new drawings. declared 'static' to not loose
    	// the status every time we come here in
    	static int iFlag = 1;
    
    	// this is a buffer for handle with some string. we may re-use this many times and 
    	// handle all string operations with this buffer
    	static TCHAR text[100];
    
    	// this defines the intervall for the timer
    	int iTimer = 10;
    
    	// this is used to get the actual mouse cursor position
    	POINT pointMouseCoords;
    
    	// this stores the actual screen size
    	RECT rectClientRect;
    
    	// this will store the coords for the cursor
    	static int iCursorX1;
    	static int iCursorY1;
    	static int iCursorX2;
    	static int iCursorY2;
    
    	// this stores the 'mode' for the ball; '+' means, the ball moves x+, respective y+
    	// additionally, we define here the starting point of the ball
    	static char cXmode = '+';
    	static char cYmode = '+';
    	static int iXBallCoord = 100;
    	static int iYBallCoord = 100;
    
    	switch (message)
    	{
    	case WM_CREATE:
    		// here we define a timer. this timer will call WM_TIMER every 'iTimer' milli seconds
    		SetTimer(hWnd, iTimerID = 1, iTimer, NULL);
    		break;
    	case WM_TIMER:
    		// here we come in, if the timer 'ticks'
    		// all we do here, is send a WM_PAINT message to our own window, so we can draw there
    		// the actual screen
    		SendMessage(hWnd, WM_PAINT, 0, 0);
    		break;
    	case WM_PAINT:
    		hdc = BeginPaint(hWnd, &ps);
    		// first get a device context
    		hdc = GetDC(hWnd);
    		// get actual screen size
    		GetClientRect(hWnd, &rectClientRect);
    		// make mouse cursor invisible
    		ShowCursor(FALSE);
    
    		// we delete the actual cursor and ball
    		hpenBackground = CreatePen(PS_SOLID, -1, RGB(255, 255, 255));
    		hbrushDeleteColor = CreateSolidBrush(RGB(255 ,255 ,255));
    		SelectObject(hdc, hpenBackground);
    		SelectObject(hdc, hbrushDeleteColor);	
    		Rectangle(hdc, iCursorX1, iCursorY1, iCursorX2, iCursorY2);
    		Ellipse(hdc, iXBallCoord, iYBallCoord, iXBallCoord + 12, iYBallCoord + 12);
    		DeleteObject(hpenBackground);
    		DeleteObject(hbrushDeleteColor);
    
    		// getting actual mouse coords and calculate coords for cursor
    		GetCursorPos(&pointMouseCoords);
    		if (pointMouseCoords.x < rectClientRect.left + 75) {
    			iCursorX1 = rectClientRect.left + 1;
    			iCursorX2 = rectClientRect.left + 151;
    		} else if (pointMouseCoords.x > rectClientRect.right - 75) {
    			iCursorX1 = rectClientRect.right - 151;
    			iCursorX2 = rectClientRect.right - 1;
    			} else {
    			iCursorX1 = pointMouseCoords.x - 75;
    			iCursorX2 = pointMouseCoords.x + 75;
    		}
    		iCursorY1 = rectClientRect.bottom - 30;
    		iCursorY2 = rectClientRect.bottom - 10;
    
    		// calculate actual ball coords
    		switch (cXmode) {
    			case '+':
    				iXBallCoord = iXBallCoord + 1;
    				// change mode, if ball hit right side of window
    				if (iXBallCoord > rectClientRect.right - 13) {
    					cXmode = '-';
    				}
    				break;
    			case '-':
    				iXBallCoord = iXBallCoord - 1;
    				// change mode, if ball hit left side of window
    				if (iXBallCoord < rectClientRect.left + 1) {
    					cXmode ='+';
    				}
    				break;
    		}
    		switch (cYmode) {
    			case '+':
    				iYBallCoord = iYBallCoord + 1;
    				// change mode, if ball hit the cursor
    				if (iYBallCoord == rectClientRect.bottom - 42 
    					&& iXBallCoord >= iCursorX1 
    					&& iXBallCoord <= iCursorX2) {
    					cYmode = '-';
    				}
    				// 'game over', if ball hit the bottom
    				if (iYBallCoord > rectClientRect.bottom - 13) {
    					DrawText(hdc, TEXT ("GAME OVER"), -1, &rectClientRect, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
    					KillTimer(hWnd, 1); 
    				}
    				break;
    			case '-':
    				iYBallCoord = iYBallCoord - 1;
    				// change mode, if ball hit top of window
    				if (iYBallCoord < rectClientRect.top + 1) {
    					cYmode ='+';
    				}
    				break;
    		}
    
    		// draw our actual coursor and ball
    		hpenDrawingColor = CreatePen(PS_SOLID, -1, RGB(0, 0, 0));
    		SelectObject(hdc, hpenDrawingColor);
    		hbrushFillColor = CreateSolidBrush(RGB(0 ,0 ,0));
    		SelectObject(hdc, hbrushFillColor);	
    		Rectangle(hdc, iCursorX1, iCursorY1, iCursorX2, iCursorY2);
    		Ellipse(hdc, iXBallCoord, iYBallCoord, iXBallCoord + 12, iYBallCoord + 12);
    		DeleteObject(hpenDrawingColor);
    		DeleteObject(hbrushFillColor);
    
    		// release device context
    		ReleaseDC(hWnd, hdc);
    		EndPaint(hWnd, &ps);
    		break;
    	case WM_DESTROY:
    		// here we kill the timer, because application is closed
    		KillTimer(hWnd, 1); 
    		PostQuitMessage(0);
    		break;
    	default:
    		return DefWindowProc(hWnd, message, wParam, lParam);
    	}
    	return 0; 
    }
    

    das problem damit ist, das es seeeehr langsam ist.
    ich habe folgenden weg gewählt:
    ein gesetzter timer ruft meine callback alle 10 ms auf. die callback fängt diesen aufruf in WM_TIMER ab und initiiert ein WM_PAINT. dort findet dann der zeichenvorgang statt. eine weitere verringerung der timer frequenz bringt nichts mehr.

    meine frage ist daher: wie kann ich hier die performance erhöhen?
    in den faq's habe ich etwas über backbuffering gelesen, ist das vllt. die lösung? oder ist grundsätzlich mein ansatz mit dem timer der falsche weg?

    schon mal vielen dank für eure antworten 🙂

    gruß pacy



  • Die WinAPI ist nicht für halsbrecherische Geschwindigkeiten gemacht 😉 Wäre vielleicht z.B. eingebettetes OpenGL etwas für dich?



  • Zeichne auf ein Bitmap im Speicher und blitte diesen immer wenn nötig auf den Canvas.



  • danke für die antworten!

    das heisst also, dass das mit dem timer gängige praxis ist, ich aber damit an die leistungsgrenzen stoße und es günstiger wäre, die operationen im speicher zu tätigen und nur zu blitten? oder ist meine art und weise schon gänzlich falsch und es gibt eine bessere lösung?

    mit opengl oder anderen zusätzlichen dingen wollte ich mich jetzt am anfang eigentlich nicht schon beschäftigen (müssen) 🙂

    gruß pacy



  • BackBuffer ist bei GDI bei viel Zeichnerei meiner Meinung nach immer gut 😉
    Die GDI-Objekte wie Brushes, Pens, etc. würde ich auch nur einmal erstellen, statt sie jedesmal neu zu erzeugen und gleich wieder zu löschen wenn WM_PAINT aufgerufen wird.

    ShowCursor() könnte man bestimmt auch noch auslagern.
    GetClientRect() und GetCursorPos() rufen Infos ab die du auch via WM_SIZE bzw. WM_MOUSEMOVE bekommst und nicht bei jedem WM_PAINT aufgerufen werden müssten - Ob das großartig was zur Performance tut bezweifle ich aber mal 😉

    Bei WM_TIMER rufst du WM_PAINT selbst auf, microsoft findet das nich so gut:

    msdn schrieb:

    The WM_PAINT message is generated by the system and should not be sent by an application.

    InvalidateRect() + UpdateWindow() wird da meistens eher verwendet.

    Der Timer selbst ist übrigens nicht sonderlich genau, kann gut sein das er auch bei kleineren Werten nur alle 10ms loslegt und dein WM_PAINT eigentlich keine 10ms braucht.


  • Mod

    Grundsätzlich muss ein Objekt, dass selbst erzeugt wurde und in einen DC geladen wurde auch wieder aus dem DC deselekiert werden. Ansonsten bekommst Du auch Speicherleaks.



  • danke euch!

    gruß pacy


Anmelden zum Antworten