Встатье «Работа с несколькими спрайтами» (см. «Мир ПК», № 2/02, с. 107) было рассказано, как нужно изменить программу, чтобы она смогла выводить несколько спрайтов одновременно. Однако попытка проделать это с сотнями спрайтов привела к тому, что частота смены кадров резко упала и экран неприятно замигал. Попробуем разобраться, как справиться с данными явлениями.

В первую очередь установим, из-за чего же все это происходит. Давайте вспомним, как выводятся несколько спрайтов. Сначала в цикле для каждого из них считывается фон из видеопамяти, затем прорисовываются и они сами. По окончании цикла выполняется вертикальная синхронизация, а затем под всеми восстанавливается фон, и процесс начинается заново. Если обрабатывается лишь несколько спрайтов, то времени на их вывод затрачивается существенно меньше, чем на ожидание, — на экране превалирует сформированная картинка. А когда большую часть времени занимает рисование спрайтов, то на экране рисунок отображается в динамике — и его отрисовка, и удаление спрайтов. Именно это и вызывает мелькание изображения. Значительное замедление работы связано не столько с недостаточной мощностью процессора, сколько с тем, что считывание из видеопамяти происходит во много раз медленнее, чем из оперативной.

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

Перед началом усовершенствования программы еще раз запустим ее с 500 спрайтами (если ПК «слабый», то можно ограничиться 100—200 спрайтами) и запомним, сколько кадров в секунду она способна выдать. При этом, чтобы на результат не влияла синхронизация с разверткой, вызов WaitVerticalRetrace необходимо закомментировать. Все дальнейшие измерения скорости будем также выполнять с отключенной синхронизацией.

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

CreateScreenBuffer; {создаем буфер}

Вызов этой процедуры следует поместить между BlackPal и PutBackGround. И вторая:

DestroyScreenBuffer; {уничтожаем буфер}

Эту процедуру нужно вызвать перед FadeOut(p).

После формирования в буфере очередного кадра он должен перебрасываться на экран при каждом повторении цикла:

ScreenBufferToScreen; {буфер — на экран}

Лучше всего поставить эту процедуру сразу же после ожидания вертикальной синхронизации, и тогда на экране не возникнут помехи.

Итак, когда все нужные изменения внесены, пойдем дальше. Однако вот беда, текст на экране почти невидим — он по-прежнему выводится, минуя буфер. Чтобы исправить это, введем в директиву uses модуля text256 ссылку на модуль sprites, а также перепишем процедуры вывода и чтения точки (листинг 2). Теперь можно запустить программу и посмотреть, что получилось.

Для проверки попытаемся временно закомментировать инициализацию экранного буфера. Все вернулось на круги своя. Стало ясно, что модуль sprites остается работоспособным без экранного буфера, но все же при его наличии скорость вывода изображения будет значительно выше, а качество картинки — лучше.

Можно также дополнительно ускорить программу, если создать еще один буфер для хранения фона. В этом случае цикл отображения будет выглядеть так: вывод из буфера фона в экранный буфер; поочередный вывод спрайтов в экранный буфер; ожидание вертикальной синхронизации; переброска содержимого буфера на экран. Как видим, цикл обработки существенно упростился — из него исчезли сохранение фона под спрайтами и его восстановление. В этом случае потребуется на один буфер больше, что приведет к дополнительному расходу 12—15% всей имеющейся в распоряжении памяти. Но и это не беда, ведь нам не придется сохранять фон под спрайтами, и на каждый из них будет расходоваться уже не 800, а 400 байт. И кроме того, если размер буфера фона сделать больше размера экранной памяти, то у нас появится возможность реализовать горизонтальный и вертикальный скроллинги, но это опять же сопряжено с дополнительным расходом памяти. Правда, можно обойтись без экранного буфера и выводить изображение сразу же на экран. При этом памяти расходуется меньше, мерцание увеличивается, а скорость появления картинки будет зависеть от количества спрайтов. Обычно считается, что если суммарная площадь выводимых одновременно спрайтов превосходит 30—40% площади экрана, то целесообразнее сохранить весь фон целиком. Если же эта площадь меньше, то лучше иметь для каждого спрайта отдельный фрагмент фона. В общем, открывается обширное поле для экспериментов.

Продолжим же наши эксперименты, для чего скопируем все нужные файлы (с расширениями .pas и .bmp) в другой каталог, чтобы сравнивать полученные результаты и, если понадобится, вернуться к старому варианту.

Теперь дополним модуль sprites тремя новыми процедурами аналогично тому, как мы это сделали при создании экранного буфера, и введем необходимые переменные. Следует также внести изменения в процедуру первоначального заполнения фона (листинг 3). Если ограничиться лишь этим, то отобразить одновременно 500 спрайтов не удастся — не хватит памяти. А поскольку теперь для хранения фона не нужны массивы в каждом спрайте, то уберем из структуры описывающее спрайт поле Back, а в процедурах создания и уничтожения спрайта ликвидируем выделение и освобождение соответствующих областей памяти. Также вынесем из модуля процедуры GetBuffer и PutBuffer, так как они уже просто не нужны, и уберем обращения к ним из основной программы. Вместо этого в программу вставим вызов процедур создания и уничтожения буфера фона рядом с аналогичными вызовами процедур создания и уничтожения экранного буфера, а также поместим в цикле перед началом рисования спрайтов вызов процедуры копирования информации из буфера фона.

Теперь мы можем сравнивать, как изменилась частота кадров по сравнению с предыдущим вариантом и с вариантом, опубликованным в «Мире ПК», № 2/02. (Полные тексты листингов программ можно найти по адресу www.pcworld.ru.) В следующий раз будет рассказано, как сделать так, чтобы спрайты не только передвигались по экрану, но и выглядели «живыми».


Листинг 1

Листинг 2

Листинг 3