Значит, если полноэкранное приложение захочет вывести курсор мыши, ему придется делать это самостоятельно. На первый взгляд все просто: нужно создать небольшую поверхность с изображением курсора и перемещать ее в соответствии с вводом от мыши. Такое решение работает, но у него есть свои недостатки.
Раз курсор мыши отображается самим приложением, а не Windows, частота его прорисовки зависит от быстродействия приложения. Если приложение постоянно работает на 75 FPS, это будет приемлемо, но что если частота вывода кадров упадет до 30 FPS и ниже? С падением частоты курсор будет все медленнее реагировать на действия пользователя.
Существует множество причин, по которым приложение может иметь низкую частоту вывода кадров. Трехмерные приложения предъявляют особенно жесткие требования к системе и часто работают с частотой 30 FPS и менее. Но «тормозить» могут и обычные, не трехмерные приложения. Сложное приложение, выводящее сотни объектов, будет иметь низкий FPS (конечно, все зависит от компьютера и видеокарты). Кроме того, режимы High и True Color часто оказываются намного медленнее 8-битных режимов.
Более того, простому приложению DirectDraw вряд ли потребуется курсор мыши. Если возникла необходимость в курсоре, значит, пользователь должен выделять определенные области экрана. Сомнительно, чтобы сложное приложение смогло обеспечить высокую частоту вывода, пока пользователь работает с мышью.
Итак, нам придется искать нетривиальное решение. Оно должно обеспечивать быструю реакцию на действия с мышью независимо от FPS, а курсор не должен мерцать. Поскольку нам все равно придется создавать собственный курсор, для него можно выбрать произвольный размер.
Эта глава посвящена решению, которое удовлетворяет всем перечисленным критериям. Сначала мы обсудим частичное обновление экрана (прямой блиттинг на первичную поверхность), а затем поговорим о том, как многопоточность обеспечивает обновление курсора, не зависящее от частоты вывода. Наконец, теория воплотится на практике в виде программы Cursor.
Частичное обновление экрана
Типичное приложение DirectDraw (наподобие тех, что рассматривались в предыдущих главах) заранее строит весь кадр во вторичном буфере и затем переключает страницы. Эта методика работает быстро (переключение страниц обычно происходит почти мгновенно) и не вызывает мерцания (построение каждого кадра завершается до его вывода).
Чтобы обновление курсора не зависело от частоты вывода, нам придется обновлять курсор так, чтобы обойтись без переключения страниц. Поэтому вместо того, чтобы обновлять весь экран, мы будем перерисовывать лишь его часть. Для этого можно непосредственно изменить содержимое первичной поверхности.
Хотя в нашем случае частичное обновление экрана используется для вывода курсора мыши, эта методика полезна и в других случаях. Например, приложение может обновить меню прямо на первичной поверхности вместо того, чтобы заново строить весь кадр (вместе с изменившимся меню) на вторичном буфере и затем переключать страницы.
С другой стороны, прямой вывод на первичную поверхность имеет свои недостатки и требует осторожности. Основная потенциальная проблема — расхождение. Переключение страниц выполняется так, чтобы предотвратить возможность расхождения. Следовательно, если вы обходите механизм переключения страниц и обновляете кадр, который в данный момент отображается на экране, то рискуете изменить область экрана, в данный момент обновляемую монитором. Если это произойдет, новое содержимое обновляемой части в течение некоторого времени будет выводиться одновременно со старым — возникнет расхождение.
Для борьбы с расхождением есть два пути. Во-первых, можно обновлять лишь малую область экрана — это сокращает вероятность расхождения. Во-вторых, обновление можно синхронизировать с циклом вертикальной развертки. Если подождать с обновлением экрана до завершения очередного переключения страниц, вероятность расхождения становится еще меньше.
Обновление курсораИтак, курсор мыши должен обновляться прямо на первичной поверхности. При очередном перемещении курсора необходимо выполнить два действия:
1. Стереть курсор в старом месте.
2. Нарисовать курсор в новом месте.
Первую задачу можно решить, восстанавливая ранее сохраненную часть первичной поверхности. Затем мы рисуем курсор мыши на первичной поверхности в новом месте. Тем не менее для восстановления изображения придется добавить дополнительный шаг — перед выводом курсора сохранить часть первичной поверхности, которую он займет. В результате получается следующий алгоритм: