Графика для Windows средствами DirectDraw
Шрифт:
• синхронизацию с потоком ввода;
• завершение потока ввода.
Основной поток должен в какой-то момент создать и запустить поток ввода (хотя создание и запуск потока можно выполнить одновременно, на самом деле это две разные операции). Поток должен быть запущен в начале работы программы, но следует позаботиться о том, чтобы это не произошло слишком рано. Преждевременный запуск может привести к тому, что поток ввода обнаружит ввод от мыши и попытается обновить курсор до того, как основной поток закончит инициализацию DirectDraw.
Основной поток также должен обновлять курсор перед каждым переключением страниц. После подготовки нового кадра во вторичном буфере, но до переключения
Чтобы основной поток не пытался обновить первичную поверхность одновременно с потоком ввода, мы должны синхронизировать работу этих двух потоков. Для основного потока это означает, что операция вывода курсора (во вторичный буфер) и переключение страниц может выполняться лишь после получения доступа к критической секции, используемой для синхронизации потока. Обратите внимание: подготовка вторичного буфера не входит в критическую секцию, потому что основной поток вполне может готовить вторичный буфер, пока поток ввода обновляет содержимое первичной поверхности.
Наконец, основной поток должен предупредить поток ввода о завершении приложения. Помните — поток ввода работает независимо от основного потока. Без извещения со стороны основного потока он не будет знать о том, что приложение собирается завершиться. Кроме того, основной поток не может просто остановить работу потока ввода; поток ввода должен завершиться сам при получении сигнала завершения от основного потока.
Поток ввода обладает более узкой специализацией по сравнению с основным потоком. Он должен делать следующее:
• обнаруживать ввод от мыши;
• обновлять курсор;
• синхронизироваться с основным потоком;
• обрабатывать сигнал завершения, полученный от основного потока.
Для получения ввода от мыши могут применяться две схемы: опрос и оповещение. Опрос плохо подходит для нашего случая, потому что поток ввода постоянно остается активным, даже если пользователь не работает с мышью. С другой стороны, если поток ввода блокируется до поступления новых данных от мыши, он почти не расходует лишнего процессорного времени. С помощью имеющегося в DirectInput механизма оповещения можно заблокировать поток ввода до тех пор, пока DirectInput не сообщит о поступлении новых данных.
После получения сигнала поток ввода извлекает новые данные и обновляет курсор одним из двух способов, рассмотренных выше. Независимо от того, какой способ будет использован, обновление курсора необходимо синхронизировать с основным потоком, чтобы потоки не пытались обратиться к первичной поверхности одновременно.
Наконец, поток ввода отвечает за свое завершение. При получении сигнала от основного потока он должен прекратить работу.
В начале этой главы мы решили создать курсор, который не мерцает и мгновенно реагирует на перемещения мыши при любой частоте вывода кадров. Нам удалось спроектировать (не считая собственно кодирования) решение, удовлетворяющее этим критериям. Многопоточность позволила отделить ввод мыши от приложения. Если не считать исходного запуска потока ввода и синхронизации, наш основной поток решает общие задачи приложения и не занимается получением ввода.
Но стоит ли полностью изолировать приложение от мыши? Если основной поток ничего не знает о том, что происходит с мышью, мы не сможем пользоваться мышью в приложении.
Мы поступили правильно, удалив получение данных о перемещениях
Поток ввода уже занимается получением данных от мыши, поэтому возможное решение проблемы — заставить его сохранять сведения о кнопках в очереди. Затем основной поток сможет получить эти данные, проверяя содержимое очереди.
Поскольку очередь совместно используется двумя потоками, нам придется позаботиться о синхронизации. В каждом потоке уже присутствует поддержка критических секций, так что добиться синхронизации будет нетрудно.
Программа Cursor
Программа Cursor использует описанную выше методику и выводит на экран изображение вращающейся спирали, меню задержки и курсор мыши. По умолчанию программа выводит кадры максимально часто, но меню задержки позволяет уменьшить частоту вывода за счет задержки в основном потоке (максимальная задержка равна 500 миллисекундам, при этом приложение замедляется до 2 FPS). Если бы курсор не управлялся отдельным потоком, его обновление происходило бы лишь с выводом очередного кадра. Но поскольку курсор мыши не зависит от основного потока, он нормально реагирует на действия пользователя при любой частоте вывода. Программа Cursor изображена на рис. 7.1.
Рис. 7.1. Программа Cursor
Перед тем как погружаться в программный код, я должен признаться, что работа над программой Cursor сопровождалась внутренней борьбой. Мне очень хотелось разбить код на несколько мелких функций, скрыть некоторые технические подробности и упорядочить структуру программы. Однако я справился с искушением — читатель наверняка захочет видеть программу прямо перед собой, вместо того чтобы искать нужный фрагмент по всему коду. В результате программа получилась менее структурированной по сравнению с другими.
Как создать собственный курсор
Программа Cursor может работать с курсором любого размера. В версии программы на CD-ROM использован небольшой курсор (12×20 пикселей), но вы можете легко изменить этот стандартный размер. Для этого достаточно заменить cursor_08.bmp и/или cursor_24.bmp файлами с более крупными изображениями курсоров.
По умолчанию приложение работает в 8-битном видеорежиме и соответственно с 8-битным курсором. Многое зависит от вашего графического редактора, но, скорее всего, вы избавитесь от проблем с палитрой, если воспользуетесь файлом cursor_08.bmp с CD-ROM как шаблоном для создания нестандартного курсора. С курсором формата True Color дело обстоит проще, но, чтобы воспользоваться им, придется слегка подправить функцию SelectInitialDisplayMode, чтобы активизировать беспалитровый видеорежим вместо палитрового.
Программа Cursor, как и все остальные программы этой книги, построена на базе структурных классов DirectDrawWin и DirectDrawApp. Эти классы остались неизменными, а вся специфика приложения реализуется классом CursorWin. На практике функциональность курсора мыши, вероятно, следовало бы встроить в структурный класс. И все же для наглядности я объединил код для работы с курсором со специфическим кодом приложения. Класс CursorWin приведен в листинге 7.1.