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

ПРОБЛЕМА: Когда я беру границу изменения размера моего приложения 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, которые мы опишем, применяются к вашим дочерним окнам вместе с родительское окно), но во время изменения размера окна у вас возникает дополнительная проблема, которая выходит за рамки этого вопроса: вам нужно заставить все ваши дочерние окна двигаться атомарно и синхронно с родительским окном. Для этой задачи вам, вероятно, понадобится BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos, и вы можете узнать о них здесь и здесь .

  • В этом вопросе предполагается, что если ваше приложение обращается к окну с использованием GDI, DirectX или OpenGL, то вы уже реализовали обработчик 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.

Ответы [ 4 ]

0 голосов
/ 05 июля 2019

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

0 голосов
/ 28 октября 2018

ЧАСТЬ 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 остается неподвижным, как и должно.

Пока все хорошо.

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

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

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

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

(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 делает.

1c3.При увеличении залейте фоновым цветом

Другой способ, который может работать при увеличении окна, заключается в следующем: 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, затем снова фиксирует вправо .Святое дерьмо!

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

Человеческий глаз чрезвычайно чувствителен к движению, даже к движению, которое происходит всего за несколько периодов времени.Наш глаз мгновенно замечает это странное движение вперед и назад 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 погрузится в эти дела ...

0 голосов
/ 28 октября 2018

ЧАСТЬ 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.

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

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

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

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

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

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

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

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

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

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

2007 эпоха WinXP «уменьшение мерцания» CodeProject рекомендация WM_ERASEBKGND + SWP_NOCOPYBITS

2008 раннее Google Bug отчет о новых проблемах Vista DWM

0 голосов
/ 26 октября 2018

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

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

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

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

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

2a.Проблемы с изменением размера из 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() вызов сначала изменяет размер не клиентской области (например, строки заголовка и границы окна), а затем переключает свое внимание на клиентскую область (основную часть окна, за которое вы отвечаете).

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

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

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

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

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

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

    • Происходит нечто удивительно разумное.Вы получаете логическое поведение, показанное на рисунках 1c3-1, 1c3-2, 1c4-1 и 1c4-2 для PART 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 пикселей, а затем видит реальные пиксели, нарисованные вашим кодом, ваш контент сначала перемещается в одном направлении, а затем возвращается назад в другом направлении.Как мы объяснили в ЧАСТЬ 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_VALIDRECTS.

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

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

      • Если увеличить клиентскую область

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

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

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

  • 2а1. WNDCLASS.style с CS_HREDRAW|CS_VREDRAW дает вам поведение на рисунках 1c3-1, 1c3-2, 1c4-1 и 1c4-2 из PART 1 , которое не идеально, но, по крайней мере, в вашей области клиента не будет двигаться в одном направлении, а затем будет двигаться назад в другом направлении

  • 2а2. 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 (помните, что это отдельный процесс, который асинхронно запускается в вашем приложении) запускает собственный таймер крайнего срока.

  • Как и в разделе 2a выше, таймер начинает тикать после того, как 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 PART 1 , и воспроизведу эти пиксели заполнить все вновь открытое пространство внизу (см., что происходит с D.) Затем Windows возьмет любые пиксельные значения, которые были вдоль правого края вашей клиентской области, растянет их до новой высоты клиентской области и выполнит репликацию. их, чтобы заполнить недавно открытое пространство в правом верхнем углу:
    --------------AAA-----------------------------------------------
    |                                |                             |
    B                                C                             |
    B                                C                             |
    B                                CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
    |                                |CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
    --------------DDD-----------------CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
    |                             DDDDDDDDD                        |
    |                             DDDDDDDDD                        |
    |                             DDDDDDDDD                        |
    |                             DDDDDDDDD                        |
    |                             DDDDDDDDD                        |
    ------------------------------DDDDDDDDD-------------------------
    
    • Я даже не представляю, что они курили. Такое поведение дает наихудший возможный результат во многих случаях. Во-первых, при перетаскивании границ левого и верхнего окон практически гарантируется генерация ужасающего движения вперед-назад, показанного на рис. 1c3-3 и рис. 1c4-3 из PART 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 или флаг, который мы можем сделать, чтобы либо продлить срок, либо полностью отключить ужасное поведение.

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

Хак, вдохновленный комментарием в https://stackoverflow.com/a/25364123/1046167, состоит в том, чтобы приложить максимум усилий для синхронизации процесса приложения с вертикальным возвратом, который управляет активностью 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 и now1 и повторить цикл, если граница недостаточно узкая.Кроме того, приоритетное прерывание может нарушить синхронизацию Sleep() или кода до или после Sleep().Вы ничего не можете с этим поделать, но оказывается, что ошибки синхронизации в этой части кода завалены неуверенным поведением DWM;Вы все еще будете получать некоторые артефакты изменения размера окна, даже если ваше время идеально.Это просто эвристика.

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

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

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

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

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

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

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

test pattern

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

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

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

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

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

...