В идеале вы хотите использовать BitBlt
и рисовать на экране один раз для каждого кадра.
В противном случае вы выполняете несколько вызовов рисования для каждого кадра, и рисование замедляется с мерцанием.Например:
case WM_PAINT:
{
PAINTSTRUCT ps;
auto hdc = BeginPaint(hwnd, &ps);
for (...)
SetPixelV(hdc, ...) //<- slow with possible flicker
EndPaint(hwnd, &ps);
return 0;
}
Основная проблема не в SetPixel
, а в том, что мы делаем тысячи запросов на отрисовку графической карты для каждого кадра.
Мы можем решить эту проблему, используя буфер в виде «контекста устройства памяти»:
HDC hdesktop = GetDC(0);
memdc = CreateCompatibleDC(hdesktop);
hbitmap = CreateCompatibleBitmap(hdesktop, w, h);
SelectObject(memdc, hbitmap);
Теперь вы можете делать все свои рисунки на memdc
.Эти рисунки будут быстрыми, поскольку они не отправляются на видеокарту.Как только вы закончите рисовать на memdc
, вы BitBlt
memdc
на фактическом hdc
для контекста целевого оконного устройства:
//draw on memdc instead of drawing on hdc:
...
//draw memdc on to hdc:
BitBlt(hdc, 0, 0, w, h, memdc, 0, 0, SRCCOPY);
На практике вам редко требуется SetPixel
.Обычно вы загружаете растровое изображение в фон и спрайт, а затем рисуете все на memdc
и BitBlt
на hdc
.
В Windows Vista и выше вы можете использовать BeginBufferedPaint
рутина, которая может быть немного более удобной.Пример:
#ifndef UNICODE
#define UNICODE
#endif
#include <Windows.h>
class memory_dc
{
HDC hdc;
HBITMAP hbitmap;
HBITMAP holdbitmap;
public:
int w, h;
memory_dc()
{
hdc = NULL;
hbitmap = NULL;
}
~memory_dc()
{
cleanup();
}
void cleanup()
{
if(hdc)
{
SelectObject(hdc, holdbitmap);
DeleteObject(hbitmap);
DeleteDC(hdc);
}
}
void resize(int width, int height)
{
cleanup();
w = width;
h = height;
HDC hdesktop = GetDC(0);
hdc = CreateCompatibleDC(hdesktop);
hbitmap = CreateCompatibleBitmap(hdesktop, w, h);
holdbitmap = (HBITMAP)SelectObject(hdc, hbitmap);
ReleaseDC(0, hdc);
}
//handy operator to return HDC
operator HDC() { return hdc; }
};
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
static memory_dc buffer;
static memory_dc sprite;
static memory_dc background;
switch(msg)
{
case WM_CREATE:
{
RECT rc;
GetClientRect(hwnd, &rc);
buffer.resize(rc.right, rc.bottom);
background.resize(rc.right, rc.bottom);
sprite.resize(20, 20);
//draw the background
rc = RECT{ 0, 0, sprite.w, sprite.h };
FillRect(sprite, &rc, (HBRUSH)GetStockObject(GRAY_BRUSH));
//draw the sprite
rc = RECT{ 0, 0, background.w, background.h };
FillRect(background, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH));
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
auto hdc = BeginPaint(hwnd, &ps);
//draw the background on to buffer
BitBlt(buffer, 0, 0, background.w, background.w, background, 0, 0, SRCCOPY);
//draw the sprite on top, at some location
//or use TransparentBlt...
POINT pt;
GetCursorPos(&pt);
ScreenToClient(hwnd, &pt);
BitBlt(buffer, pt.x, pt.y, sprite.w, sprite.h, sprite, 0, 0, SRCCOPY);
//draw the buffer on to HDC
BitBlt(hdc, 0, 0, buffer.w, buffer.w, buffer, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
return 0;
}
case WM_MOUSEMOVE:
InvalidateRect(hwnd, NULL, FALSE);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int)
{
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.lpfnWndProc = WndProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszClassName = L"classname";
RegisterClassEx(&wcex);
CreateWindow(wcex.lpszClassName, L"Test", WS_VISIBLE | WS_OVERLAPPEDWINDOW,
0, 0, 600, 400, 0, 0, hInstance, 0);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
Обратите внимание, этого будет достаточно для простых рисунков.Но функции GDI не могут обрабатывать матрицы и т. Д., Они имеют ограниченную поддержку прозрачности, поэтому вы можете использовать другую технологию, например Direct2D, которая лучше интегрирована с графическим процессором