Реактивное введение в программирование Game Boy Advance (часть 2 из 8)
Пиксельные видеорежимы…
Часть 1: Инструментарий, основы, кнопки, таймеры
Часть 2: Пиксельные видеорежимы
Часть 3: Тайловые видеорежимы
Часть 4: Спрайты
Часть 5: Вращающиеся фоны и прозрачность
Часть 6: Прерывания, DMA
Часть 7: Звук (Direct Sound)
Часть 8: Сохранения
Итак, пришло время вывести что-нибудь на экран GBA. LCD-экран аппарата имеет физическое разрешение 240x160 пикселей и способен отображать 15-битный (High Color) цвет формата 5:5:5. 59.73 раз в секунду (в точности 280896 тактов ЦП) видеоадаптер обновляет содержимое экрана содержимым видеопамяти и памяти спрайтов. Каждое обновление экрана состоит из 160 обновлений строк (этот период в целом называется VDraw), за которыми следуют как бы 68 «невидимых» строк — период когда видеоконтроллер бездействует (он в свою очередь называется VBlank). Каждое обновление строки экрана в свою очередь разбито на период обновления пикселей в строке (HDraw), который длится 1004 тактов за которым следует короткий период бездействия длиной 228 тактов (HBlank).
GBA имеет 6 разных видеорежимов (MODE_0-5) из которых мы сейчас рассмотрим 3 последних — так называемых «пиксельных» видеорежимов. Они являются самыми простыми для первых попыток что-либо отобразить. Рассмотрим их по порядку:
Раскладка памяти более чем классическая — пиксели описываются строками от левого верхнего угла экрана слева-направа-сверху-вниз по порядку.
Здесь, однако, возникает очень важное замечание: вся видеопамять (VID_RAM) обладает очень существенной особенностью: в неё нельзя записать меньше, чем 2 байта (16 бит) за раз. Контроллер видеопамяти устроен таким образом, что попытка записать одиночный байт в видеопамять приведет к дублированию его записи в смежный по чётности байт. Любопытно, но с чтением такой особенности не возникает, а вот запись то ли для скорости (что иногда сомнительно), то ли для сокращения количества проводов всегда осуществляется 16-битными обращениями. Для видеорежимов 3 и 5 это не страшно — пиксели там и так двухбайтные и с байтами возится не надо. Но 8-битный палитровый режим 4 хранит по байту на каждый пиксель и выходит, что записывать/обновлять мы можем только по 2 соседних пикселя за раз. Чтобы обновить отдельный пиксель нужно считать значение пары, в зависимости от четности обновить его и записать обратно. Это довольно таки дорого, поэтому есть смысл иметь эту особенность ввиду.
Установка видеорежимов и самых важных параметров видеоадаптера производится через запись в порт REG_DISPCNT. Нижние 3 его бита численно равны выставленному видеорежиму (от 0 до 6). Включение 4-го (нумерация с нуля!) бита переключает дисплей на отображение второй видеостраницы пиксельных видеорежимов. А биты с 9 по 12 включают отображение задних фонов (backgrounds) от BG0 до BG3. Эти биты в основном нужны в тайловых видеорежимах, которые мы пока не рассматриваем, но видеоадаптер в пиксельных видеорежимах ведет себя так, как будто карта пикселей это задний фон BG2 и нам его надо включить, иначе на экране ничего не появится.
Вторым важным сейчас для нас видеопортом является REG_VCOUNT в котором видеоадаптер поддерживает текущий номер обновляемой строки экрана. Это значение периодически увеличивается от 0 до 159 во время VDraw, потом еще 68 раз увеличивается во время VBlank, после чего цикл повторяется. Это позволяет нам легко реализовать т.н. VSync.
Вкратце интерфейс работы с пиксельными видеорежимами можно описать вот так:
Ну и вот теперь мы готовы создать первую осмысленную нашу программу под GBA, использующую кнопки, таймеры и пиксельные видеорежимы.
Для отрисовки текста нам понадобится какой никакой, но шрифт. Проще всего быстро набросать его как массив и подключать потом при необходимости его прямо в тело программы.
Для начала мы сделаем вот что — создадим заголовочный файл gba_defs.h и добавим в него весь код что мы описывали выше, включая предыдущую главу (в исходниках он уже присутствует в полном объёме по всем урокам). Этот файл в итоге составит как бы API для работы с GBA, весь код с макросами описывающими порты ввода-вывода попадёт в него.
Далее создадим файл microfont.cpp и заполним его следующим кодом:
Теперь создадим программу, которая:
Выводит FPS.
Может включать/выключать VSync (вкл. — кнопка «A», выкл. — кнопка «B»)
Может залить фон однотонным цветом (вкл. заливку — «L», выкл. — «R»)
Может изменять цвет заливки (кнопками Влево-Вправо).
По нажатию на кнопку Вверх переключает видеорежим на 0x3, а по нажатию на кнопку Вниз — 0x4.
Создаём файл 04_bitmap_modes.cpp и заполняем его следующим кодом:
Далее создаём файл build_04_bitmap_modes.bat:
Если нигде не допущено ошибок, то должен появится файл 04_bitmap_modes.gba запустив который мы можем наглядно увидеть, что пиксельные видеорежимы 16-мегагерцовым ARM-ом GBA заливаются довольно медленно и их реальное применение в играх очень сильно ограничено. Видеоадаптер больше не может нам с ними ничем помочь — нет аппаратного ускорения блиттинга, как, например, в DirectDraw.
Поэтому по настоящему раскрывается консоль в тайловых видеорежимах, которые по сути являются аппаратным ускорением двумерной графики. Следующая часть будет про них.
Однако есть один забавный момент, который нельзя не отметить в этой новой редакции статьи — в следующих уже главах я обнаружу, что использование вещественных чисел (которая на GBA реализуется программной эмуляцией) насколько сильно роняет производительность даже в примитивных вещах, что даже просто подсчёт fps с их помощью в данном уроке должен сильно исказить достоверный результат — поэтому истинную картину предлагаю вам восстановить самостоятельно.
Продолжение...
Оглавление
Часть 1: Инструментарий, основы, кнопки, таймеры
Часть 2: Пиксельные видеорежимы
Часть 3: Тайловые видеорежимы
Часть 4: Спрайты
Часть 5: Вращающиеся фоны и прозрачность
Часть 6: Прерывания, DMA
Часть 7: Звук (Direct Sound)
Часть 8: Сохранения
4. Пиксельные видеорежимы
Итак, пришло время вывести что-нибудь на экран GBA. LCD-экран аппарата имеет физическое разрешение 240x160 пикселей и способен отображать 15-битный (High Color) цвет формата 5:5:5. 59.73 раз в секунду (в точности 280896 тактов ЦП) видеоадаптер обновляет содержимое экрана содержимым видеопамяти и памяти спрайтов. Каждое обновление экрана состоит из 160 обновлений строк (этот период в целом называется VDraw), за которыми следуют как бы 68 «невидимых» строк — период когда видеоконтроллер бездействует (он в свою очередь называется VBlank). Каждое обновление строки экрана в свою очередь разбито на период обновления пикселей в строке (HDraw), который длится 1004 тактов за которым следует короткий период бездействия длиной 228 тактов (HBlank).
GBA имеет 6 разных видеорежимов (MODE_0-5) из которых мы сейчас рассмотрим 3 последних — так называемых «пиксельных» видеорежимов. Они являются самыми простыми для первых попыток что-либо отобразить. Рассмотрим их по порядку:
- Режим 3 имеет разрешение 240x160x15. Начинается с адреса 0x06000000 (VID_RAM_START). Каждый пиксель занимает 2 байта 15-битного RGB-цвета. Из-за большой величины видеобуфера не имеет второй страницы. Если завести макрос #define VID_BUFFER ((u16 *) VID_RAM_START), то можно адресовать отдельные пиксели как VID_BUFFER[ x + 240 * y ], где x — номер пикселя по горизонтали от 0 до 239, а y — по вертикали (сверху-вниз) от 0 до 159.
- Режим 4 имеет разрешение 240x160x8. Начинается с адреса 0x06000000, имеет вторую страницу по адресу 0x0600A000. Переключение между видеостраницами контролируется битом MODE_PAGE_2 регистра REG_DISPCNT, что позволяет реализовать быстрое переключение страниц. Однобайтовые пиксели хранят индекс в 16-битной палитре из 256 ячеек по адресу BGR_PALETTE, таким образом одновременно на экране не может быть отображено больше 256 вариантов цветов, но какие именно цвета могут быть отображены одновременно — контролируются в массиве палитры.
- Режим 5 имеет разрешение 160x128x16. Начинается с адреса 0x06000000, из-за пониженного разрешения тоже имеет вторую страницу по адресу 0x0600A000.
Раскладка памяти более чем классическая — пиксели описываются строками от левого верхнего угла экрана слева-направа-сверху-вниз по порядку.
Здесь, однако, возникает очень важное замечание: вся видеопамять (VID_RAM) обладает очень существенной особенностью: в неё нельзя записать меньше, чем 2 байта (16 бит) за раз. Контроллер видеопамяти устроен таким образом, что попытка записать одиночный байт в видеопамять приведет к дублированию его записи в смежный по чётности байт. Любопытно, но с чтением такой особенности не возникает, а вот запись то ли для скорости (что иногда сомнительно), то ли для сокращения количества проводов всегда осуществляется 16-битными обращениями. Для видеорежимов 3 и 5 это не страшно — пиксели там и так двухбайтные и с байтами возится не надо. Но 8-битный палитровый режим 4 хранит по байту на каждый пиксель и выходит, что записывать/обновлять мы можем только по 2 соседних пикселя за раз. Чтобы обновить отдельный пиксель нужно считать значение пары, в зависимости от четности обновить его и записать обратно. Это довольно таки дорого, поэтому есть смысл иметь эту особенность ввиду.
Установка видеорежимов и самых важных параметров видеоадаптера производится через запись в порт REG_DISPCNT. Нижние 3 его бита численно равны выставленному видеорежиму (от 0 до 6). Включение 4-го (нумерация с нуля!) бита переключает дисплей на отображение второй видеостраницы пиксельных видеорежимов. А биты с 9 по 12 включают отображение задних фонов (backgrounds) от BG0 до BG3. Эти биты в основном нужны в тайловых видеорежимах, которые мы пока не рассматриваем, но видеоадаптер в пиксельных видеорежимах ведет себя так, как будто карта пикселей это задний фон BG2 и нам его надо включить, иначе на экране ничего не появится.
Вторым важным сейчас для нас видеопортом является REG_VCOUNT в котором видеоадаптер поддерживает текущий номер обновляемой строки экрана. Это значение периодически увеличивается от 0 до 159 во время VDraw, потом еще 68 раз увеличивается во время VBlank, после чего цикл повторяется. Это позволяет нам легко реализовать т.н. VSync.
Вкратце интерфейс работы с пиксельными видеорежимами можно описать вот так:
// Порты ввода-вывода видеоадаптера
// Макрос RGB показывает раскладку цветов 15-битного цвета.
#define RGB(r,g,b) (r+((g)<<5)+((b)<<10))
// *** РЕГИСТР КОНТРОЛЯ ВИДЕОРЕЖИМА
#define REG_DISPCNT (*((volatile u32 *) 0x4000000))
// Нужные нам битовые флаги
#define MODE_3 0x3
#define MODE_4 0x4
#define MODE_5 0x5
// Бит 4: для пиксельных режимов переключает отображение на вторую страницу.
#define MODE_PAGE_2 0x0010
// Включение заднего фона от 0 до 3. Пиксельные видеорежимы считаются задним фоном BG2.
#define BG0_ENABLE 0x0100
#define BG1_ENABLE 0x0200
#define BG2_ENABLE 0x0400
#define BG3_ENABLE 0x0800
// *** РЕГИСТР ТЕКУЩЕЙ ВЫВОДИМОЙ НА ЭКРАН СТРОКИ ПИКСЕЛЕЙ
#define REG_VCOUNT (*((volatile u16 *) 0x4000006))
#define BGR_PALETTE ((u16 *) BGR_PAL_RAM_START)
#define SPR_PALETTE ((u16 *) SPR_PAL_RAM_START)
#define VID_BUFFER ((u16 *) VID_RAM_START)
Ну и вот теперь мы готовы создать первую осмысленную нашу программу под GBA, использующую кнопки, таймеры и пиксельные видеорежимы.
Для отрисовки текста нам понадобится какой никакой, но шрифт. Проще всего быстро набросать его как массив и подключать потом при необходимости его прямо в тело программы.
Для начала мы сделаем вот что — создадим заголовочный файл gba_defs.h и добавим в него весь код что мы описывали выше, включая предыдущую главу (в исходниках он уже присутствует в полном объёме по всем урокам). Этот файл в итоге составит как бы API для работы с GBA, весь код с макросами описывающими порты ввода-вывода попадёт в него.
Далее создадим файл microfont.cpp и заполним его следующим кодом:
#include "gba_defs.h"
static const unsigned char micro_font[] =
// 32
" "
" "
" "
" "
" "
" "
// 33
" X "
" X "
" X "
" "
" X "
" "
// 34
"XX XX "
" "
" "
" "
" "
" "
// 35
" X X "
"XXXXX "
" X X "
"XXXXX "
" X X "
" "
// 36
" X "
" XXXX "
" X "
"XXXX "
" XX "
" "
// 37
"XX X "
"XX X "
" X "
" X XX "
"X XX "
" "
// 38
" XX "
"X X X "
" X X "
"X X "
" X X "
" "
// 39
" X "
" X "
" "
" "
" "
" "
// 40
" X "
" X "
" X "
" X "
" X "
" "
// 41
" X "
" X "
" X "
" X "
" X "
" "
// 42
" "
" X X "
" X "
" X X "
" "
" "
// 43
" "
" X "
" XXX "
" X "
" "
" "
// 44
" "
" "
" "
" "
" X "
" X "
// 45
" "
" "
" XXXX "
" "
" "
" "
// 46
" "
" "
" "
" "
" X "
" "
// 47
" "
" X "
" X "
" X "
" X "
" "
// 48
" XXX "
"X X "
"X X X "
"X X "
" XXX "
" "
// 49
" X "
" XX "
" X X "
" X "
" X "
" "
// 50
" XXX "
"X X "
" X "
" XX "
"XXXXX "
" "
// 51
" XXX "
" X "
" XX "
" X "
" XXX "
" "
// 52
" X X "
" X X "
" XXXX "
" X "
" X "
" "
// 53
"XXXX "
"X "
"XXXX "
" XX "
"XXXX "
" "
// 54
"XXXXX "
"X "
"XXXX "
"X X "
" XXX "
" "
// 55
" XXXX "
" X "
" X "
" X "
" X "
" "
// 56
" XXX "
"X X "
" XXX "
"X X "
" XXX "
" "
// 57
" XXX "
"X X "
" XXXX "
" X "
" XXX "
" "
// 58
" "
" XX "
" "
" XX "
" "
" "
// 59
" "
" XX "
" "
" XX "
" X "
" "
// 60
" "
" X "
" X "
" X "
" "
" "
// 61
" "
" XXXX "
" "
" XXXX "
" "
" "
// 62
" "
" X "
" X "
" X "
" "
" "
// 63
" XXX "
" X "
" X "
" "
" X "
" "
// 64
" XXX "
"X XX "
"X XXX "
" XXXX "
" XXX "
" "
// 65
" XX "
" X X "
" XXXX "
" X X "
" X X "
" "
// 66
" XX "
" X X "
" XXX "
" X X "
" XXX "
" "
// 67
" XXX "
"X X "
"X "
"X X "
" XXX "
" "
// 68
"XXXX "
"X X "
"X X "
"X X "
"XXXX "
" "
// 69
" XXXX "
" X "
" XX "
" X "
" XXXX "
" "
// 70
" XXXX "
" X "
" XXX "
" X "
" X "
" "
// 71
" XXX "
"X "
"X XX "
"X X "
" XXX "
" "
// 72
"X X "
"X X "
"XXXXX "
"X X "
"X X "
" "
// 73
" XXX "
" X "
" X "
" X "
" XXX "
" "
// 74
" XXX "
" X "
" X "
" X X "
" X "
" "
// 75
" X X "
" X X "
" XX "
" X X "
" X X "
" "
// 76
" X "
" X "
" X "
" X "
" XXXX "
" "
// 77
"X X "
"XXXXX "
"X X X "
"X X "
"X X "
" "
// 78
"X X "
"XX X "
"X X X "
"X XX "
"X X "
" "
// 79
" XXX "
"X X "
"X X "
"X X "
" XXX "
" "
// 80
" XXX "
" X X "
" XXX "
" X "
" X "
" "
// 81
" XXX "
"X X "
"X X X "
"X X "
" XX X "
" "
// 82
" XXX "
" X X "
" XXX "
" X X "
" X X "
" "
// 83
" XXX "
" X X "
" X "
"X X "
" XXX "
" "
// 84
"XXXXX "
" X "
" X "
" X "
" X "
" "
// 85
" X X "
" X X "
" X X "
" X X "
" XX "
" "
// 86
"X X "
"X X "
" X X "
" X X "
" X "
" "
// 87
"X X "
"X X "
"X X X "
"X X X "
" X X "
" "
// 88
"X X "
" X X "
" X "
" X X "
"X X "
" "
// 89
"X X "
" X X "
" X "
" X "
" X "
" "
// 90
"XXXXX "
" X "
" X "
" X "
"XXXXX "
" "
// 91
" XXX "
" X "
" X "
" X "
" XXX "
" "
// 92
" "
" X "
" X "
" X "
" X "
" "
// 93
" XXX "
" X "
" X "
" X "
" XXX "
" "
// 94
" X "
" X X "
" "
" "
" "
" "
// 95
" "
" "
" "
" "
" XXXX "
" "
// 96
" XX "
" X "
" "
" "
" "
" "
// 97
" XX "
" X X "
" XXXX "
" X X "
" X X "
" "
// 98
" XX "
" X X "
" XXX "
" X X "
" XXX "
" "
// 99
" XXX "
"X X "
"X "
"X X "
" XXX "
" "
// 100
"XXXX "
"X X "
"X X "
"X X "
"XXXX "
" "
// 101
" XXXX "
" X "
" XX "
" X "
" XXXX "
" "
// 102
" XXXX "
" X "
" XXX "
" X "
" X "
" "
// 103
" XXX "
"X "
"X XX "
"X X "
" XXX "
" "
// 104
"X X "
"X X "
"XXXXX "
"X X "
"X X "
" "
// 105
" XXX "
" X "
" X "
" X "
" XXX "
" "
// 106
" XXX "
" X "
" X "
" X X "
" X "
" "
// 107
" X X "
" X X "
" XX "
" X X "
" X X "
" "
// 108
" X "
" X "
" X "
" X "
" XXXX "
" "
// 109
"X X "
"XXXXX "
"X X X "
"X X "
"X X "
" "
// 110
"X X "
"XX X "
"X X X "
"X XX "
"X X "
" "
// 111
" XXX "
"X X "
"X X "
"X X "
" XXX "
" "
// 112
" XXX "
" X X "
" XXX "
" X "
" X "
" "
// 113
" XXX "
"X X "
"X X X "
"X X "
" XX X "
" "
// 114
" XXX "
" X X "
" XXX "
" X X "
" X X "
" "
// 115
" XXX "
" X X "
" X "
"X X "
" XXX "
" "
// 116
"XXXXX "
" X "
" X "
" X "
" X "
" "
// 117
" X X "
" X X "
" X X "
" X X "
" XX "
" "
// 118
"X X "
"X X "
" X X "
" X X "
" X "
" "
// 119
"X X "
"X X "
"X X X "
"X X X "
" X X "
" "
// 120
"X X "
" X X "
" X "
" X X "
"X X "
" "
// 121
"X X "
" X X "
" X "
" X "
" X "
" "
// 122
"XXXXX "
" X "
" X "
" X "
"XXXXX "
" "
// 123
" XXX "
" X "
"XX "
" X "
" XXX "
" "
// 124
" X "
" X "
" X "
" X "
" X "
" "
// 125
" XXX "
" X "
" XX "
" X "
" XXX "
" "
// 126
" X X "
" X X "
" "
" "
" "
" "
// 127
"X X X "
" X X "
"X X X "
" X X "
"X X X "
" "
;
// Вывод текста микрошрифта 6x6 в видеорежиме 0x4.
void draw3_micro_text( int x, int y, const char *text )
{
u16 *buf = VID_BUFFER + x + 240 * y; // Адрес для вывода текущего символа
char c;
while ( c = *text )
{
if ( (c < 32) || (c > 127) )
c = 127; // все символы кроме ASCII будут "решеткой"
c -= 32;
// находим опорный адрес символа в микрошрифте
const u8 *base = micro_font + 6 * 6 * c;
u16 *cur = buf;
for ( int l = 0; l < 6; l++ )
{
for ( int w = 0; w < 6; w++ )
cur[ w ] = *base++;
cur += 240;
};
buf += 6;
x += 6;
// Проверим выход за правый край экрана и вернем его
// в начало следующей строки при необходимости.
if ( x >= 240 )
{
x -= 240;
buf += 240 * 5;
};
text++;
};
};
// Вывод текста микрошрифта 6x6 в видеорежиме 0x4.
// В связи с тем, что запись в видеопамять должна
// производится 16-битными порциями приходится "паковать"
// соседние пиксели в пары при записи.
void draw4_micro_text( int x, int y, const char *text )
{
x = x & (~1u); // Координата по X должна быть чётной!
u16 *buf = VID_BUFFER + x + 120 * y; // Адрес для вывода текущего символа
char c;
while ( c = (*text) )
{
if ( (c < 32) || (c > 127) )
c = 127; // все символы кроме ASCII будут "решеткой"
c -= 32;
// находим опорный адрес символа в микрошрифте
const u8 *base = micro_font + 6 * 6 * c;
int soffs = 0;
for ( int l = 0; l < 6; l++ )
{
// Выводим строку пикселей пакуя соседние пары в u16 для записи.
buf[ soffs + 0 ] = (base[ 1 ] << 8) + base[ 0 ];
buf[ soffs + 1 ] = (base[ 3 ] << 8) + base[ 2 ];
buf[ soffs + 2 ] = (base[ 5 ] << 8) + base[ 4 ];
soffs += 120;
base += 6;
};
buf += 3;
x += 6;
// Проверим выход за правый край экрана и вернем его
// в начало следующей строки при необходимости.
if ( x >= 240 )
{
x -= 240;
buf += 120 * 5;
};
text++;
};
};
Теперь создадим программу, которая:
Выводит FPS.
Может включать/выключать VSync (вкл. — кнопка «A», выкл. — кнопка «B»)
Может залить фон однотонным цветом (вкл. заливку — «L», выкл. — «R»)
Может изменять цвет заливки (кнопками Влево-Вправо).
По нажатию на кнопку Вверх переключает видеорежим на 0x3, а по нажатию на кнопку Вниз — 0x4.
Создаём файл 04_bitmap_modes.cpp и заполняем его следующим кодом:
#include <string.h>
#include "gba_defs.h"
// Просто подключим массив микрошрифта в текущий юнит.
#include "microfont.cpp"
// sprintf оказался настолько тормозным, что возникло искушение
// сделать свою функцию по преобразованию int в строку.
// Обратите внимание, что для большей простоты в буфере уже дожно
// быть достаточно места слева, т.к. вывод цифр идёт как раз
// справа-налево.
void itostr( char *s, int d )
{
if ( !d )
{
*s-- = '0';
return;
};
while ( d )
{
int rem = d % 10;
d = d / 10;
*s-- = '0' + rem;
};
};
// Заливка экрана цветом.
// Для режима 0x4 для скорости заливка происходит двухбайтовыми
// значениями, что приводит к появлению вертикальных полосок.
void fill_screen_by_color( bool mode3, u16 color )
{
u16 *ptr = VID_BUFFER;
int vidsize = 240 * 160;
if ( !mode3 )
vidsize /= 2; // для 8-битного режима видеообласть вдвое меньше.
for ( int i = 0; i < vidsize; i++ )
{
*ptr++ = color;
};
};
int main(void)
{
// Изначально переключаемся в видеорежим 0x3, запоминаем что находимся в нём и
// для простоты обработки нажатий кнопок вводим флаг необходимости переключения.
REG_DISPCNT = MODE_3 | BG2_ENABLE;
bool mode3 = true, need_mode3 = true;
// 15-битный формат цвета консоли:
// 0 BBBBB GGGGG RRRRR
// F EDCBA 98765 43210
// Инициализируем палитру так, чтобы 8-битный индекс цвета совпадал с цветовой маской XBBGGGRR
// (X означает "не влияет")
// XBB GGG RR
// 765 432 10
for ( int i = 0; i < 256; i++ )
{
BGR_PALETTE[ i ] = RGB( (i & 3) << 3, i & 28, (i & 96) >> 2 );
};
// Включаем 0-ой таймер на частоту 65536 раз в секунду.
REG_TM0CNT = TIMER_FREQUENCY_256 | TIMER_ENABLE;
u16 t_last = REG_TM0D;
char buf[ 64 ];
u16 color = 63; // Текущий цвет для заливки экрана.
bool fill_by_color = false; // Заливать экран текущим цветом или нет.
bool wait_for_vblank = true; // Делать VSYNC или нет.
// Бесконечный цикл
while ( true )
{
// Дождёмся выхода в VBLANK
if ( wait_for_vblank )
{
while ( REG_VCOUNT < 160 );
};
// Вычислим время прошедшее с предыдущего чтения таймера
// и вычислим FPS.
u16 t_new = REG_TM0D;
u16 t_diff = t_new - t_last;
t_last = t_new;
float t = 0.0;
if ( t_diff > 0 )
{
t = 65536.0f / t_diff;
};
// Отобразим время и FPS на экране:
strcpy( buf, "Diff: " );
itostr( buf + 15, t_diff );
if ( mode3 )
draw3_micro_text( 0, 6 * 12, buf );
else
draw4_micro_text( 0, 6 * 12, buf );
strcpy( buf, "FPS: " );
itostr( buf + 15, (int) t );
if ( mode3 )
draw3_micro_text( 0, 6 * 13, buf );
else
draw4_micro_text( 0, 6 * 13, buf );
// Считаем текущее состояние кнопок и сразу
// инвертируем биты, чтобы нажатые стали единицами.
int keys = ~REG_KEYS;
// Реагируем на нажатые кнопки.
if ( keys & KEY_A )
wait_for_vblank = true;
if ( keys & KEY_B )
wait_for_vblank = false;
if ( keys & KEY_LEFT )
color--;
if ( keys & KEY_RIGHT )
color++;
if ( keys & KEY_L )
fill_by_color = true;
if ( keys & KEY_R )
fill_by_color = false;
if ( keys & KEY_UP )
need_mode3 = true;
if ( keys & KEY_DOWN )
need_mode3 = false;
// Если возникла необходимость переключить видеорежим - делаем это.
if ( mode3 != need_mode3 )
{
// Переключить видеорежим в 0x3 или 0x4.
mode3 = need_mode3;
if ( mode3 )
REG_DISPCNT = MODE_3 | BG2_ENABLE;
else
REG_DISPCNT = MODE_4 | BG2_ENABLE;
// Очистим экран после переключения
fill_screen_by_color( mode3, 0 );
};
// Если нужно - зальём видеопамять цветом заливки.
if ( fill_by_color )
{
fill_screen_by_color( mode3, color );
}
// Доводим синхронизацию по VLANK до конца.
if ( wait_for_vblank )
{
while ( REG_VCOUNT >= 160 );
};
};
return 0;
}
Далее создаём файл build_04_bitmap_modes.bat:
@SET MODULES=
@set PROGNAME=04_bitmap_modes
@call build_gba.bat
Если нигде не допущено ошибок, то должен появится файл 04_bitmap_modes.gba запустив который мы можем наглядно увидеть, что пиксельные видеорежимы 16-мегагерцовым ARM-ом GBA заливаются довольно медленно и их реальное применение в играх очень сильно ограничено. Видеоадаптер больше не может нам с ними ничем помочь — нет аппаратного ускорения блиттинга, как, например, в DirectDraw.
Поэтому по настоящему раскрывается консоль в тайловых видеорежимах, которые по сути являются аппаратным ускорением двумерной графики. Следующая часть будет про них.
Однако есть один забавный момент, который нельзя не отметить в этой новой редакции статьи — в следующих уже главах я обнаружу, что использование вещественных чисел (которая на GBA реализуется программной эмуляцией) насколько сильно роняет производительность даже в примитивных вещах, что даже просто подсчёт fps с их помощью в данном уроке должен сильно исказить достоверный результат — поэтому истинную картину предлагаю вам восстановить самостоятельно.
Продолжение...
4 комментария
Что-то здесь не то. Такой арм, навскидку, должен в кадре успевать залить такой маленький экранчик несколько раз. А тут либо компилятор сосёт, либо дикие задержки доступа к видеопамяти. Во всяком случае, причина явно не в 16 мегагерцах.
www.youtube.com/watch?v=t4hfPKWM4Mk
Doom в уменьшенном примерно до gba-шного размера окошке сносно шевелился на 386DX40. По моему опыту, уже самые первые армы примерно в 2+ раза эффективней по тактам были. Так что в «ужасном качестве» я склонен виноватить кривые ручки (программистов Дума, компилятора, или разработчиков железа геймбоя). В архимедовских демах, вон, даже воксельный ландшафт бодро бегал:
www.youtube.com/watch?v=bLdpCIfOqmw (с 17:50)