Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Системное программирование и WinAPI > Плавная перерисовка экрана


Автор: MironenkoDima 15.6.2013, 03:55
Здравствуйте.
Подскажите, пожалуйста, как реализовать плавное обновление экрана в программе.
Вся графика выводится с использованием библиотеки GDI+. Программа позволяет перемещаться по рабочей области с помощью зажатия курсора мыши и т.п. манипуляции, при таком подходе экран мерцает при перерисовки. Как это можно преодолеть?

Автор: Dem_max 15.6.2013, 06:00
Двойное буфферизирование ?????

Автор: Earnest 15.6.2013, 17:25
Т.е. буферизация.
Другими словами - рисуем в памяти, дураку полработы не показываем, а показываем все сразу, плюхнув эту память на экран, что, как правило, быстро. Ну и убираем лишние очистки экрана, которые тоже вызывают мерцания. (В виндовсе они по-умолчанию на каждом шагу.)

Автор: MironenkoDima 15.6.2013, 17:39
Earnest, спасибо за развёрнутый ответ!
Буду копать дальше )

Автор: MironenkoDima 19.6.2013, 18:12
Под руки попалась книжка "Компьютерная графика" Виктора Порева. В ней нашёл кое-что про двойную буферизацию.
Рассмотрено такое применение:
Код
void DrawStudyExample(HWND hWnd)
{
    HDC hdcWin, hdc;
    HBITMAP hBitmap;

    hdcWin = GetDC(hWnd);
    hdc = CreateCompatibleDC(winhdc);
    hBitmap = CreateCompatibleBitmap(winhdc, 400, 300);
    SelectObject(hdc, hBitmap);
    Rectangle(hdc, -1, -1, 401, 301); // очистка
    // тут рисование
    BitBlt(hdcWin, 0, 0, 400, 300, hdc, 0, 0, SRCCOPY);
    DeleteDC(hdc);
    DeleteObject(hBitmap);
    ReleaseDC(hWnd, hdcWin);
}


Попробовал я сделать подобное в своём проекте:
Код

case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC winhdc, hdc;
    HBITMAP hBitmap;
    RECT rect;

    winhdc = BeginPaint(hWnd, &ps);
    hdc = CreateCompatibleDC(winhdc);

    GetClientRect(hWnd, &rect);
    hBitmap = CreateCompatibleBitmap(winhdc, rect.right, rect.bottom);
    SelectObject(hdc, hBitmap);
    Rectangle(hdc, -1, -1, rect.right+1, rect.bottom+1);
        
    DrawGraphic(winhdc, hdc, rect);
            
    DeleteDC(hdc);
    DeleteObject(hBitmap);
    EndPaint(hWnd, &ps);
            
    return 0;
}

void DrawGraphic(HDC winhdc, HDC hdc, RECT rect)
{
    Graphics graphics(hdc);
    // рисование
    BitBlt(winhdc,0,0,rect.right,rect.bottom, hdc,0,0,SRCCOPY);
}


К сожалению не помогло.. Не подскажите, что можно изменить?

Автор: Dem_max 20.6.2013, 03:57
Код

HDC g_hdcWin,g_ hdcMem;
HBITMAP g_hBitmap, g_hOldBitmap;

void Init(HWND hWnd)
{
    GetClientRect(hWnd, &rect);
    g_hdcWin = GetDC(hWnd);
    g_hdcMem = CreateCompatibleDC(g_hdcWin);
    g_hBitmap = CreateCompatibleBitmap(g_hdcWin, rect.right, rect.bottom);
    g_hOldBitmap = (HBITMAP)SelectObject(g_hdcMem, g_hBitmap);
}

void UnInit(HWND hWnd)
{
    SelectObject(g_hdcMem, g_hOldBitmap);
    DeleteObject(g_hBitmap);
    DeleteDC(g_hdcMem);
    ReleaseDC(hWnd, g_hdcWin);
}

void DrawStudyExampleToMemory(HWND hWnd)
{
    // тут рисование
    GetClientRect(hWnd, &rect);
    Rectangle(g_hdcMem, 0, 0, rect.right, rect.bottom); // очистка
    
}

case WM_PAINT:
{
    PAINTSTRUCT ps = {0};
    HDC hDC = BeginPaint(hwnd, &ps);
    GetClientRect(hwnd, &rect);
    BitBlt(hDC, 0, 0, rect.right, rect.bottom, g_hdcMem, 0, 0, SRCCOPY);
    EndPaint(hwnd, &ps);
    return 0;
}

Автор: Earnest 20.6.2013, 05:00
Не помогло в каком смысле? Не рисуется? По прежнему мерцает?

Автор: MironenkoDima 20.6.2013, 18:35
Цитата(Earnest @  20.6.2013,  05:00 Найти цитируемый пост)
Не помогло в каком смысле? Не рисуется? По прежнему мерцает?

Рисует, но по-прежнему мерцает.


Dem_max, не совсем понял, в какое место Init и UnInit вставлять? Рабочая область у меня меняется, т.е. пользователь может увеличить/уменьшить размеры. Неужели в WM_SIZE постоянно обрабатывать?

И правильно ли я понял, что перед всеми InvalidateRect'ов нужно вызывать функцию DrawStudyExampleToMemory(из вашего примера)? А в WM_PAINT'е оставить только функцию переноса BitBlt ?


-----------------------------------

Так-с, попробовал ваш пример. В принципе, мерцаний стало гораздо меньше, только теперь пропала плавность. Изменения происходят с дёрганием

Автор: Dem_max 20.6.2013, 19:49
Цитата

в какое место Init и UnInit вставлять?

в начале и конце программы.


Цитата

Рабочая область у меня меняется, т.е. пользователь может увеличить/уменьшить размеры. Неужели в WM_SIZE постоянно обрабатывать?

Да. И постоянно вызывать UnInit() а потом Init().

Цитата

А в WM_PAINT'е оставить только функцию переноса BitBlt ?

WM_PAINT не трогай, что я нарисовал то и вставляй.

Цитата

И правильно ли я понял, что перед всеми InvalidateRect'ов нужно вызывать функцию DrawStudyExampleToMemory(из вашего примера)?

нет, не обязательно. InvalidateRect 'bh всего лишь одна из функций которая заставляет вызвать WM_PAINT

DrawStudyExampleToMemory() вызывается в любой момент времени после Init() и пока не наступит UnInit()

Автор: MironenkoDima 21.6.2013, 00:23
Вставил вызов Init в WM_CREATE, вызов UnInit в WM_DESTROY,  пару UnInit+Init в WM_SIZE. Обработку WM_PAINT скопировал из вашего примера.
Все прошлые вызовы InvalidateRect заменил на вызов функции, в которой рисуется вся графика, а в конец этой функции поставил InvalidateRect.
Как сказал выше, теперь пропала плавность перерисовки..

Автор: Dem_max 21.6.2013, 03:25
Как может пропасть "плавность" перерисовки если в WM_PAINT только 1 функция и то самая быстрая для переноса графики ????
Код

case WM_PAINT:
{
    PAINTSTRUCT ps = {0};
    HDC hDC = BeginPaint(hwnd, &ps);
    // Попробуй GetClientRect вынести за пределы этой функции
    GetClientRect(hwnd, &rect);

    // !!! это самая быстрая функция переноса данных, выполняется аппаратно.
    BitBlt(hDC, 0, 0, rect.right, rect.bottom, g_hdcMem, 0, 0, SRCCOPY);
    EndPaint(hwnd, &ps);
    return 0;
}


Автор: MironenkoDima 21.6.2013, 04:05
Попробовал. Сделал rect - глобальной. И поместил GetClientRect в Init. Не помогло..

Может у меня нарушена "общая" логика обновления, но тут я даже не знаю, как можно по-другому.
Всякий раз, когда мне нужно обновить экран, вызываю свою функцию рисования. В ней создаётся класс Graphics(gdi+) по дескриптору на контекст граф.устройства, который создаём в памяти(g_hdcMem из примера). Используются различные методы класса Graphics(не мало методов). В конце этой функции находится InvalidateRect..

Автор: Dem_max 21.6.2013, 06:26
Кидай сюда сокращенный код(проект) я посмотрю где накосячил.
P.S. ну и расскажи что я должен увидеть и как ты определил что все не плавно.

Автор: Earnest 21.6.2013, 07:11
MironenkoDima, за мерцание может отвечать стирание фона. По-умолчанию оно почти всегда есть. Бороться 2 способами: при регистрации класса окна задать нулевую кисть, либо перехватить WM_ERASEBKGND и ничего не делать (и вернуть TRUE... или FALSE... не помню, в MSDN смотри, там написано, чтобы система не лезла).
Что касается принципа двойной буферизации, то он таков: на WM_PAINT ты практически всегда  ТОЛЬКО копируешь на экран внеэкранный буфер. Рисовать во внеэкранный буфер можно в любой момент (и даже в отдельном потоке). Хотя можно и на PAINT, но совсем не каждый раз, а только при необходимости перерисовать внутренний буфер. Например, если твое окно временно перекрылось другим, при восстановлении буфер перестраивать не нужно.

Бывают случаи, когда рисование происходит как бы двумя частями: на относительно постоянном фоне рисуют какие-то детали. Например, у тебя сложный чертеж, а ты добавляешь линию. Или нужно показать какой-то объект выделенным. Очевидно, что в этом случае неэффективно рисовать все целиком, чтобы добавить (или стереть) фрагмент линии. Тогда либо переменную часть рисуют прямо на экран, либо держат 2 буфера.

Как-то так. Надеюсь, принцип понятен.

Автор: Dem_max 21.6.2013, 10:24
Сделай так
Код

    WNDCLASSEX wcex = {0};

    wcex.cbSize          = sizeof(WNDCLASSEX);
    wcex.style           = 0    ;//CS_HREDRAW | CS_VREDRAW;  <--------------- !!!!

Автор: MironenkoDima 21.6.2013, 16:33
Цитата(Earnest @  21.6.2013,  07:11 Найти цитируемый пост)
Бывают случаи, когда рисование происходит как бы двумя частями: на относительно постоянном фоне рисуют какие-то детали. Например, у тебя сложный чертеж, а ты добавляешь линию. Или нужно показать какой-то объект выделенным. Очевидно, что в этом случае неэффективно рисовать все целиком, чтобы добавить (или стереть) фрагмент линии. Тогда либо переменную часть рисуют прямо на экран, либо держат 2 буфера.

Вот это практически про мой проект. А у меня именно всё и перерисовывается..


Dem_max, попробовал убрать эти два стиля - безрезультатно.
Прикреплю свои исходники (сразу предупреждаю: быдло-код), компилирую в OpenWatcom'e, файлов немного, поэтому сам проект не приложил.

Автор: Dem_max 21.6.2013, 18:35
Херня какая то, завтра попробую на основе своего контрола попробовать типа как у тебя сделать, посмотрю что выйдет.

Автор: Earnest 24.6.2013, 07:13
Dem_max, совет насчет стилей неправильный. Эти флаги отвечают за вызов перерисовки при изменении размеров (что почти всегда полезно), а не за то, КАК перерисовка происходит.
MironenkoDima, я уже пару раз писала про стирание фона, что его нужно убрать. Оно вообще не нужно при двойной буферизации. 

Автор: MironenkoDima 24.6.2013, 18:04
Извините, что пропал..

Dem_max, спасибо за пробы! И, конечно, спасибо за приведенный код!

Earnest, на счёт стилей: в моём случае это не критично, т.к. в WM_SIZE заново происходит инициализация контекста и последующее обновление экрана.
Я наконец прислушался к Вашим словам на счёт стирания фона. Добавил в конец своей функции рисования следующее:
Код

InvalidateRect(hwnd, NULL, FALSE);
UpdateWindow(hwnd);

И в итоге получил то, что нужно было изначально!

Всем большое спасибо за помощь! Пока остановлюсь на таком варианте!

Автор: GremlinProg 25.6.2013, 06:46
Цитата(MironenkoDima @  24.6.2013,  20:04 Найти цитируемый пост)
в WM_SIZE заново происходит инициализация контекста

Цитата(MironenkoDima @  24.6.2013,  20:04 Найти цитируемый пост)
Добавил в конец своей функции рисования следующее:
Код

InvalidateRect(hwnd, NULL, FALSE);
UpdateWindow(hwnd);

И в итоге получил то, что нужно было изначально!

Вот это жесть! Только ни кому больше не показывайте свой код. Это просто малина для троллей  smile 

Автор: MironenkoDima 25.6.2013, 15:47
GremlinProg, если вы про всю программу, то спорить не буду) Первый опыт написания подобного, поэтому каждый новый шаг - неизвестность для меня. Приходится изучать так или иначе..
А если про UnInit и Init в WM_SIZE, то это обсуждалось выше.. Ну а UpdateWindow(hwnd) - единственное, что смог придумать. Ибо без него перерисовка с "тормозами", если так можно выразиться.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)