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.
-
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