Ответ 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]
возвращаемые значения, которые полностью запрещают WindowsBitBlting
любой из пикселей старой клиентской области в новую клиентскую область, или заставляют WindowsBitBlt
один пиксель из одного и того же места, что, по сути, одно и то же, поскольку никакие пиксели на экране не будут изменены. Просто установите оба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 пиксель на каждом из четырех краев):
Затем возьмите любую границу окна и начните быстро изменять ее размер. Если вы видите прерывистые гигантские цветные полосы (синие или зеленые полосы в случае этого тестового шаблона, поскольку на нижнем краю есть синий, а на правом - зеленый), то вы знаете, что видите проблему в Разделе 2b.
Вы можете проверить, видите ли вы проблему в Разделе 2а, установив для WNDCLASS.hbrBackground
отдельный цвет фона, например красный. При изменении размера окна, новые экспонированные части будут отображаться с этим цветом. Но прочитайте Раздел 2a, чтобы убедиться, что ваши обработчики сообщений не вызывают Windows для BitBlt
всей клиентской области, что заставит Windows не рисовать фоновый цвет.
Помните, что проблемы в Разделе 2a и 2b обнаруживаются только в том случае, если ваше приложение не может рисовать к определенному крайнему сроку, а каждая проблема имеет свой крайний срок.
Таким образом, без изменений ваше приложение может показывать только проблему Раздела 2b, но если вы измените свое приложение так, чтобы оно рисовалось медленнее (например, вставьте Sleep()
в WM_PAINT
перед SwapBuffers()
), вы можете пропустить крайний срок для Раздела 2a и Раздел 2b и начать видеть обе проблемы одновременно.
Это также может произойти, когда вы переключаете свое приложение с более медленной сборки DEBUG
сборку RELEASE
, что может очень затруднить погоню за этими проблемами изменения размера. Знание того, что происходит под капотом, может помочь вам справиться с запутанными результатами.