Эффективный способ настройки пикселей в GDI - PullRequest
0 голосов
/ 28 декабря 2018

Я создал базовую программу, которая рендерит спрайты в консоли Windows, используя метод SetPixel(), и она отлично работает, но с большими накладными расходами.Я сделал несколько оптимизаций для этого, что помогло, но это все еще слишком медленно.

В настоящее время моя программа использует два буфера COLORREF, рисует новые на экране, меняет их и запускает заново.Однако он перерисовывает пиксель только в том случае, если указанный пиксель изменился.Это значительно улучшило производительность, но все еще медленно.Замена буфера пока не выполняется с указателями, но реальные издержки составляют SetPixel(), поэтому я ищу альтернативный способ создания графики на уровне пикселей с помощью GDI, который быстрее, чем SetPixel() (игнорируйте anim_frame ипервое измерение вектора img_data, они просто на будущее, если я решу добавить анимированные объекты)

void graphics_context::update_screen()
{
update_buffer();

for (int x = 0; x < this->width; x++)
{
    for (int y = 0; y < this->height; y++)
    {
        if (this->buffer.at(x).at(y) != this->buffer_past.at(x).at(y))
        {
            for (int i = 0; i < this->scale_factor; i++)
            {
                for (int j = 0; j < this->scale_factor; j++)
                {
                    int posX = i + (this->scale_factor  * x) + this->width_offset;
                    int posY = j + (this->scale_factor  * y) + this->height_offset;

                    SetPixel(this->target_dc, posX, posY, this->buffer.at(x).at(y));
                }
            }
        }
    }
}

buffer_past = buffer;
}

И это метод update_buffer():

void graphics_context::update_buffer()
{
for (int x = 0; x < this->width; x++)
{
    for (int y = 0; y < this->height; y++)
    {
        buffer.at(x).at(y) = RGB(0, 0, 0);
    }
}

//this->layers.at(1)->sprite; <- pointer to member gfx_obj pointer

for (int i = 0; i < this->layers.size(); i++)
{
    gfx_object tmp_gfx = *this->layers.at(i)->sprite;

    for (int x = 0; x < tmp_gfx.img_data.at(0).size(); x++)
    {
        for (int y = 0; y < tmp_gfx.img_data.at(tmp_gfx.anim_frame).at(0).size(); y++)
        {
            if(tmp_gfx.img_data.at(tmp_gfx.anim_frame).at(x).at(y) != RGB(0,255,0))
            buffer.at(x + this->layers.at(i)->locX).at(y + this->layers.at(i)->locY) = tmp_gfx.img_data.at(tmp_gfx.anim_frame).at(x).at(y);
        }
    }
}
}

1 Ответ

0 голосов
/ 29 декабря 2018

В идеале вы хотите использовать 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, которая лучше интегрирована с графическим процессором

...