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

ПРОБЛЕМА: Когда я беру границу изменения размера моего приложения Windows, особенно верхнюю или левую границы, и изменяю размер окна, содержимое окна изменяет размер "вживую" при перетаскивании, но они изменяют размер отвратительным образом, который выглядит как вопиющая ошибка даже самого начинающего пользователя: содержимое на противоположном краю окна от края, который я дико перетаскиваю джиттер/мерцание/скачок назад и вперед. В зависимости от ситуации явление может выглядеть следующим образом:

  • содержимое, которое, кажется, сходит с края окна и возвращается назад, когда мы замедляемся или перестаем перетаскивать
  • содержимое, которое, кажется, тянет в окно, периодически смещается границей различных цветов, часто черного или белого
  • серьезно уродливое "двойное изображение" с двумя перекрывающимися копиями контента, смещенными на расстояние, пропорциональное тому, насколько/насколько быстро мы перетаскиваем

Ужасное явление прекращается, как только я прекращаю перетаскивать, но во время перетаскивания оно заставляет приложение выглядеть любительским и непрофессиональным.

Не стоит преуменьшать, что эта проблема Windows свела с ума тысячи разработчиков приложений.

Вот два примера картины этого явления, любезно подготовленные к смежному вопросу Романа Старкова:

example 1: jitter

example 2: border

Другой пример, показывающий злое явление "двойного изображения" (обратите внимание на быстрые вспышки) от Кенни Лю:

example 2: double-image

Еще один пример видео о явлении с помощью диспетчера задач здесь.

ЦЕЛЬ НАСТОЯЩЕГО ВОПРОСА. Любой разработчик, столкнувшийся с этой проблемой, быстро обнаруживает, что существует по меньшей мере 30 вопросов StackOverflow, некоторые из которых были недавно, а некоторые возникли в 2008 году, полные многообещающих ответов, которые редко работают. Реальность такова, что эта проблема имеет много причин, и существующие вопросы/ответы StackOverflow никогда не проясняют более широкий контекст. Этот вопрос стремится ответить:

  • Каковы наиболее вероятные причины этого вида уродливого дрожания/мерцания/прыжков?
  • Как я могу сказать, что, потому что я вижу?
  • это причина специфическая для конкретных графических драйверов или общая для Windows?
  • как исправить каждую причину? может ли приложение это исправить?

СФЕРА ЭТОГО ВОПРОСА: Для объема этого вопроса StackOverflow, явление происходит с:

  • как родные Win32, так и управляемые приложения .NET/WPF/Windows Forms
  • и обычные окна Win32 и окна диалога Win32
  • Версии Windows, включая XP, Vista, 7, 8 и 10 (но см. Ниже темную правду о множественных причинах)

НЕ ФЛАГИРУЙТЕ ШИРОКИЙ ИЛИ ДУПАЙ: Пожалуйста, прочитайте полный ответ ниже, прежде чем отмечать как дупли или отмечать широко. Мы уже прошли этот цикл, и пользователи SO впоследствии вновь открыли вопрос, поняв следующее: этот вопрос приносит пользу сообществу, поскольку он является первым Q & A, объясняющим все различные причины дрожания изменения размера окна, чтобы пользователи могли определить, какой из причины вызывает их проблему и решить ее. Приведенные ниже ответы являются длинными, просто потому, что сложно объяснить, почему джиттер возникает в целом, а не потому, что этот вопрос охватывает множество отдельных ошибок/проблем, которые будут лучше обслуживаться отдельными вопросами и ответами. Как вы увидите, все приведенные выше перестановки (нативная/управляемая, окно/диалоговое окно, XP-10) сводятся только к двум основным причинам, но выявление того, что у вас есть, является сложной задачей. Вот о чем этот вопрос. Разделение вопроса полностью сведет на нет эту выгоду. Мы дополнительно ограничим сферу вопроса в следующем разделе...

НЕ В СФЕРЕ ЭТОГО ВОПРОСА:

  • Если в вашем приложении есть одно или несколько дочерних окон (дочерних HWND), информация в этом вопросе полезна для вас (поскольку BitBlts вызывающие BitBlts мы опишем, применяются к вашим дочерним окнам вместе с родительским окном), но при изменении размера окна у вас есть дополнительная проблема для решения, которая выходит за рамки этого вопроса: вам нужно заставить все ваши дочерние окна двигаться атомарно и синхронно с родительским окном. Для этой задачи вам, вероятно, понадобится BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos и вы можете узнать о них здесь и здесь.

  • Этот вопрос предполагает, что если ваше приложение обращается к окну, используя GDI, DirectX или OpenGL, то вы уже WM_ERASEBKGND обработчик WM_ERASEBKGND в свой wndproc который просто возвращает 1. WM_ERASEBKGND - это тайный остаток Windows от Windows 3.1, который предшествует WM_PAINT чтобы дать у вашего приложения есть шанс "стереть фон" вашего окна, прежде чем вы начнете рисовать окно... ага. Если вы позволите сообщению WM_ERASEBKGND войти в DefWindowProc(), это приведет к тому, что все ваше окно будет окрашено в сплошной цвет, обычно белый, при каждой перерисовке, включая перерисовки, которые происходят при изменении размера живого окна. В результате получается мерзкое мерцание в полноэкранном режиме, но не тот тип джиттера/мерцания/прыжков, о котором мы говорим в этом вопросе. Перехват WM_ERASEBKGND эту проблему немедленно.

  • Этот вопрос в первую очередь касается изменения размера в реальном времени путем перетаскивания границ окна с помощью мыши. Тем не менее, многое из того, что написано здесь, также относится к уродливым артефактам, которые вы можете увидеть, когда приложение вручную изменяет размер окна с помощью SetWindowPos(). Они менее заметны, потому что они мерцают на экране только на одно мгновение, а не в течение длительного периода перетаскивания.

  • Этот вопрос не о том, как сделать так, чтобы ваш код рисования для конкретного приложения работал быстрее, хотя во многих случаях это может быть решением проблемы с изменяемым размером. Если вашему приложению действительно требуется огромное количество времени для повторного отображения его содержимого при изменении размера живого окна, рассмотрите возможность оптимизации кода рисования в целом или, по крайней мере, переключитесь на более быстрый и менее качественный режим рисования во время изменения размера, перехватывая сообщения WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE для обнаружения размер.

  • Если вашему приложению вообще не удается изменить размер во время изменения размера приложения (например, оно "зависает" во время изменения размера, особенно если это OpenGL, использующий GLFW или другую библиотеку), посмотрите эти другие вопросы, в которых объясняется отвратительный вложенный/модальный цикл событий Microsoft внутри WM_SYSCOMMAND во время перетаскивания.: вот особенно этот хороший ответ, здесь, здесь, здесь и здесь.

НАЧАЛЬНЫЕ ОТВЕТЫ

Для начала мы предоставили два начальных ответа, которые вы должны прочитать в следующем порядке:

а также список исходного материала, который может помочь другим понять:

Мы надеемся, что люди дадут больше ответов с творческими способами избежать проблем, описанных в 2a и особенно 2b.

Ответ 1

ЧАСТЬ 2: выявление и устранение проблем с изменением размера Windows

Примечание: сначала вы хотите прочитать ЧАСТЬ 1, чтобы этот ответ имел смысл.

Этот ответ не решит все ваши проблемы с изменением размера.

Он организует все еще используемые идеи из других постов и добавляет несколько новых идей.

Ни одно из этих действий вообще не задокументировано в Microsoft MSDN, и то, что следует ниже, является результатом моего собственного эксперимента и просмотра других сообщений StackOverflow.

2а. Изменение размера проблем из SetWindowPos() BitBlt и фоновой заливки

Следующие проблемы возникают во всех версиях Windows. Они относятся к самым первым дням прокрутки в реальном времени на платформе Windows (Windows XP) и все еще присутствуют в Windows 10. В более поздних версиях Windows другие проблемы с изменением размера могут лежать поверх этой проблемы, как мы объясним ниже.

Вот события Windows, связанные с типичным сеансом щелчка по границе окна и перетаскивания этой границы. Отступы указывают на вложенный wndproc (вложенный из-за отправленных (не опубликованных) сообщений или из-за отвратительного модального цикла Windows, упомянутого в "НЕ В СФЕРЕ ЭТОГО ВОПРОСА" в вопросе выше):

msg=0xa1 (WM_NCLBUTTONDOWN)  [click mouse button on border]
  msg=0x112 (WM_SYSCOMMAND)  [window resize command: modal event loop]
    msg=0x24 (WM_GETMINMAXINFO)
    msg=0x24 (WM_GETMINMAXINFO) done
    msg=0x231 (WM_ENTERSIZEMOVE)      [starting to size/move window]
    msg=0x231 (WM_ENTERSIZEMOVE) done
    msg=0x2a2 (WM_NCMOUSELEAVE)
    msg=0x2a2 (WM_NCMOUSELEAVE) done

  loop:
    msg=0x214 (WM_SIZING)             [mouse dragged]
    msg=0x214 (WM_SIZING) done
    msg=0x46 (WM_WINDOWPOSCHANGING)
      msg=0x24 (WM_GETMINMAXINFO)
      msg=0x24 (WM_GETMINMAXINFO) done
    msg=0x46 (WM_WINDOWPOSCHANGING) done
    msg=0x83 (WM_NCCALCSIZE)
    msg=0x83 (WM_NCCALCSIZE) done
    msg=0x85 (WM_NCPAINT)
    msg=0x85 (WM_NCPAINT) done
    msg=0x14 (WM_ERASEBKGND)
    msg=0x14 (WM_ERASEBKGND) done
    msg=0x47 (WM_WINDOWPOSCHANGED)
      msg=0x3 (WM_MOVE)
      msg=0x3 (WM_MOVE) done
      msg=0x5 (WM_SIZE)
      msg=0x5 (WM_SIZE) done
    msg=0x47 (WM_WINDOWPOSCHANGED) done
    msg=0xf (WM_PAINT)                    [may or may not come: see below]
    msg=0xf (WM_PAINT) done
goto loop;

    msg=0x215 (WM_CAPTURECHANGED)       [mouse released]
    msg=0x215 (WM_CAPTURECHANGED) done
    msg=0x46 (WM_WINDOWPOSCHANGING)
      msg=0x24 (WM_GETMINMAXINFO)
      msg=0x24 (WM_GETMINMAXINFO) done
    msg=0x46 (WM_WINDOWPOSCHANGING) done
    msg=0x232 (WM_EXITSIZEMOVE)
    msg=0x232 (WM_EXITSIZEMOVE) done  [finished size/moving window]
  msg=0x112 (WM_SYSCOMMAND) done
msg=0xa1 (WM_NCLBUTTONDOWN) done

Каждый раз, когда вы перетаскиваете мышь, Windows выдает вам серию сообщений, показанных в цикле выше. Самое интересное, что вы получаете WM_SIZING затем WM_NCCALCSIZE затем WM_MOVE/WM_SIZE, тогда вы можете (подробнее об этом ниже) получить WM_PAINT.

Помните, мы предполагаем, что вы предоставили обработчик WM_ERASEBKGND который возвращает 1 (см. "НЕ В СФЕРЕ ЭТОГО ВОПРОСА" в приведенном выше вопросе), так что сообщение ничего не делает, и мы можем его игнорировать.

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

В течение каждой последовательности сообщений от одного перетаскивания Microsoft дает вам определенное время для самостоятельного обновления клиентской области.

Часы для этого крайнего срока, по-видимому, начинают тикать после WM_NCCALCSIZE. В случае окон OpenGL, крайний срок, очевидно, удовлетворяется, когда вы вызываете SwapBuffers() для представления нового буфера (а не когда ваш WM_PAINT вводится или возвращается). Я не использую GDI или DirectX, поэтому я не знаю, что такое эквивалентный вызов SwapBuffers(), но вы, вероятно, можете сделать правильное предположение, и вы можете проверить, вставив Sleep(1000) в различные точки вашего кода, чтобы увидеть когда приведенное ниже поведение срабатывает.

Сколько времени у вас есть, чтобы уложиться в срок? По моим экспериментам, это число составляет около 40-60 миллисекунд, но, учитывая те виды махинаций, которые обычно выполняет Microsoft, я не удивлюсь, если это число зависит от конфигурации вашего оборудования или даже от предыдущего поведения вашего приложения.

Если вы действительно обновите свою клиентскую область к крайнему сроку, то Microsoft оставит вашу клиентскую область красиво безупречной. Ваш пользователь увидит только те пиксели, которые вы нарисовали, и у вас будет максимально плавное изменение размера.

Если вы не обновите свою клиентскую область к крайнему сроку, тогда Microsoft вмешается и "поможет" вам, сначала показав некоторые другие пиксели вашему пользователю на основе комбинации метода "Заполнить некоторым цветом фона" (раздел 1c3 из ЧАСТЬ 1) и техника "Отрежьте несколько пикселей" (Раздел 1c4 ЧАСТИ 1). Что именно пиксели Microsoft показывает вашему пользователю, хорошо, сложно:

  • Если ваше окно имеет WNDCLASS.style который включает CS_HREDRAW|CS_VREDRAW (вы передаете структуру WNDCLASS в RegisterClassEx):

    • Нечто удивительно разумное происходит. Вы получаете логическое поведение, показанное на рисунках 1c3-1, 1c3-2, 1c4-1 и 1c4-2 ЧАСТИ 1. При увеличении клиентской области Windows заполнит новые открытые пиксели "фоновым цветом" (см. Ниже) на той же стороне перетаскиваемого окна. При необходимости (левая и верхняя границы), Microsoft делает BitBlt для достижения этой цели. При уменьшении клиентской области Microsoft отсекает пиксели на той же стороне окна, которое вы перетаскиваете. Это означает, что вы избегаете поистине отвратительного артефакта, из-за которого объекты в вашей клиентской области кажутся движущимися в одном направлении, а затем движутся назад в другом направлении.

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

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

  • Если у вашего окна есть WNDCLASS.style, который не включает CS_HREDRAW|CS_VREDRAW (включая диалоги, где Windows не позволяет вам установить WNDCLASS.style):

    • Windows пытается "помочь" вам, выполняя BitBlt который делает копию определенного прямоугольника пикселей из вашей старой клиентской области и записывает этот прямоугольник в определенное место в вашей новой клиентской области. Это BitBlt 1:1 (он не масштабирует и не масштабирует ваши пиксели).

    • Затем Windows заполняет другие части новой клиентской области (части, которые Windows не перезаписывала во время операции BitBlt), "фоновым цветом".

    • Операция BitBlt часто является ключевой причиной, почему изменение размера выглядит так плохо. Это связано с тем, что Windows неправильно оценивает, как ваше приложение будет перерисовывать клиентскую область после изменения размера. Windows размещает ваш контент не в том месте. BitBlt результатом является то, что когда пользователь сначала видит пиксели BitBlt а затем видит реальные пиксели, нарисованные вашим кодом, ваш контент сначала перемещается в одном направлении, а затем возвращается назад в другом направлении. Как мы объяснили в ЧАСТИ 1, это создает самый отвратительный тип артефакта изменения размера.

    • Таким образом, большинство решений для исправления проблем изменения размера включают отключение BitBlt.

    • Если вы реализуете обработчик WM_NCCALCSIZE и этот обработчик возвращает WVR_VALIDRECTS когда wParam равен 1, вы можете фактически контролировать, какие пиксели Windows копирует (BitBlts) из старой клиентской области и где Windows размещает эти пиксели в новой клиентской области. WM_NCCALCSIZE просто едва задокументирована, но посмотрите подсказки о WVR_VALIDRECTS и NCCALCSIZE_PARAMS.rgrc[1] and [2] на страницах MSDN для WM_NCCALCSIZE и NCCALCSIZE_PARAMS. Вы даже можете предоставить NCCALCSIZE_PARAMS.rgrc[1] and [2] возвращаемые значения, которые полностью запрещают Windows BitBlting любой из пикселей старой клиентской области в новую клиентскую область, или заставляют Windows BitBlt один пиксель из одного и того же места, что, по сути, одно и то же, поскольку никакие пиксели на экране не будут изменены. Просто установите оба NCCALCSIZE_PARAMS.rgrc[1] and [2] в один и тот же 1-пиксельный прямоугольник. В сочетании с устранением "цвета фона" (см. Ниже), это дает вам возможность предотвратить растушевку Windows пикселями вашего окна, прежде чем вы успеете их нарисовать.

    • Если вы реализуете обработчик WM_NCCALCSIZE и он возвращает что-либо, кроме WVR_VALIDRECTS когда wParam равен 1, то вы получаете поведение, которое (по крайней мере в Windows 10) совсем не похоже на то, что говорит MSDN. Кажется, что Windows игнорирует все флаги выравнивания влево/вправо/вверх/вниз, которые вы возвращаете. Я советую вам не делать этого. В частности, популярная статья StackOverflow Как заставить Windows НЕ перерисовывать что-либо в моем диалоге, когда пользователь изменяет размер моего диалога? возвращает WVR_ALIGNLEFT|WVR_ALIGNTOP и теперь, по крайней мере, в моей тестовой системе Windows 10 эта WVR_ALIGNLEFT|WVR_ALIGNTOP полностью нарушена. Код в этой статье может сработать, если вместо него будет возвращен WVR_VALIDRECTS.

    • Если у вас нет собственного обработчика WM_NCCALCSIZE, вы получите довольно бесполезное поведение, которого лучше всего избегать:

      • Если вы уменьшаете клиентскую область, ничего не происходит (ваше приложение вообще не получает WM_PAINT)! Если вы используете верхнюю или левую границу, содержимое вашей клиентской области будет перемещаться вместе с верхним левым краем клиентской области. Чтобы получить любое живое изменение размеров при уменьшении окна, вам нужно вручную нарисовать из сообщения wndproc как WM_SIZE, или вызвать InvalidateWindow() чтобы вызвать более позднюю WM_PAINT.

      • Если вы увеличите клиентскую зону

        • Если вы перетащите нижнюю или правую границу окна, Microsoft заполняет новые пиксели "фоновым цветом" (см. Ниже).

        • Если вы перетащите верхнюю или левую границу окна, Microsoft скопирует существующие пиксели в верхний левый угол развернутого окна и оставит старую ненужную копию старых пикселей во вновь открытом пространстве.

Итак, как вы можете видеть из этой грязной истории, есть две полезные комбинации:

  • 2a1. WNDCLASS.style с CS_HREDRAW|CS_VREDRAW дает вам поведение на рисунках 1c3-1, 1c3-2, 1c4-1 и 1c4-2 ЧАСТИ 1, которое не идеально, но, по крайней мере, содержимое вашей клиентской области не будет двигаться в одном направлении, тогда рвануть в обратном направлении

  • 2a2. WNDCLASS.style без CS_HREDRAW|CS_VREDRAW плюс обработчик WM_NCCALCSIZE возвращающий WVR_VALIDRECTS (когда wParam равен 1), который ничего не BitBlts, плюс отключение "фона" (см. Ниже) может полностью отключить растление Windows вашей клиентской области.

Существует, по-видимому, другой способ достижения эффекта комбинации 2a2. Вместо реализации вашего собственного WM_NCCALCSIZE вы можете перехватить WM_WINDOWPOSCHANGING (сначала передав его в DefWindowProc) и установить WINDOWPOS.flags |= SWP_NOCOPYBITS, который отключает BitBlt внутри внутреннего вызова SetWindowPos() который Windows делает во время изменения размера окна. Я сам не пробовал этот трюк, но многие пользователи SO сообщили, что он работает.

В нескольких пунктах выше мы упомянули "цвет фона". Этот цвет определяется полем WNDCLASS.hbrBackground которое вы передали в RegisterClassEx. Это поле содержит объект HBRUSH. Большинство людей устанавливает его, используя следующий стандартный код:

wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

COLOR_WINDOW+1 дает вам белый цвет фона. См. MSDN dox для WNDCLASS для объяснения +1 и обратите внимание, что существует много неверной информации о +1 на форумах StackOverflow и MS.

Вы можете выбрать свой собственный цвет, как это:

wndclass.hbrBackground = CreateSolidBrush(RGB(255,200,122));

Вы также можете отключить фоновое заполнение, используя:

wndclass.hbrBackground = NULL;

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

2b. Задачи изменения размера из DWM Composition Fill

В определенный момент во время разработки Aero Microsoft добавила еще одну проблему дрожания живого изменения размера в дополнение к проблеме всех версий Windows, описанной выше.

Читая более ранние сообщения StackOverflow, на самом деле трудно сказать, когда эта проблема появилась, но мы можем сказать, что:

  • эта проблема определенно возникает в Windows 10
  • эта проблема почти наверняка возникает в Windows 8
  • эта проблема могла также возникать в Windows Vista с включенным Aero (многие сообщения с проблемами изменения размера в Vista не говорят, если у них включен Aero или нет).
  • эта проблема, вероятно, не возникала в Windows 7, даже при включенном Aero.

Проблема связана с серьезным изменением архитектуры, представленной Microsoft в Windows Vista, под названием DWM Desktop Composition. Приложения больше не рисуют напрямую в графический фрейм-буфер. Вместо этого все приложения фактически рисуют в закадровом кадровом буфере, который затем объединяется с выходом других приложений новым, злым процессом Windows Window Manager (DWM).

Таким образом, поскольку для отображения ваших пикселей используется еще один процесс, существует еще одна возможность испортить ваши пиксели.

И Microsoft никогда не упустит такую возможность.

Вот что, по-видимому, происходит с составом DWM:

  • Пользователь щелкает мышью по границе окна и начинает перетаскивать мышь

  • Каждый раз, когда пользователь перетаскивает мышь, это запускает последовательность событий wndproc в вашем приложении, которую мы описали в разделе 2a выше.

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

  • Аналогично разделу 2а выше, таймер начинает тикать после того, как WM_NCCALCSIZE возвращается и удовлетворяется, когда ваше приложение рисует и вызывает SwapBuffers().

  • Если вы обновить клиентскую область к сроку, то DWM оставит ваш район клиента красиво недосаждавшим. Существует определенная вероятность того, что ваша клиентская область все еще может быть осквернена проблемой в разделе 2a, поэтому обязательно прочитайте также раздел 2a.

  • Если вы не обновите свою клиентскую область к крайнему сроку, тогда Microsoft сделает что-то действительно отвратительное и невероятно плохое (разве Microsoft не усвоила их урок?):

    • Предположим, это ваша клиентская область до изменения размера, где A, B, C и D представляют цвета пикселей в середине верхней, левой, правой и нижней кромок вашей клиентской области:
    --------------AAA-----------------
    |                                |
    B                                C
    B                                C
    B                                C
    |                                |
    --------------DDD-----------------
    
    • Предположим, вы используете мышь для увеличения клиентской области в обоих измерениях. Genius Windows DWM (или, возможно, Nvidia: подробнее об этом позже) всегда будет копировать пиксели вашей клиентской области в верхний левый угол новой клиентской области (независимо от того, какую границу окна вы перетаскиваете), а затем делать самые абсурдные вещи. мыслимый для остальной части клиентской зоны. Windows возьмет любые пиксельные значения, которые были вдоль нижнего края вашей клиентской области, растянет их до новой ширины клиентской области (ужасная идея, которую мы исследовали в Разделе 1c2 ЧАСТИ 1), и скопирую эти пиксели, чтобы заполнить все новые открытое пространство внизу (смотрите, что происходит с D). Затем Windows возьмет любые пиксельные значения, которые были вдоль правого края вашей клиентской области, растянет их до новой высоты клиентской области и скопирует их, чтобы заполнить новые открытое пространство в правом верхнем углу:
    --------------AAA-----------------------------------------------
    |                                |                             |
    B                                C                             |
    B                                C                             |
    B                                CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
    |                                |CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
    --------------DDD-----------------CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
    |                             DDDDDDDDD                        |
    |                             DDDDDDDDD                        |
    |                             DDDDDDDDD                        |
    |                             DDDDDDDDD                        |
    |                             DDDDDDDDD                        |
    ------------------------------DDDDDDDDD-------------------------
    
    • Я даже не представляю, что они курят. Такое поведение дает наихудший возможный результат во многих случаях. Во-первых, при перетаскивании границ левого и верхнего окон практически гарантируется генерация ужасающего движения вперед-назад, которое мы показали на рис. 1c3-3 и рис. 1c4-3 ЧАСТИ 1, поскольку скопированный прямоугольник всегда находится в верхнем левом углу. независимо от того, какую границу окна вы перетаскиваете. Во-вторых, еще более загадочная вещь, которая происходит с реплицируемыми краевыми пикселями, будет приводить к появлению некрасивых полос, если у вас есть какие-либо пиксели, установленные там, кроме фона. Обратите внимание, что созданные полосы C и D даже не совпадают с исходными C и D из скопированных старых пикселей. Я могу понять, почему они воспроизводят края, надеясь найти фоновые пиксели там, чтобы "автоматизировать" процесс обнаружения цвета фона, но кажется, что вероятность этого на самом деле сильно перевешивается фактором взлома и вероятностью отказа. Было бы лучше, если бы DWM использовал приложение, выбранное "фоновым цветом" (в WNDCLASS.hbrBackground), но я подозреваю, что DWM может не иметь доступа к этой информации, так как DWM находится в другом процессе, отсюда и взлом. Вздох.

Но мы даже не дошли до худшей части:

  • Каков на самом деле крайний срок, который DWM дает вам для того, чтобы нарисовать свою собственную клиентскую область, прежде чем DWM испортит ее с помощью этого неуклюжего предположения? По-видимому (из моих экспериментов) крайний срок составляет порядка 10-15 миллисекунд ! Учитывая, что 15 миллисекунд близки к 1/60, я бы предположил, что крайний срок фактически является концом текущего кадра. И подавляющее большинство приложений не могут уложиться в этот срок в большинстве случаев.

Вот почему, если вы запустите Windows Explorer в Windows 10 и перетащите левую границу, вы, скорее всего, увидите полосу прокрутки на правом джиттере/мерцании/скачкообразном скачке, как будто Windows была написана четвероклассником.

Я не могу поверить, что Microsoft выпустила подобный код и считает, что он "готов". Также возможно, что ответственный код находится в графическом драйвере (например, Nvidia, Intel,...), но некоторые сообщения StackOverflow заставили меня поверить, что это поведение между устройствами.

Вы можете сделать очень мало, чтобы этот слой некомпетентности не генерировал отвратительный джиттер/мерцание/прыжок при изменении размера с использованием левой или верхней границы окна. Это потому, что грубое, не согласное изменение вашей клиентской области происходит в другом процессе.

Я действительно надеюсь, что какой-то пользователь StackOverflow предложит в Windows 10 какой-нибудь волшебный параметр DWM или флаг, который мы можем сделать, чтобы либо продлить срок, либо полностью отключить ужасное поведение.

Но в то же время я придумал один хак, который несколько уменьшает частоту отвратительных возвратно-поступательных артефактов при изменении размера окна.

Хак, вдохновленный комментарием на fooobar.com/questions/8832506/..., состоит в том, чтобы приложить максимум усилий для синхронизации процесса приложения с вертикальным возвратом, который управляет активностью DWM. На самом деле сделать эту работу в Windows не тривиально. Код для этого хака должен быть самым последним в вашем обработчике WM_NCCALCSIZE:

LARGE_INTEGER freq, now0, now1, now2;
QueryPerformanceFrequency(&freq); // hz

// this absurd code makes Sleep() more accurate
// - without it, Sleep() is not even +-10ms accurate
// - with it, Sleep is around +-1.5 ms accurate
TIMECAPS tc;
MMRESULT mmerr;
MMC(timeGetDevCaps(&tc, sizeof(tc)), {});
int ms_granularity = tc.wPeriodMin;
timeBeginPeriod(ms_granularity); // begin accurate Sleep() !

QueryPerformanceCounter(&now0);

// ask DWM where the vertical blank falls
DWM_TIMING_INFO dti;
memset(&dti, 0, sizeof(dti));
dti.cbSize = sizeof(dti);
HRESULT hrerr;
HRC(DwmGetCompositionTimingInfo(NULL, &dti), {});

QueryPerformanceCounter(&now1);

// - DWM told us about SOME vertical blank
//   - past or future, possibly many frames away
// - convert that into the NEXT vertical blank

__int64 period = (__int64)dti.qpcRefreshPeriod;

__int64 dt = (__int64)dti.qpcVBlank - (__int64)now1.QuadPart;

__int64 w, m;

if (dt >= 0)
{
    w = dt / period;
}
else // dt < 0
{
    // reach back to previous period
    // - so m represents consistent position within phase
    w = -1 + dt / period;
}

// uncomment this to see worst-case behavior
// dt += (sint_64_t)(0.5 * period);

m = dt - (period * w);

assert(m >= 0);
assert(m < period);

double m_ms = 1000.0 * m / (double)freq.QuadPart;

Sleep((int)round(m_ms));

timeEndPeriod(ms_granularity);

Вы можете убедить себя в том, что этот хак работает, раскомментировав строку, демонстрирующую поведение "наихудшего случая", попытавшись запланировать рисование прямо в середине кадра, а не при вертикальной синхронизации, и заметив, сколько у вас еще артефактов. Вы также можете попытаться изменить смещение в этой строке медленно, и вы увидите, что артефакты внезапно исчезают (но не полностью) примерно через 90% периода и возвращаются снова через 5-10% периода.

Поскольку Windows не является операционной системой реального времени, ваше приложение может быть прервано где-либо в этом коде, что приведет к неточности в сопряжении now1 и dti.qpcVBlank. Вытеснение в этом небольшом разделе кода встречается редко, но возможно. Если вы хотите, вы можете сравнить now0 и now0 и now1 цикл, если граница не достаточно тесная. Кроме того, приоритетное прерывание может нарушить синхронизацию Sleep() или кода до или после Sleep(). Вы ничего не можете с этим поделать, но оказывается, что ошибки синхронизации в этой части кода завалены неуверенным поведением DWM; Вы все еще будете получать некоторые артефакты изменения размера окна, даже если ваше время идеально. Это просто эвристика.

Есть второй взлом, и он невероятно креативный: как объяснено в посте StackOverflow Не удается избавиться от дрожания при перетаскивании левой границы окна, вы можете создать два основных окна в своем приложении, и каждый раз Windows сделает SetWindowPos, вы это поймете и вместо этого скроете одно окно и покажете другое! Я еще не пробовал это, но OP сообщает, что он обходит безумную пиксельную копию пикселя DWM, описанную выше.

Существует третий способ взлома, который может работать в зависимости от вашего приложения (особенно в сочетании с указанным выше взломом синхронизации). Во время WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE изменения размера (которое вы можете обнаружить, перехватывая WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE), вы можете изменить свой код рисования, чтобы изначально нарисовать что-то намного более простое, что гораздо более вероятно выполнить в срок, установленный проблемами 2a и 2b, и вызвать SwapBuffers() для потребуйте свой приз: этого будет достаточно, чтобы не допустить, чтобы Windows выполнила плохой блиц/заливку, описанную в разделах 2a и 2b. Затем, сразу после частичной отрисовки, сделайте еще одну отрисовку, которая полностью обновляет содержимое окна, и снова вызовите SwapBuffers(). Это может все еще выглядеть несколько странно, поскольку пользователь увидит обновление вашего окна в двух частях, но, скорее всего, оно будет выглядеть намного лучше, чем отвратительный артефакт движения назад и вперед от Windows.

Еще один интересный момент: некоторые приложения в Windows 10, в том числе консоль (start cmd.exe), не имеют артефактов DWM Composition даже при перетаскивании левой границы. Так что есть какой-то способ обойти проблему. Давай найдем это!

2с. Как диагностировать вашу проблему

Когда вы пытаетесь решить вашу конкретную проблему изменения размера, вы можете задаться вопросом, какие из перекрывающихся эффектов из Раздела 2a и Раздела 2b вы видите.

Один из способов разделить их - немного отладить в Windows 7 (с отключенным Aero, просто для безопасности).

Другой способ быстро определить, видите ли вы проблему в Разделе 2b, - это изменить приложение для отображения тестового шаблона, описанного в Разделе 2b, как в этом примере (обратите внимание на цветные линии толщиной в 1 пиксель на каждом из четырех краев):

test pattern

Затем возьмите любую границу окна и начните быстро изменять ее размер. Если вы видите прерывистые гигантские цветные полосы (синие или зеленые полосы в случае этого тестового шаблона, поскольку на нижнем краю есть синий, а на правом - зеленый), то вы знаете, что видите проблему в Разделе 2b.

Вы можете проверить, видите ли вы проблему в Разделе 2а, установив для WNDCLASS.hbrBackground отдельный цвет фона, например красный. При изменении размера окна, новые экспонированные части будут отображаться с этим цветом. Но прочитайте Раздел 2a, чтобы убедиться, что ваши обработчики сообщений не вызывают Windows для BitBlt всей клиентской области, что заставит Windows не рисовать фоновый цвет.

Помните, что проблемы в Разделе 2a и 2b обнаруживаются только в том случае, если ваше приложение не может рисовать к определенному крайнему сроку, а каждая проблема имеет свой крайний срок.

Таким образом, без изменений ваше приложение может показывать только проблему Раздела 2b, но если вы измените свое приложение так, чтобы оно рисовалось медленнее (например, вставьте Sleep() в WM_PAINT перед SwapBuffers()), вы можете пропустить крайний срок для Раздела 2a и Раздел 2b и начать видеть обе проблемы одновременно.

Это также может произойти, когда вы переключаете свое приложение с более медленной сборки DEBUG сборку RELEASE, что может очень затруднить погоню за этими проблемами изменения размера. Знание того, что происходит под капотом, может помочь вам справиться с запутанными результатами.

Ответ 2

ЧАСТЬ 1: Что делает изменение размера хорошим или плохим?

В вопросах StackOverflow так много неопределенности и неясности относительно плавного изменения размера, что нам нужно создать общий словарь, чтобы помочь людям сделать свои ответы более ясными.

Это то, что мы будем делать в этом разделе.

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

Ниже мы будем ссылаться на окно

  • "не-клиентская область": часть окна, которой управляет Windows, включая строку заголовка вверху и границы окна по всем краям, и

  • "клиентская зона": основная часть окна, за которую вы отвечаете

Предположим, у вас есть приложение с:

  • кнопка или метка L, которая должна оставаться вровень-влево
  • кнопка или метка R, которая должна оставаться вровень-вправо

независимо от того, как окно изменяется.

Ваше приложение может рисовать сам L/R (например, используя GDI/OpenGL/DirectX внутри одного окна) или L/R может быть неким элементом управления Microsoft (который будет иметь свой собственный HWND, отдельный от вашего главного окна HWND); не имеет значения

Вот упрощенное представление клиентской области окна вашего приложения. Как вы можете видеть, у нас есть LLL шириной в три столбца в дальнем левом углу клиентской области и RRR шириной в три столбца в крайнем правом углу клиентской области, с различным другим содержимым клиентской области, представленным знаком "-" в между (пожалуйста, игнорируйте серый фон, который StackOverflow настаивает на добавлении; L и R находятся на крайнем левом и правом краях вашей клиентской области):

LLL-----------RRR

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

1a. Простой случай: рисование вовремя

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

Когда вы перетаскиваете границу приложения, пользователь видит на экране следующее (каждая строка этих цифр представляет один момент времени):

Перетаскивание правой границы вправо (увеличение ширины):

(Figure 1a-1)
LLL-----------RRR     (initially, when you click the mouse)
LLL------------RRR    (as you drag the mouse)
LLL-------------RRR   (as you drag the mouse)
LLL--------------RRR  (when you release the mouse)

Перетаскивание правой границы влево (ширина сжатия):

(Figure 1a-2)
LLL-----------RRR
LLL----------RRR
LLL---------RRR
LLL--------RRR

Перетаскивание левой границы влево (увеличение ширины):

(Figure 1a-3)
   LLL-----------RRR
  LLL------------RRR
 LLL-------------RRR
LLL--------------RRR

Перетаскивание левой границы вправо (ширина сжатия):

(Figure 1a-4)
LLL-----------RRR
 LLL----------RRR
  LLL---------RRR
   LLL--------RRR

Все это выглядит хорошо и гладко:

  • При настройке правой границы, R, кажется, движется с постоянной скоростью в одном направлении, а L остается неподвижным, как и должно.
  • При настройке левой границы, L, кажется, движется с постоянной скоростью в одном направлении, а R остается неподвижным, как и должно.

Все идет нормально.

1б. Трудный случай: рисование отстает

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

  • что должно отображаться на экране в этот период, и
  • кто решает что показать?

Например, при перетаскивании правой границы вправо (увеличение ширины):

(Figure 1b-1)
LLL-----------RRR
??????????????????    (what should show here?)
???????????????????   (what should show here?)
LLL--------------RRR  (app catches up)

В качестве другого примера, при перетаскивании левой границы влево (ширина сжатия):

(Figure 1b-2)
LLL-----------RRR
 ????????????????  (what should show here?)
  ???????????????  (what should show here?)
   LLL--------RRR  (app catches up)

Это ключевые вопросы, которые определяют, выглядит ли движение гладким или нет, и они являются ключевыми вопросами, вокруг которых вращается весь этот вопрос StackOverflow.

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

1c. Временные решения в ожидании приложения для рисования

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

1c1. Ничего не делать

Экран может оставаться таким же, как и до тех пор, пока приложение не догонит (ни пиксели клиента, ни даже граница окна в области, не являющейся клиентом, не изменятся):

Пример при перетаскивании правой границы вправо (увеличение ширины):

(Figure 1c1-1)
LLL-----------RRR
LLL-----------RRR
LLL-----------RRR
LLL--------------RRR  (app catches up)

Пример при перетаскивании левой границы влево (ширина сжатия):

(Figure 1c1-2)
LLL-----------RRR
LLL-----------RRR
LLL-----------RRR
   LLL--------RRR  (app catches up)

Очевидный недостаток этого метода заключается в том, что в течение рассматриваемого периода приложение, по-видимому, зависло и, по-видимому, не реагирует на движения мыши, поскольку ни R, ни '-', ни L, ни граница окна не являются перемещение.

Microsoft часто считают, что Windows не отвечает на запросы (иногда это их ошибка, а иногда и ошибка разработчика приложения), поэтому с тех пор, как Microsoft представила live-resize (Windows XP?), Microsoft никогда не использовала метод "ничего не делать". само собой.

Метод "ничего не делать" раздражает пользователя и выглядит непрофессионально, но оказывается (очень неочевидно), что он не всегда является худшим выбором. Читать дальше...

1c2. Масштаб содержимого

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

Этот метод, как правило, хуже, чем любой другой метод, потому что он приведет к размытому изображению вашего исходного контента, которое, вероятно, будет непропорциональным. Так что никто не должен делать это в любом случае. За исключением, как мы увидим в ЧАСТИ 2, иногда Microsoft делает.

1С3. При увеличении укажите цвет фона

Другой способ, который может сработать при увеличении окна, заключается в следующем: Windows всегда может заставить границу окна мгновенно следовать вашим движениям мыши, и Windows может заполнить новые пиксели увеличенной клиентской области некоторым временным цветом фона B:

Например, при перетаскивании правой границы вправо (увеличение ширины):

(Figure 1c3-1)
LLL-----------RRR
LLL-----------RRRB
LLL-----------RRRBB
LLL--------------RRR  (app catches up)

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

Еще одна приятная особенность заключается в том, что во время перетаскивания L остается на месте, как и должно быть.

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

Огромный и важный вопрос: какого цвета должен быть новый цвет фона B? Если B окажется черным, а ваше приложение будет иметь в основном белый фон, или наоборот, это будет намного страшнее, чем если B соответствует вашему существующему цвету фона контента. Как мы увидим в ЧАСТИ 2, Windows развернула несколько различных стратегий, чтобы улучшить выбор B.

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

Логично было бы добавить новый цвет фона в левой части окна:

(Figure 1c3-2)
   LLL-----------RRR
  BLLL-----------RRR
 BBLLL-----------RRR
LLL--------------RRR  (app catches up)

Это было бы логично, потому что R останется на месте, как и должно быть. У L будет та же странность, которую мы описали вместе с рисунком 1c3-1 выше (L будет стоять неподвижно, а затем в последний момент внезапно дернет 3 столбца влево), но по крайней мере L будет двигаться только в правильном направлении.

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

Вместо этого Windows иногда заполняет фоновые пиксели B справа, даже если вы перетаскиваете левую границу окна:

(Figure 1c3-3)
   LLL-----------RRR
  LLL-----------RRRB
 LLL-----------RRRBB
LLL--------------RRR  (app catches up)

Да, это безумие.

Посмотрите, как это выглядит для пользователя:

  • L, кажется, движется очень плавно с постоянной скоростью в одном направлении, так что это на самом деле хорошо, но

  • Просто посмотрите, что делает R:

      RRR
     RRR
    RRR  
      RRR  (app catches up)
    
    • R первым перемещается влево на две колонки, которые он не должен делать: R должен оставаться утопленного право в любое время
    • Затем R снова возвращается вправо. Святое дерьмо!

Это выглядит ужасно, ужасно, ужасно, отвратительно... даже нет слов, чтобы описать, как плохо это выглядит.

Человеческий глаз чрезвычайно чувствителен к движению, даже к движению, которое происходит всего за несколько кадров. Наш глаз мгновенно замечает это странное движение вперед и назад, и мы сразу же узнаем, что что-то серьезно не так.

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

В действительности оба случая (рис. 1c3-2 и рис. 1c3-3) делают что-то странное. На рисунке 1c3-2 мы временно добавляем фоновые пиксели B, которые там не принадлежат. Но это странное поведение гораздо менее заметно, чем движение вперед и назад на рисунке 1c3-3.

Это движение вперед-назад - это дрожание/мерцание/прыжки, о которых так много вопросов StackOverflow.

Поэтому любое решение проблемы плавного изменения размера должно:

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

  • в идеале также избегайте необходимости добавлять фоновые пиксели B, если это возможно

1c4. При сжатии отрежьте несколько пикселей

Раздел 1c3 посвящен расширению окна. Если мы посмотрим на сжатие окна, то увидим, что существует точно такой же набор случаев.

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

Например, при перетаскивании правой границы влево (уменьшение ширины):

(Figure 1c4-1)
LLL-----------RRR
LLL-----------RR
LLL-----------R
LLL--------RRR     (app catches up)

С помощью этой техники L остается на своем месте, как и должно быть, но странная вещь происходит справа: R, который должен оставаться заподлицо независимо от размера окна, по-видимому, получает правый край, постепенно срезанный правым краем клиентской области, пока R не исчезнет, а затем внезапно R снова появится в правильном положении, когда приложение догонит. Это очень странно, но имейте в виду, что ни в одной точке R не движется вправо. R левый край, кажется, остается неподвижным до последнего момента, когда все R перескочат назад на 3 столбца влево. Таким образом, как мы видели на рисунке 1c3-1, R движется только в правильном направлении.

Теперь рассмотрим, что происходит, когда мы перетаскиваем левую границу вправо (уменьшение ширины).

Логично было бы стричь пиксели слева от клиентской области:

(Figure 1c4-2)
LLL-----------RRR
 LL-----------RRR
  L-----------RRR
   LLL--------RRR  (app catches up)

Это будет иметь те же странные свойства, что и на рисунке 1c4-1, только с измененными ролями слева и справа. Казалось бы, что L постепенно сбривается от левого края L, но правый край L будет оставаться неподвижным до тех пор, пока в последний момент L не появится, чтобы прыгнуть вправо. Таким образом, L движется только в правильном направлении, хотя и резко.

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

Вместо этого Windows иногда обрезает пиксели справа, даже если вы перетаскиваете левую границу окна:

(Figure 1c4-3)
LLL-----------RRR
 LLL-----------RR
  LLL-----------R
   LLL--------RRR  (app catches up)

Посмотрите, как это выглядит для пользователя:

  • L, кажется, движется очень плавно с постоянной скоростью в одном направлении, так что это на самом деле хорошо, но

  • Просто посмотрите, что делает R:

    RRR
     RR
      R
    RRR  (app catches up)
    
    • R сначала скользит вправо на два столбца. R левый край, кажется, движется вправо вместе с остальной частью R.
    • Затем R снова возвращается влево.

Как вы теперь должны знать после прочтения раздела 1c3, это движение назад и вперед выглядит абсолютно ужасно и намного хуже, чем, по общему признанию, странное поведение на рис. 1c4-1 и рис. 1c4-2.

1c5. Подождите немного, затем попробуйте один из вышеперечисленных

Пока что мы представили отдельные идеи о том, что делать, когда пользователь начал перетаскивать границы окна, но приложение еще не перерисовалось.

Эти методы могут быть объединены.

На мгновение попробуйте подумать об этой проблеме с точки зрения Microsoft. В тот момент, когда пользователь начинает перетаскивать мышь, чтобы изменить размер окна, у Microsoft нет возможности заранее узнать, сколько времени займет рисование вашего приложения. Поэтому Microsoft должна найти баланс:

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

  • но если Microsoft будет ждать, пока вы будете рисовать слишком долго, ваше приложение (и, соответственно, Windows) будет выглядеть зависшим и не отвечающим, как мы объяснили в Разделе 1c1. Это заставляет Microsoft терять лицо, даже если это ваша вина.

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

Звучит ли это ужасно и смешно для вас? Угадай, что? Это то, что делает Windows, по крайней мере, двумя разными способами одновременно с двумя разными сроками. ЧАСТЬ 2 погрузится в эти дела...

Ответ 3

ЧАСТЬ 3: Галерея Скорби: аннотированный список ссылок по теме

Вы можете найти идеи, которые я пропустил, просмотрев исходный материал:

2014 с обновлениями 2017 года: не удается избавиться от дрожания при перетаскивании левой границы окна: возможно, это самый актуальный вопрос, но в нем по-прежнему отсутствует контекст; предлагает творческий, но довольно сумасшедший хак с двумя окнами и попеременным их отображением во время изменения размера в реальном времени! Также единственный вопрос, который я нашел с ответом, в котором упоминается состояние гонки в DWM и частичное исправление синхронизации с помощью DwmGetCompositionTimingInfo().

2014 Почему каждый раз при изменении размера окна WPF возникает черная задержка? : да WPF тоже это делает. Нет полезных ответов

2009 Как исправить изменение размера формы WPF - отстающие элементы управления и черный фон? : элементы управления отстают и черный фон? "мульти-пример HWND. Упоминает WM_ERASEBKGND и трюки с фоновой кистью, но не современный ответ.

2018 Есть ли способ уменьшить или предотвратить мерцание формы при использовании WPF? : да, еще не исправлено по состоянию на 2018 год.

2018 Уменьшение мерцания при использовании SetWindowPos для изменения левого края окна: вопрос без ответа, который получил много устаревших рекомендаций, таких как WM_NCCALCSIZE

2012 OpenGL мерцает/поврежден с изменением размера окна и активным DWM: хорошее изложение проблемы, ответчики совершенно не поняли контекст и дали неприменимые ответы.

2012 Как избежать временных изменений в изменении размера графического интерфейса? : упоминает хитрость перехвата WM_WINDOWPOSCHANGING и установки WINDOWPOS.flags |= SWP_NOCOPYBITS.

Отчет об ошибке Unity 2016: "Изменение размера окна очень нестабильно и заикается (граница не плавно следует за мышью)" типичный отчет об ошибках, обнаруженный в сотнях приложений, который частично связан с проблемой в этом отчете об ошибках, а частично из-за того, что некоторые приложения имеют медленное рисование. Единственный документ, который я когда-либо нашел, который на самом деле говорит, что Windows 10 DWM зажимает и расширяет внешний пиксель старого окна, что я могу подтвердить.

2014 Мерцание в окне при изменении размера с левой стороны с ответом до Windows-8, включая CS_HREDRAW/CS_VREDRAW и WM_NCCALCSIZE.

Окно изменения размера 2013 вызывает размытие около правой границы с помощью старой версии Win-7, которая отключает Aero.

2018 Расширение (изменение размера) окна влево без мерцания пример многооконного (multi-HWND) случая, реального ответа нет.

WinAPI 2013 C++: изменение размера окна перепрограммирования: слишком неоднозначно задан вопрос, идет ли речь о мерцании клиентской области (как этот вопрос) или мерцании вне клиентской области.

Ошибка GLFW 2018 года "Изменение размеров окон в Windows 10 демонстрирует скачкообразное поведение", одна из МНОГИХ таких ошибок, которые никогда не объясняют контекст, как многие сообщения StackOverflow

2008 "Изменение размера основного кадра без мерцания" CodeProject, который фактически выполняет StretchBlt, но не работает в мире Windows 8+, где приложение не имеет контроля, когда на экране отображаются неправильные пиксели.

2014 Плавное изменение размеров окна в Windows (с использованием Direct2D 1.1)? : Четко заявленная, но оставшаяся без ответа проблема с копией DWM в Windows 8+

2010 Как заставить Windows НЕ перерисовывать что-либо в моем диалоге, когда пользователь изменяет размер моего диалога? Исправлено: WM_NCCALCSIZE, чтобы отключить битовый бит, который больше не работает в Windows 8+, поскольку DWM повреждает экран, прежде чем приложение сможет его отобразить.

2014 Мерцание при перемещении/изменении размера окна: сводка предыдущих исправлений, которые не работают в Windows 8+.

CodeProject 2007 года эпохи WinXP "уменьшающий мерцание", рекомендующий WM_ERASEBKGND + SWP_NOCOPYBITS

Ранний отчет Google Bug 2008 года о новых проблемах Vista DWM

Ответ 4

См. Сообщение в блоге. Тест плавного изменения размера, в котором есть некоторый анализ и указатели на решения. По сути, существует выигрышная стратегия, которая заключается в рендеринге на поверхность перенаправления при изменении размера в реальном времени и использовании swapchain в другое время. Я не уверен, решит ли это вашу конкретную проблему, так как вам нужен достаточно низкоуровневый контроль над тем, как работает презентация, чтобы реализовать это. Этот подход также предполагает, что вы рисуете с использованием Direct2D (как я сейчас делаю) или DirectX.