DWM в Win7/8 + GDI

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

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

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

Итак, что вы увидите, если у вас есть система, подобная моей (настольный Win7 с Geforce, включенным Aero), это обычная система лассо, не более призрачная, чем у монитора. В других системах (например, Surface Pro, для определения полностью известной системы), при расширении лассо вы увидите, что граница лассо исчезает. Немного похоже на блики на ЖК-дисплее, но гораздо заметнее.

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

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

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

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

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

Так что, если кто-то узнает больше об этом...

2 ответа

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

Во-первых, я также обнаружил, что эта "ошибка", похоже, влияет на все системы, но не одинаково.

Используя DwmGetCompositionTimingInfo, один раз можно создавать анимацию с V-синхронизацией GDI. Это теоретически, потому что на практике это не сработает из-за той же "ошибки", даже в системах, на которые не влияет то, что я описал выше. DWM просто примет решение не обновлять что-либо, когда его недостаточно для обновления, и это приведет к пропускам кадров при прокрутке окна с использованием ScrollWindowEx, и недостаточное количество пикселей будет признано недействительным.

Так в чем же решение? Обманите DWM, думая, что он должен немедленно поменять буферы, так как в этом вся проблема. При выполнении операций GDI можно подумать, что результат появится на следующем обновлении с синхронизацией vblank, но это не так. Некоторые части будут, некоторые будут отложены. Итак, хитрость заключается в том, чтобы заставить DWM поменяться местами, вот что я нашел по этому поводу:

Во-первых, DWMFlush не делает этого (как и GDIFlush). В любом случае DWMFlush является более или менее WaitForVBlank

-DWMSetPresentParameters, похоже, тоже этого не позволяет, хотя я не особо беспокоился об этой функции, так как она уже отсутствовала в Windows 8

- DWM, кажется, обновляется, когда имеется достаточно пикселей для замены, но он также кажется разделенным, вполне возможно, на широкие, но короткие прямоугольники (это то, что кажется на Surface Pro - но не на моем рабочем столе). Связано ли это с отверстиями для VRAM или сегментированием на маленькие текстуры, я понятия не имею, может, кто-то знает?

Итак, что работает для меня: указание GDI обновлять вертикальные полосы шириной 1 пиксель с разницей около 500 пикселей по всему экрану. Если вы делаете это только на части экрана, вы все равно будете мерцать на других частях. Он также работает с использованием горизонтальных полос, но тогда вам понадобится намного больше их, поэтому я считаю, что сегментация выполняется в широких коротких прямоугольниках. Вы можете увидеть эти прямоугольники, когда обманываете DWM.

Какие функции GDI работают? Многие делают, но они не все имеют одинаковое использование процессора. Во-первых, забудьте GetPixel, он работает, но загрузка процессора, очевидно, чрезвычайно высока. Во-вторых, GDI кажется достаточно интеллектуальным, чтобы обнаруживать вещи, которые можно буферизовать просто в виде команд, поэтому заполнение прямоугольника нулевой кистью или рисование пустого текста не заставит DWM обновляться. Что работает, так это BitBlt или AlphaBlend с использованием пустых вертикальных полос. Использование процессора все еще в порядке, хотя это не так далеко от того, чтобы бить весь экран. Это должно быть сделано в окне верхнего уровня, а не на рабочем столе.

Создание цели визуализации Direct2D для DC и выполнение begin/enddraw также работает, но это нормально, так как это приведет к обновлению полного прямоугольника, и, следовательно, стоимость ЦП выше.

Если кто-нибудь знает лучший способ принудительного обновления DWM, я хотел бы знать. Но так как Windows 8 уже устарела, большинство интересных функций DWM (к счастью, DwmGetCompositionTimingInfo все еще частично работает, или было бы невозможно сделать таймеры vsynced без DirectX), я не уверен, что есть какой-то лучший способ.

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

Вот несколько общих указателей, касающихся GDI относительно вашего кода:

  1. Когда вы вызываете InvalidateRect API, он может не сразу отправлять уведомление WM_PAINT, поэтому вызов InvalidateRect два раза подряд, как вы это делали в методе TForm1.FormMouseMove, наверняка вызывает этот визуальный эффект. Первая перерисовка еще не обрабатывается, когда вы вызываете ее во второй раз.

  2. Я не уверен, что именно Canvas.Rectangle делает "изнутри" или какие API он вызывает. Я предполагаю, что он использует DrawFocusRect, и в этом случае вы должны знать, что его рисунок XORed, так что если вы сделаете это дважды по одному и тому же прямоугольнику, это сотрет его.

Хорошо, вот что я бы сделал, если бы рисовал это поле выбора:

  1. Вызовите API InvalidateRect только один раз. Для этого вам нужно будет рассчитать ограничивающий прямоугольник, который будет включать в себя положение поля выбора до и после перемещения. Если я не ошибаюсь, вы можете использовать для этого UnionRect API или просто рассчитать ограничивающий прямоугольник самостоятельно.

  2. Если важно избежать визуальной задержки, я бы сделал все рисование поля выбора в TForm1.FormMouseMove. Вы можете получить контекст устройства, вызвав GetDC API на вашем дескрипторе окна. После этого сделайте тот же рисунок. В этом случае вам не понадобится аннулирование. (Извините, я не могу дать вам процедуру для Delphi. Именно так я бы сделал это с простыми WinAPI.)

РЕДАКТИРОВАТЬ: После просмотра вашего кода C++ я смог воспроизвести визуальный мерцание, которое вы описываете. Я также сделал два C++ проекта, основанных на вашем исходном коде, в надежде решить его: вот простая версия, а вот та, с поддержкой перетаскивания мышью. Никто из них не решил проблему, хотя.

Мне удалось воспроизвести мерцание на рабочем столе Windows 7. После нескольких тестов я убедился, что этот визуальный артефакт вызван драйвером дисплея. В моем случае на рабочем столе установлена ​​видеокарта ATI Radeon. Вот как я смог сделать вывод: я запустил тестовый исполняемый файл на другом (старом) рабочем столе с ОС Windows XP, и мерцания не было. Я запускал точно такой же исполняемый файл на виртуальной машине с установленной на ней Windows XP на настольном компьютере, который производил мерцание. Мерцание присутствовало, когда исполняемый файл запускался в Windows XP на этой виртуальной машине. Поскольку один и тот же видеодрайвер отвечает за рендеринг как самого рабочего стола, так и виртуального компьютера, наиболее вероятно, что происходит некоторая "забавная работа" с оптимизацией или кэшированием в видеодрайвере, которая вызывает этот артефакт.

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

Ваши следующие шаги, вероятно, должны быть такими:

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

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

  • Измените теги на ваш вопрос здесь. Добавьте их: C, C++, WinAPI, GDI, DWM и удалите то, что у вас есть сейчас. Таким образом, это будет видно для сообщества разработчиков Win32, так что вы получите больше просмотров.

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

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

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