Как стать автором
Обновить
520.44
YADRO
Тут про железо и инженерную культуру

Когда хочется портировать старые игры, но нет ни сил, ни времени: запускаем отсекатель лучей на RISC-V RV32I

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров824

Всех, кто работает с софт-ядрами, наверное, можно разделить на две категории: первые хотят запустить на своем ядре Linux, вторые — DOOM. Я отношусь ко второй: идея запустить DOOM на ядре YRV, что я синтезирую на отладках, любезно предоставленных FPGA-Systems.ru, преследует меня постоянно и не дает спать.

Как запустить DOOM с ходу, непонятно, ведь у меня все-таки некоммерческий микроконтроллер. Поэтому начну с подготовительного упражнения, чтобы понять, что возможно реализовать на аппаратной части. Когда мы говорим про DOOM, то вспоминаем другие игры любимой компании id Software — Wolfenstein 3D, Catacomb 3D и Hovertank 3D. В них все начинается с алгоритма отсечения лучей (raycasting), с которым и будем поработать. С raycasting можно получить и doom-образный геймплей, надо лишь улучшить разрешение и текстуры.

Мое знакомство с этим алгоритмом началось с книги Ла Мота А., Ратклиффа Д., Семинаторе М. «Секреты программирования игр». Сейчас по фразе raycasting на Хабре и Youtube вы найдете множество реализаций этого алгоритма. Там же, кстати, и объясняют, почему в DOOM отсечение лучей не используется. Тогда для меня эта книга была открытием, да и сейчас я периодически использую приемы оттуда для программирования всяких графических штук для ядра YRV.

Запускаем отсекатель лучей под Windows

Итак, необходимо портировать алгоритм трехмерной графики, который когда-то компилировался и работал под MS-DOS. А теперь должен быть скомпилирован и запущен на RISC-V-ядре YRV, синтезированном на плате DE0-CV. Я возьму алгоритм из шестой главы книги, о которой говорил выше (RAY.C). Это плохой пример с точки зрения быстродействия, так как он использует плавающую точку и, значит, будет медленным. Но хорошим с точки зрения портирования, так как не использует текстуры. Тем более моя задача, как я сказал выше, — портировать существующий код.

Иллюстрация из книги «Секреты программирования игр», с. 200
Иллюстрация из книги «Секреты программирования игр», с. 200

Сейчас возиться в DOSbox с компилятором Microsoft C у меня нет никакого желания. Да и не видал я в своем детстве компилятор Microsoft C — у меня только Turbo C от компании Borland и Microsoft QuickC был. Поэтому я портирую этот пример под SDL по инструкции.

Для портирования надо поправить указатели, чтобы были без всяких far. Кроме этого, вся работа сводится к написанию заглушек на SDL для следующих функций:

void setcolor(int newcolor);
void _moveto(int x1, int y1);
int _setpixel(int x, int y);
int _lineto(int x2, int y2);
int _rectangle(int order, int x1, int y1, int x2, int y2);

Реализация видеоадаптера на плате DE0-CV — это QVGA-адаптер с разрешением 320х240, где точка получается делением пополам из обычного VGA 640x480 (пример лабораторных работ Юрия Панчула). Я модифицировал пример под разрешение 320х240, убрал шрифты, а переключение в режим обзора/карты будет работать по клавише M.

Пример режима обзора (по кислотности палитры, кажется, я переплюнул HoverTank)
Пример режима обзора (по кислотности палитры, кажется, я переплюнул HoverTank)

Так как в тулчейне GCC для YRV нет синуса, косинуса и тангенса, то реализацию их вычисления я также добавил в исходник. Как вычислить при помощи CORDIC синус на RV32I, я рассмотрел в статье «Стандарт RISC-V RV32I и математика с плавающей точкой», так что с портированием этой части проблем быть не должно.

Режим карты. Она влезла и даже немного похожа на настоящую игру.(в Wolfenstein 3d карты же не было?)
Режим карты. Она влезла и даже немного похожа на настоящую игру.(в Wolfenstein 3d карты же не было?)

Так как в YRV нет файловой системы, то карту сразу уберем в массив:

char world[WORLD_ROWS][WORLD_COLUMNS] = { 
	{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,1,1,1,1,1,0,0,0,0,0,0,0,1},
	{1,0,0,1,0,0,0,1,0,1,0,1,0,1,0,1},
	{1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1},
	{1,0,0,1,0,0,1,1,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,1,1,0,0,1,1,1,1,1,1,0,0,1},
	{1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1},
	{1,0,0,1,1,1,0,0,0,0,0,0,1,0,0,1},
	{1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,1},
	{1,0,0,1,1,1,1,1,1,1,1,0,1,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};    

Плавающая точка есть, тригонометрию посчитали, файлы убрали. Причин, чтобы этот код не запустился на YRV, я не вижу. Исправленный пример лежит на GitHub.

Переносим все на GCC RV32I

Все необходимые операции по портированию были сделаны еще под Windows. Осталось избавиться от SDL: для этого необходимо реализовать те же графические функции, что и для SDL.

void setcolor(int newcolor);
void _moveto(int x1, int y1);
int _setpixel(int x, int y);
int _lineto(int x2, int y2);
int _rectangle(int order, int x1, int y1, int x2, int y2);

Так как адресация видеоадаптера такая же, как и VGA-режим 13h, то все приемы по работе с графическими примитивами можно взять из той же книги Андре Ла Мота. Нужно лишь учесть, что отсекатель луча рисует прямой отрезок снизу вверх, поэтому никакой алгоритм Брезенхема для рисования линии в самом отсекателе лучей не нужен. Для этого сделаем функцию _linetoh(x,y), которая рисует линию вверх — так должно работать побыстрей.

Синусы и косинусы считаем в начале, добавляем в таблицу. Поэтому для оценки объема оперативной памяти заполним таблички 1.0f — тогда компилятор перенесет их в раздел .rodata:

float tan_table[1921] = {1.0f};
float inv_tan_table[1921]= {1.0f};
float y_step[1921]= {1.0f};
float x_step[1921]= {1.0f}; 
float cos_table[1921]= {1.0f}; 
float inv_cos_table[1921]= {1.0f}; 
float inv_sin_table[1921]= {1.0f};
float cos_local[361]= {1.0f};
float sin_local[361]= {1.0f};

Итоговый размер бинарника — 92 кБ, а значит, необходимо в проект YRV Plus добавить памяти. 

Аппаратное обеспечение

Для реализации использую свой проект «Ретро-компьютер уровня "Радио-86РК" с RISC-V процессором на плате OMDAZZ», но используем более мощную отладку DE0-CV. Чтобы все собралось, нужно установить Quartus, GCC, а также Python 3.x.

Плата DE0-CV содержит чип Cyclone V, который позволяет синтезировать 3080 кбит BRAM, так что нам хватит и на видеоадаптер, и на оперативную память (здесь вполне достаточно 128 кБ). Также надо поменять условие в селекторе шины AHB-Lite, чтобы работали порты ввода/вывода, так как нам понадобится клавиатура.

Поскольку объем загружаемого бинарника вырос, необходимо исправить задержку в модуле BOOT_HEX_PARSER. Сделаем ее равной 10 секунд: так наша система будет грузиться за 10 секунд. 

Работа с клавиатурой реализована ровно так же, как и в проекте «Ретро-компьютер уровня "Радио-86РК" с RISC-V процессором на плате OMDAZZ». Оттуда я взял модули работы с PS/2 без изменений.

Результирующий проект с YRV Plus и кодом отсекателя лучей я разместил на GitHub. Так как это проект я собирал под WIndows, то загрузка бинарника производится командой load.bat, что находится в одной директории с исходниками отсекателя лучей (programs/05_warlock). В файлике разве что надо поменять номер COM-порта.

Собираем и запускаем

Демонстрацию работы raycaster я выложил на Youtube.

Выводы

При перерисовке изображение полосует. Очевидно, нужен второй банк видеопамяти, а вообще скорости вполне хватает. Оперативной памяти сейчас недостаточно, но дополнительного мегабайта, наверно, хватит — надо что-то решать с микросхемами памяти.

Разрешение QVGA (320×240) сейчас бесполезное, так как все мониторы широкоугольные. Поэтому вне зависимости от того, 200 или 240 у нас строк, поля сбоку все равно будут. Чтобы выглядело нормально, надо сделать отступ сверху. Если оставить 200 строк, то оперативной памяти хватит на два банка.

Главный вывод: дорогу осилит идущий. Для начала надо портировать Hovertank One — на него ресурсов точно хватит, и его исходники открыты, как и у DOOM.

Теги:
Хабы:
+22
Комментарии2

Публикации

Информация

Сайт
yadro.com
Дата регистрации
Дата основания
Численность
5 001–10 000 человек
Местоположение
Россия
Представитель
Ульяна Соловьева