В прошлый раз я познакомил тебя со спрайтами и сегодня хотел показать тебе более интересный пример создающий анимацию. Но когда я его написал, то ощутил нехватку производительности, поэтому сначала нам надо узнать, как можно ускорить вывод графики. Как говориться: "никогда не бывает самых быстрых программ, всегда есть способ ускорить уже существующее решение". С одним из способов ускорения графики мы и познакомимся.
Для начала попробуем ускорить пример, написанный в прошлом статье. При выводе графики, мы использовали очень быструю функцию BltFast, но она рисует прямо на первичной поверхности. Что же в этом страшного? Пока мы рисуем только одну картинку, ты не заметишь тормозов, но как только количество рисунков превысит 5, тормоза будут уже ощутимыми.
После вывода каждой картинки видюха должна отобразить изменения на экране, а, как известно, вывод на экран намного медленнее, чем прямая работа с памятью. Намного эффективнее было бы подготовить все изображение в отдельной памяти, а потом полностью вывести его на экран. Ты наверно уже бросился стучать по клавишам, корректируя пример? Не торопись, потому что и это не предел оптимизации.
Когда ты выводишь картинку на экран, данные полностью копируются из системной памяти в видеокарту. Это копирование очень медленное и если изображение на экране очень часто меняется, то может непроизвольно появиться эффект бликов. Производители железа всеми силами стараются увеличить скорость шины видеокарты, вводя разные версии AGP, но она в данном случае не помощник.
Чтобы не быть привязанным к шине видюхи, программеры пошли на одну маленькую хитрость. Видеокарты имеют достаточный объем видеопамяти и могут хранить не одну копию экрана. Трюк состоит в том, что мы будем создавать две поверхности экрана и поочередно показывать их. Пока одна поверхность отображается, мы можем смело рисовать во вторичной поверхности. Как только изображение сформировалось, мы делаем вторую поверхность главной, и она моментально отображается на экране. Такое отображение называется переключением экранов.
Рисунок 1
При переключении экранов не происходит копирования второй поверхности в первую. В видеокарте есть специальный параметр, который отвечает за то, какая область памяти видюхи является главной и содержит данные, которые надо отобразить. Во время переключения поверхностей меняется только этот параметр, а это происходит практически моментально, потому что обе поверхности уже находятся в видеокарте и любая из них может быть главной.
Давай подкорректируем наш предыдущий пример, чтобы он отображал данные с помощью переключающихся экранов. Для этого в раздел var добавь объявление еще одной поверхности, которая будет вторичной:
FBackgroundSurface : IDirectDrawSurface7;
Теперь надо подкорректировать код, который создает главную поверхность и указать, что нужно будет создавать и вторичную поверхность:
FillChar (SurfaceDesc, SizeOf(SurfaceDesc), 0); // Обнуляем структуру SurfaceDesc
SurfaceDesc.dwSize := SizeOf(SurfaceDesc); // Указываем ее размер (это обязательно)
SurfaceDesc.dwFlags := DDSD_CAPS or DDSD_BACKBUFFERCOUNT;
SurfaceDesc.dwBackBufferCount := 1; //Указываем количество вторичных экранов
SurfaceDesc.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE or
DDSCAPS_FLIP or DDSCAPS_COMPLEX;
FDirectDraw.CreateSurface(SurfaceDesc, FPrimarySurface, nil); //Создаем поверхность
Заметь, что в свойстве dwFlags структуры SurfaceDesc (SurfaceDesc.dwFlags) появился еще один флаг - DDSD_BACKBUFFERCOUNT. Этот флаг говорит о том, что в свойстве dwBackBufferCount будет указано количество дополнительных экранов. Если этот флаг не указать, то свойство dwBackBufferCount игнорируется.
Сколько поверхностей ты создашь, зависит только от твоей жадности и возможностей видюхи (точнее сказать от ее памяти), но для большинства примеров достаточно 1 дополнительной поверхности. Если поверхность не поместиться в памяти видюхи, то она будет создана в оперативке, но тут уже можно забыть о переключении. Так что я не советую сильно жадничать и использовать память по минимуму.
В параметре SurfaceDesc.ddsCaps.dwCaps я указал следующие флаги:
DDSCAPS_PRIMARYSURFACE - создаваемая поверхность будет первичной.
DDSCAPS_FLIP - поверхность должна поддерживать переключения.
DDSCAPS_COMPLEX - поверхности должны быть комплексными.
После того, как структура SurfaceDesc подготовлена, мы можем создать первичную поверхность точно так же, как это делалось раньше с помощью CreateSurface. Вместе с главной поверхностью сразу создаются и все дополнительные. Нам не надо тратить свои силы на их создание, потому что все уже готово.
Чтобы получить доступ к вторичной поверхности, необходимо написать следующий код:
FillChar(fcaps, SizeOf(fcaps), 0); //Обнуляем структуру fcaps.
fcaps.dwCaps := DDSCAPS_BACKBUFFER; //Указываем, что нам нужна вторичная поверхность.
FPrimarySurface.GetAttachedSurface(fcaps, FBackgroundSurface); //Получаем ее.
Чтобы получить доступ к вторичной поверхности надо вызвать метод главной поверхности GetAttachedSurface. В качестве второго параметра этого метода надо указать переменную, куда будет записана информация о вторичной поверхности.
Здесь я использую новую структуру fcaps, которую тоже надо добавить в раздел var:
var
fcaps : TDDSCaps2;
В этой структуре указывается, что именно мы хотим сделать при вызове метода GetAttachedSurface. Мы получаем доступ к вторичной поверхности, значит надо обязательно указать флаг DDSCAPS_BACKBUFFER.
Теперь подкорректируем код отображения. Как я уже говорил, при использовании переключающихся поверхностей, рисовать надо на вторичной, а потом только делать ее главной. В виде кода это выглядит следующим образом:
Теперь я рисую с помощью функции BltFast на вторичной поверхности FBackgroundSurface. После этого я только вызываю метод главной поверхности Flip и вторичная поверхность становиться главной. В качестве второго параметра я указываю флаг DDFLIP_WAIT, что означает, что если сейчас переключение экрана невозможно, то дождаться разрешения.
Если ты подумал, что после переключения экрана переменная FPrimarySurface начинает показывать не на первичную, а на вторичную поверхность, то ты сильно ошибаешься. Переменные FPrimarySurface и FBackgroundSurface не меняют своего назначения. Переменная FPrimarySurface всегда указывает на первичную, а FBackgroundSurface на вторичную поверхность.
Луч монитора рисует изображение, начиная с левого верхнего угла примерно так, как показано на рисунке 2. Когда луч доходит до левого нижнего угла, отображение останавливается, и луч вхолостую направляется в начало экрана (на рисунке показано красной стрелкой).
Рисунок 2
Переключение экрана может быть невозможно в тот момент, когда луч экрана отображает информацию и разрешено, когда луч возвращается в начало рисования. Это вполне логично, потому что если попробовать переключить экран в середине отображения, верхняя половина экрана будет показывать старый экран, а нижняя новый. Такой эффект губителен для глаз и создаст сумасшедшие мерцания. Именно поэтому переключение доступно не всегда и если не установить флаг DDFLIP_WAIT, то переключение может вообще не произойти.