Мерцание при перемещении / изменении размера окна

Я разработал приложение для отображения изображений JPEG. Он может отображать 4 изображения, по одному в каждом квадранте экрана. Для этого используется 4 окна. Окна не имеют ни рамки (рамки), ни заголовка. Когда загружается новое изображение, размер окна корректируется для нового изображения, а затем изображение отображается.

Особенно, когда окно становится больше, часто возникает мерцание. Я смотрю в прорези, кажется, что старое содержимое перемещается при изменении размера до отображения нового содержимого.

Я справился со многими ресурсами и использовал все приемы:

  • окно имеет только стиль CS_DBLCLKS (нет CS_HREDRAW или же CS_VREDRAW);

  • кисть фона имеет значение NULL;

  • WM_ERASEBKGND возвращает 1;

  • WM_NCPAINT возвращает 0;

  • WM_NCCALCSIZE говорит, чтобы выровнять в сторону, не перемещенную (можете ли вы сказать, чтобы сбросить клиентскую область?);

  • WM_WINDOWPOSCHANGING возвращает 0;

  • SetWindowPos имеет флаги SWP_NOCOPYBITS | SWP_DEFERERASE | SWP_NOREDRAW | SWP_NOSENDCHANGING,

Тем не менее, мерцание (или перемещение содержимого) возникает при изменении размера окна. Что я хочу это:

  • SetWindowPos для нового размера и позиции;

  • InvalidateRect (hWnd, NULL, FALSE);

  • UpdateWindow (HWND);

без рисования, стирания фона или перемещения контента до WM_PAINT,

WM_PAINT делает:

hDC= BeginPaint (hWnd, &ps);
hMemDC= CreateCompatibleDC (hDC);
hOldBitmap = SelectObject (hMemDC, hNewBitmap);
BitBlt (hDC,...,hMemDC,0,0,SRCCOPY);
SelectObject (hMemDC, hOldBitmap);
DeleteDC (hMemDC);
EndPaint (hWnd, &ps);

Может кто-нибудь сказать мне, если / где я делаю ошибку, которая вызывает перемещение старого содержимого окна?

Аппаратное обеспечение и т. Д.: Windows 7 на HP Elitebook Core7 64 бит с драйвером NVIDIA Quadro K1000m 9.18.13.3265 (обновлено до 341.44).


ОБНОВЛЕНИЕ (июль 17)

Я видел поведение программы также на другом компьютере с Windows (Windows 8/10). Это не похоже на драйвер дисплея NVIDIA.

Такое поведение наиболее заметно при изменении размера окна, расположенного по центру экрана (справа внизу = w/2, h/2) в левом или левом верхнем углу (0, 0).

У меня могут быть проблемы с вычислениями для сообщения WM_NCCALCSIZE, чтобы заставить Windows ничего не делать. Может ли кто-нибудь привести пример расчета для моей цели? См. Также Как заставить Windows НЕ перерисовывать что-либо в моем диалоге, когда пользователь изменяет размер моего диалога?

4 ответа

У вас действительно есть хороший набор трюков.

Во-первых, я могу предложить несколько вариантов существующих приемов, которые могут особенно помочь в XP/Vista/7, а во-вторых, я хочу упомянуть, откуда, вероятно, возникает постоянное мерцание, которое вы видите в Win8/10, и некоторые приемы, чтобы уменьшить это.

Во-первых, несмотря на совет об обратном в других постах OP, вы можете обнаружить, что если вы установите CS_HREDRAW|CS_VREDRAW стилей окон, которых вы раньше избегали, это фактически устраняет BitBlt что делается внутри SetWindowPos Windows делает это во время изменения размера окна (но - и это запутанная часть - в Windows 8/10 вы все равно увидите мерцание из другого источника... подробнее об этом ниже).

Если вы не хотите включать CS_HREDRAW|CS_VREDRAWВы также можете перехватить WM_WINDOWPOSCHANGING (сначала передавая его на DefWindowProc) и установить WINDOWPOS.flags |= SWP_NOCOPYBITS, который отключает BitBlt внутри внутреннего звонка SetWindowPos() что Windows делает во время изменения размера окна.

Или вы можете добавить в свой WM_NCCALCSIZE обмануть, вернув ему набор значений, который скажет Windows просто BitBlt 1 пиксель сверху самого себя, так что даже если BitBlt действительно случается, это ничего не делает заметно:

case WM_NCCALCSIZE:
{
    RECT ocr; // old client rect
    if (wParam)
    {
        NCCALCSIZE_PARAMS *np = (NCCALCSIZE_PARAMS *)lParam;
        // np->rgrc[0] is new window rect
        // np->rgrc[1] is old window rect
        // np->rgrc[2] is old client rect
        ocr = np->rgrc[2];
    }
    else
    {
        RECT *r = (RECT *)lParam;
        // *r is window rect
    }

    // first give Windoze a crack at it
    lRet = DefWindowProc(hWnd, uMsg, wParam, lParam);

    if (wParam)
    {
        NCCALCSIZE_PARAMS *np = (NCCALCSIZE_PARAMS *)lParam;

        // np->rgrc[0] is new client rect computed
        // np->rgrc[1] is going to be dst blit rect
        // np->rgrc[2] is going to be src blit rect
        // 
        ncr = np->rgrc[0];
        RECT &dst = np->rgrc[1];
        RECT &src = np->rgrc[2];

        // FYI DefWindowProc gives us new client rect that
        // - in y
        //   - shares bottom edge if user dragging top border
        //   - shares top edge if user dragging bottom border
        // - in x
        //   - shares left edge if user dragging right border
        //   - shares right edge if user dragging left border
        //
        src = ocr;
        dst = ncr; 

        // - so src and dst may have different size
        // - ms dox are totally unclear about what this means
        // https://docs.microsoft.com/en-us/windows/desktop/
        //         winmsg/wm-nccalcsize
        // https://docs.microsoft.com/en-us/windows/desktop/
        //         api/winuser/ns-winuser-tagnccalcsize_params
        // - they just say "src is clipped by dst"
        // - they don't say how src and dst align for blt
        // - resolve ambiguity

        // essentially disable BitBlt by making src/dst same
        // make it 1 px to avoid waste in case Windoze still does it

        dst.right  = dst.left + 1;
        dst.bottom = dst.top  + 1;

        src.right  = dst.left + 1;
        src.bottom = dst.top  + 1;

        lRet = WVR_VALIDRECTS;
    }
    else // wParam == 0: Windoze wants us to map a single rect w->c
    {
        RECT *r = (RECT *)lParam;
        // *r is client rect
    }

    return lRet;

Это все очень хорошо, но почему Windows 8/10 выглядит так плохо?

Приложения под Windows 8/10 Aero не рисуют прямо на экране, а рисуют в закадровых буферах, которые затем создаются злым оконным менеджером DWM.exe. DWM фактически добавляет еще один слой BitBltповедение поверх существующей устаревшей XP / Vista / 7 BitBlt поведение.

Блиц-поведение DWM еще более сумасшедшее, потому что они не просто копируют клиентскую область, но фактически копируют пиксели по краям вашей старой клиентской области, чтобы создать новую. К сожалению, заставить DWM не делать блиц намного сложнее, чем просто передать несколько дополнительных флагов.

У меня нет 100% -го решения, но я надеюсь, что приведенная выше информация поможет, и, пожалуйста, просмотрите этот раздел вопросов и ответов, чтобы узнать, как использовать трюк с таймингами, чтобы уменьшить вероятность появления блика на уровне DWM в вашем приложении:

Как сгладить уродливый джиттер / мерцание / прыжок при изменении размеров окон, особенно перетаскивая левую / верхнюю границу (Win 7-10; bg, bitblt и DWM)?

У вас есть внушительный список анти-мерцающих трюков:-)

Я не знаю, важно ли это (поскольку это зависит от того, как создаются окна инструментов и, особенно, если они являются дочерними окнами для общего родителя): Попробуйте установить стиль окна WS_CLIPCHILDREN в родительском окне инструментов (если оно есть).

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

Это теория больше, чем ответ:

По умолчанию в современной Windows ваше окно - это просто текстура на видеокарте, а менеджер окон рабочего стола отображает ее в прямоугольник на экране. Вы, кажется, сделали все необходимое, чтобы убедиться, что текстура обновляется одним махом.

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

Вы можете проверить эту теорию, временно отключив компоновку рабочего стола. В Windows 7 перейдите в "Свойства системы", выберите вкладку "Дополнительно", в разделе "Производительность" выберите "Настройки", на вкладке "Визуальные эффекты" отмените выбор параметра "Включить композицию на рабочем столе". Затем попробуйте воспроизвести проблему. Если это уходит, то это поддерживает (но не абсолютно доказывает) мою теорию.

(Не забудьте включить композитинг, так как большинство ваших пользователей будет работать большую часть времени.)

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

По иронии судьбы, ваши усилия по устранению мерцания могут сработать против вас, поскольку вы намеренно подавили аннулирование и перерисовку, которые обычно возникают в результате SetWindowPos, пока вы не сделаете это вручную на более позднем этапе.

Совет по отладке: попробуйте ввести задержки (например, Sleep(1000);) в ключевых точках процесса, чтобы вы могли видеть, действительно ли изменение размера и перерисовка отображаются на экране как два отдельных шага.

Просто в дополнение к вашему списку трюков. У меня та же проблема с мерцанием на моем ноутбуке Dell XPS при изменении размера окна с помощью левого / верхнего края. Я перепробовал все трюки, которые вы упомянули. Насколько я понимаю, граница окна рисуется в графическом процессоре, а содержимое окна подготавливается в подсистеме GDI и передается в видеопамять для композиции окна (DWM представлен в Windows 8.1). Я попытался полностью удалить рендеринг GDI (установив стиль WS_EX_NOREDIRECTIONBITMAP), который создает окно без какого-либо содержимого, а затем создать поверхность содержимого непосредственно, используя подсистему DXGI (используя CreateSwapChainForComposition, есть несколько примеров того, как это сделать). Но проблема остается. Между рендерингом оконной рамки и изменением размеров / отображением поверхности контента все еще существует задержка.

Однако это может решить вашу проблему, поскольку у вас нет границы, и вы не заметите эту задержку. Но вы будете иметь полный контроль над перерисовкой окна, и это будет сделано на стороне GPU.

Другие вопросы по тегам