Уменьшите мерцание с помощью GDI + и C ++ - PullRequest
20 голосов
/ 13 октября 2008

Я использую GDI + в приложении C ++ / MFC и просто не могу избежать мерцания при изменении размера окна.

Я уже пробовал эти шаги:

  • вернул TRUE OnEraseBkGnd();
  • вернул NULL на OnCtlColor();
  • используется двойная буферизация по этому коду:

void vwView::OnDraw(CDC* pDC) 
{
   CRect rcClient;
   GetClientRect(rcClient);

   Bitmap bmp(rcClient.Width(), rcClient.Height());
   Graphics graphics(&bmp);

   graphics.DrawImage(m_image, rcClient.left, rcClient.top);

   Graphics grph(pDC->m_hDC);
   grph.DrawImage(&bmp, 0, 0);
}

Я что-то не так делаю? Или есть другой способ добиться этого?

Ответы [ 6 ]

40 голосов
/ 14 октября 2008

Чтобы полностью избежать мерцания, вам необходимо выполнить все рисование в промежутке между обновлениями экрана. Windows не предоставляет каких-либо простых способов сделать это для обычной окраски окон (Vista предоставляет составной чертеж через DWM , но на это нельзя полагаться даже в системах, работающих под управлением Vista). Поэтому лучшее, что вы можете сделать, чтобы свести к минимуму мерцание, это нарисовать все как можно быстрее ( уменьшить разрыв, увеличивая ваши шансы на завершение всего рисования в цикле обновления), и избежать перерисовки (рисование части экрана а затем рисовать что-то еще поверх: рискует представить пользователю частично нарисованный экран).

Давайте обсудим методы, представленные здесь до сих пор:

  • Ничего не делать OnEraseBkgnd () : помогает избежать перерисовки, предотвращая заполнение недействительной области окна цветом фона окна. Полезно, когда вы снова будете рисовать всю область во время WM_PAINT обработки в любом случае , как в случае рисования с двойной буферизацией ... но смотрите Примечания по предотвращению перерисовки путем предотвращения рисование по вашему WM_PAINT методу .

  • Возврат NULL для OnCtlColor () : на самом деле это не должно делать что-либо ..., если в вашей форме нет дочерних элементов управления. В этом случае см. Примечания по предотвращению перерисовки путем предотвращения рисования вместо вашего WM_PAINT метода вместо этого.

  • Рисунок с двойной буферизацией : помогает избежать разрыва (и, возможно, также перерисовки), уменьшая фактическое изображение на экране до одного BitBLT . Хотя это может повредить время, необходимое для рисования: аппаратное ускорение не может быть использовано (хотя при использовании GDI + шансы на использование любого аппаратного рисования весьма невелики), необходимо создавать и заполнять закадровое растровое изображение для каждой перерисовки, а также все окно должно быть перекрашено для каждой перерисовки. См. Примечания по эффективной двойной буферизации .

  • Использование вызовов GDI вместо GDI + для BitBlt : Это часто хорошая идея - Graphics::DrawImage() может быть очень медленным. Я даже обнаружил, что обычный вызов GDI BitBlt() работает быстрее в некоторых системах. Поиграйте с этим, но только после того, как попробуете несколько других предложений.

  • Избегание стилей класса окна, которые вызывают полное перерисовывание при каждом изменении размера ( CS_VREDRAW , CS_HREDRAW ) : это поможет, но только если вы не нужно , чтобы перерисовать все окно при изменении размера.

Замечания о том, как избежать оверлея путем предотвращения рисования до вашего WM_PAINT метода

Когда все или часть окна будут признаны недействительными, они будут стерты и перекрашены. Как уже отмечалось, вы можете пропустить удаление, если планируете перекрасить всю недействительную область. Однако , если вы работаете с дочерним окном, то вы должны убедиться, что родительское окно (окна) также не стирает вашу область экрана. Стиль WS_CLIPCHILDREN должен быть установлен во всех родительских окнах - это предотвратит рисование областей, занятых дочерними окнами (включая ваш вид).

Замечания о том, как избежать оверлея путем предотвращения рисования после вашего WM_PAINT метода

Если у вас есть любые дочерние элементы управления, размещенные в вашей форме, вы можете использовать стиль WS_CLIPCHILDREN , чтобы не рисовать их (и впоследствии не перетягивать их. , это несколько повлияет на скорость процедуры BitBlt.

Замечания по эффективной двойной буферизации

Прямо сейчас вы создаете новое изображение заднего буфера каждый раз, когда представление отрисовывается само. Для больших окон это может представлять значительный объем выделяемой и освобождаемой памяти, и приведет к значительным проблемам с производительностью. Я рекомендую сохранять динамически размещаемое растровое изображение в вашем объекте представления, перераспределяя его по мере необходимости, чтобы соответствовать размеру вашего представления.

Обратите внимание, что при изменении размера окна это приведет к тому же количеству выделений, что и к существующей системе, поскольку для каждого нового размера потребуется выделение нового растрового изображения заднего буфера для его соответствия - вы можете немного ослабить боль, округление размеров до следующего наибольшего числа, кратного 4, 8, 16 и т. д., что позволяет избежать перераспределения при каждом крошечном изменении размера.

Обратите внимание, что, если размер окна не изменился с момента последнего рендеринга в задний буфер, вам не нужно повторно отображать его, когда окно недействительно - просто выпустите уже визуализированный изображение на экран.

Кроме того, выделите растровое изображение, соответствующее битовой глубине экрана. Конструктор для Bitmap, который вы сейчас используете, по умолчанию будет 32bpp, ARGB-layout; если это не соответствует экрану, то его придется конвертировать. Попробуйте получить метод GDI CreateCompatibleBitmap(), чтобы получить соответствующее растровое изображение.

Наконец-то ... я предполагаю, что ваш пример кода - это просто иллюстративный фрагмент. Но, если вы на самом деле ничего не делаете, кроме визуализации существующего изображения на экране, тогда вам вообще не нужно поддерживать резервный буфер - просто Blt непосредственно из изображения (и заранее конвертируйте формат изображения в соответствовать экрану).

4 голосов
/ 13 октября 2008

Вы можете попытаться использовать старомодный GDI вместо GDI + для записи в DC, тем более что вы уже буферизуете изображение. Используйте Bitmap :: LockBits для доступа к необработанным данным растрового изображения, создайте структуру BITMAPINFO и используйте SetDIBitsToDevice для отображения растрового изображения.

3 голосов
/ 09 мая 2012

Вы можете получить некоторую тягу, используя Direct3D , чтобы «сообщить» вам, когда происходит vsync, и т. Д., Чтобы вы могли своевременно BitBlt / обновить. См. GDI vsync, чтобы избежать разрыва (хотя в некоторых случаях перенос данных в один маленький бит-бит может быть «достаточно хорошим»).

Также обратите внимание, что похоже, что GDI BitBlt не синхронизирован с vsync экрана. Смотри Быстрее, чем BitBlt .

Также обратите внимание, что использование CAPTUREBLT (которое позволяет захватывать прозрачные окна) вызывает мерцание мыши (если aero не используется), если используется.

3 голосов
/ 13 октября 2008

Убедитесь, что класс окна для окна не включает флаги CS_VREDRAW и CS_HREDRAW в своем стиле.

См. http://msdn.microsoft.com/en-us/library/ms633574(VS.85).aspx

2 голосов
/ 30 августа 2013

Эта ссылка содержит полезную информацию: http://www.catch22.net/tuts/flicker-free-drawing

(я знаю, что это очень позднее добавление в ветку, но это для любого (как я), кто нашел его, когда хотел уменьшить мерцание в моем приложении Win32 ...)

0 голосов
/ 13 октября 2008

Есть ли дочерние окна в форме? Диспетчер окон начинает с того, что заставляет родительское окно стирать свой фон, отправляя сообщение WM_ERASEBKGND, ТО затем отправляет сообщение wM_PAINT - предположительно это сопоставляется с вашим методом wx :: OnDraw. Затем он перебирает каждый дочерний элемент управления и заставляет их рисовать себя.

Если это ваш сценарий ... использование Vistas new aero look решит вашу проблему, поскольку оконный менеджер aero на рабочем столе выполняет автоматическую компоновку окон. Со старым оконным менеджером это лаваш.

...