Программирование под PSP. Часть 1: использование glut
История
Доброго всем времени суток. Совсем недавно я стал счастливым обладателем устройства под названием PSP-3008. Я был просто поражен, когда увидел, сколько прыти помещается в столь малое и относительно старое устройство.
Естественно во мне заговорили программерские гены, и я захотел чего-нибудь написать под данную платформу. Меня ждало разочарования, когда в рунете я нашел лишь несколько уроков по настройке среды под хомбрю девкит и небольшие Hello World приложения. Непорядок.
Скачав MinPSPW, я улицезрел, что огромное количество библиотек портировано под данную платформу. Количество примеров хоть и не было большим, но они затрагивали почти все стороны. Конечно, программирование под голую систему интересно, но использование библиотек куда большее интересное и продуктивное занятие. Тем более что GU (родная графическая библиотека) очень схожа с OpenGL, но в отличие от OpenGL, немного неопрятная и нелогичная, как мне показалось.
У меня был выбор: использовать SDL+OpenGL или использовать glut+OpenGL. Я пошел по первому пути, но, увы, меня ждало разочарование. SDL напрочь отказывалась инициализироваться с флагом OpenGL. Поиск, естественно, не дал никаких результатов. Второй путь стал куда более продуктивным, так как по этой теме нашлись даже уроки от небезызвестного NeHe, хотя и здесь были свои подводные камни.
В данном посте я поведаю этот путь. Кому интересна эта тема – добро пожаловать под кат.
Что потребуется
Для того чтобы скомпилировать и запустить код из данного поста, вам потребуется:
- PSP с возможность запуска хомбрю приложений
- MinPSPW – комплект компиляторов под PSP
Начало. Код
Мы создадим с вами куб, который мы сможем вращать, приближать/отдалять, включать/выключать освещение и текстуры.
Я буду приводить фрагмент кода, и сразу же комментировать его.
Создаем файл main.cpp и начинаем:
Для начала подключим все необходимые хидеры:
//Хидеры PSP SDK #include <pspsdk.h> #include <pspkernel.h> //Хидеры GLUT и OpenGL #include <GL/glut.h> #include <GL/gl.h> #include <GL/glu.h> //Хидер на компонент библиотеки STL. Он понадобиться нам для парсинга BMP #include
Теперь несколько строчек, которые используются для настройки PSP приложения:
//Информация модуля //Первый атрибут – название модуля, второй – атрибуты модуля, третий и четвертый – версия модуля. PSP_MODULE_INFO("PSPOpenGL", PSP_MODULE_USER, 1, 0); //Атрибуты потока. Нам ничего особенного не надо. PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER); /* Колбек на выход из игры. Здесь мы сохраняем данные игры и чистим мусор. Данный кусок кода, по большому счету, перекочевывает из проекта в проект. Код меняется лишь в одном месте. */ int exit_callback(int arg1, int arg2, void *common) { //Здесь мы можем сохранить игру, к примеру //Меняется только этот фрагмент //Выходим sceKernelExitGame(); return 0; } /* Регистрируем наши колбеки */ int CallbackThread(SceSize args, void *argp) { int cbid; cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL); sceKernelRegisterExitCallback(cbid); sceKernelSleepThreadCB(); return 0; } /* Создаем поток, который будет обрабатывать колбеки */ int SetupCallbacks(void) { int thid = 0; thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0); if(thid >= 0) { sceKernelStartThread(thid, 0, 0); } return thid; }
Пару дефайнов и вспомогательные величины и переменные:
//Нужно для передачи колбеков как параметров #define glutcallback(func, attr) void (*func)(attr) #define abs(x) ((x)<0?-(x):(x)) //Углы для поворота и зум float xrot = 0, yrot = 30, zoom = 5; //Включен ли свет bool lighting = true; //Включен ли текстуринг bool texturing = true; //Кнопки bool keys[256];
Далее код очень схож с кодом на ПК. О различиях и замеченных камнях буду писать, остальные комментарии GL кода буду опускать.
Класс для загрузки BMP-изображений и их преобразования в GL-текстуры:
class Image { public: void loadFromBMP(std::string filename) { std::ifstream file; file.open(filename.c_str(), std::ios::binary); //Get Width file.seekg(18, std::ios_base::beg); width = (unsigned int)file.get(); //Get Height file.seekg(22, std::ios_base::beg); height = (unsigned int)file.get(); //Calculate Size size = width * height * 3; //Читаем данные file.seekg(51, std::ios_base::beg); data = new char[size]; char* tempdata = new char[size]; file.get(tempdata, size+1); char temp; for ( unsigned int i = 0; i < size; i++) data[i] = tempdata[size-i-1]; delete[] tempdata; //Спешу заметить, что GL_BGR не работает colordata = GL_RGB; colors = 3; //Создаем GL-текстуру createGLTexture(); //Чистим данные delete[] data; } GLuint texture; private: void createGLTexture() { glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, colors, width, height, 0, colordata, GL_UNSIGNED_BYTE, data); } unsigned int width, height, size, colors; GLenum colordata; char* data; }; //Наши текстуры для куба Image front, bottom, left;
Класс для инициализации графики и колбеки. Здесь почти нету ничего специфичного для платформы (кроме разрешения экрана и названия кнопок, которые я прокомментировал), поэтому код не буду усердно комментировать:
class Graphic { #define SCREENWIDTH 480 #define SCREENHEIGHT 272 public: //Наш конструктор Graphic(unsigned int mode) { initGlut(); initDisplayMode(mode); createScreen(); //Инициализируем кнопки for (unsigned int i = 0; i < 256; i++) keys[i] = false; } //Колбек на отрисовку void setDrawingCallback(glutcallback(drawcallback, void)) { glutDisplayFunc(drawcallback); glutIdleFunc(drawcallback); } //Инициализируем OpenGL. Данный фрагмент опущу void initGL() { glEnable( GL_TEXTURE_2D ); glShadeModel( GL_SMOOTH ); glClearColor( 0, 0, 0, 0 ); glClearDepth( 1 ); glEnable( GL_DEPTH_TEST ); glDepthFunc( GL_LEQUAL ); glViewport( 0, 0, SCREENWIDTH, SCREENHEIGHT ); glMatrixMode( GL_PROJECTION ); glLoadIdentity(); gluPerspective( 45, (GLfloat)SCREENWIDTH/(GLfloat)SCREENHEIGHT, 0.1, 100 ); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); } //Начинаем главный цикл void start() { startMainLoop(); } private: //Инициализируем glut void initGlut() { glutInit(NULL, NULL); } //Устанавливаем дополнительные параметры отображений void initDisplayMode(unsigned int mode) { glutInitDisplayMode(mode); } //Создадим наш экран void createScreen() { //Установим позицию экрана и его размеры glutInitWindowPosition(0, 0); glutInitWindowSize(SCREENWIDTH, SCREENHEIGHT); //Создаем наш экран screen = glutCreateWindow(NULL); } //Запускаем главный цикл void startMainLoop() { glutMainLoop(); } //Наш экран int screen; }; void onDraw() { //Очищаем экран glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); //Если включен свет if (lighting) glEnable( GL_LIGHTING ); else glDisable( GL_LIGHTING ); //Если включен текстуринг if (texturing) glEnable( GL_TEXTURE_2D ); else glDisable( GL_TEXTURE_2D ); //Рисуем здесь glTranslatef( 0, 0, -zoom ); glRotatef( yrot, 1, 0, 0 ); glRotatef( xrot, 0, 1, 0 ); glBindTexture(GL_TEXTURE_2D, front.texture); glBegin(GL_QUADS); //Перед glNormal3f( 0, 0, 1 ); glTexCoord2f(0, 0); glVertex3f( -1, 1, 1 ); glTexCoord2f(1, 0); glVertex3f( 1, 1, 1 ); glTexCoord2f(1, 1); glVertex3f( 1,-1, 1 ); glTexCoord2f(0, 1); glVertex3f( -1,-1, 1 ); //Зад glNormal3f( 0, 0, -1 ); glTexCoord2f(0, 0); glVertex3f( 1, 1, -1 ); glTexCoord2f(1, 0); glVertex3f( -1, 1, -1 ); glTexCoord2f(1, 1); glVertex3f( -1, -1, -1 ); glTexCoord2f(0, 1); glVertex3f( 1, -1, -1 ); glEnd(); glBindTexture(GL_TEXTURE_2D, bottom.texture); glBegin(GL_QUADS); //Верх glNormal3f( 0, 1, 0 ); glTexCoord2f(0, 0); glVertex3f( -1, 1, -1 ); glTexCoord2f(1, 0); glVertex3f( 1, 1, -1 ); glTexCoord2f(1, 1); glVertex3f( 1, 1, 1 ); glTexCoord2f(0, 1); glVertex3f( -1, 1, 1 ); //Низ glNormal3f( 0, -1, 0 ); glTexCoord2f(0, 0); glVertex3f( -1, -1, 1 ); glTexCoord2f(1, 0); glVertex3f( 1, -1, 1 ); glTexCoord2f(1, 1); glVertex3f( 1, -1, -1 ); glTexCoord2f(0, 1); glVertex3f( -1, -1, -1 ); glEnd(); glBindTexture(GL_TEXTURE_2D, left.texture); glBegin(GL_QUADS); //Правая сторона glNormal3f( 1, 0, 0 ); glTexCoord2f(0, 0); glVertex3f( 1, 1, 1 ); glTexCoord2f(1, 0); glVertex3f( 1, 1, -1 ); glTexCoord2f(1, 1); glVertex3f( 1, -1, -1 ); glTexCoord2f(0, 1); glVertex3f( 1, -1, 1 ); //Левая сторона glNormal3f( -1, 0, 0 ); glTexCoord2f(0, 0); glVertex3f( -1, 1, -1 ); glTexCoord2f(1, 0); glVertex3f( -1, 1, 1 ); glTexCoord2f(1, 1); glVertex3f( -1, -1, 1 ); glTexCoord2f(0, 1); glVertex3f( -1, -1, -1 ); glEnd(); //Выводим glutSwapBuffers(); } void onJoystick(unsigned int buttonMask, int x, int y, int z) { //Порог, ниже которого получаются ложные срабатывания const int threshold = 150; if (abs(x) > threshold) { xrot += x/100; } if (abs(y) > threshold) { yrot += y/100; } //Фиксеры while (xrot>=360) xrot -= 360; if (yrot>90) yrot = 90; if (yrot<-90) yrot = -90; } void onShift(int button, int state, int x, int y) { if (button == GLUT_LEFT_BUTTON)//Левый шифт { if (state == GLUT_DOWN)//Нажат { zoom += 0.2; } if (state == GLUT_UP)//Отпущен { } } if (button == GLUT_RIGHT_BUTTON)//Правый шифт { if (state == GLUT_DOWN)//Нажат { zoom -= 0.2; //Фиксер if (zoom<0) zoom = 0; } if (state == GLUT_DOWN)//Отпущен { } } } void onKeyDown(unsigned char key, int x, int y) { switch (key) { case 'd': //Треугольник break; case 'o': //Круг if (keys[key] == false) lighting = !lighting; break; case 'q': //Квадрат if (keys[key] == false) texturing = !texturing; break; case 'x': //Крест break; case 'n': //Нотка break; case 's': //Select break; case 'a': //Start break; default: break; } keys[key] = true; } void onKeyUp(unsigned char key, int x, int y) { switch (key) { case 'd': //Треугольник break; case 'o': //Круг break; case 'q': //Квадрат break; case 'x': //Крест break; case 'n': //Нотка break; case 's': //Select break; case 'a': //Start break; default: break; } keys[key] = false; } void onSpecialKeyDown(int key, int x, int y) { switch (key) { case GLUT_KEY_UP: //Стрелка вверх break; case GLUT_KEY_DOWN: //Стрелка вниз break; case GLUT_KEY_LEFT: //Стрелка влево break; case GLUT_KEY_RIGHT: //Стрелка вправо break; case GLUT_KEY_HOME: //Кнопка PSP (Home) break; default: break; } } void onSpecialKeyUp(int key, int x, int y) { switch (key) { case GLUT_KEY_UP: //Стрелка вверх break; case GLUT_KEY_DOWN: //Стрелка вниз break; case GLUT_KEY_LEFT: //Стрелка влево break; case GLUT_KEY_RIGHT: //Стрелка вправо break; case GLUT_KEY_HOME: //Кнопка PSP (Home) break; default: break; } }
И наша главная функция:
int main() { //Ставим колбеки SetupCallbacks(); //Инициализируем графику Graphic graphic(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH); //Ставим колбек graphic.setDrawingCallback(onDraw); //Инициализируем OpenGL graphic.initGL(); //Грузим текстуры front.loadFromBMP("textures/front.bmp"); bottom.loadFromBMP("textures/bottom.bmp"); left.loadFromBMP("textures/left.bmp"); //Ставим свет GLfloat LightAmbient[] = { 1, 1, 1, 1 }; GLfloat LightDiffuse[] = { 1, 1, 1, 1 }; GLfloat LightPosition[] = { 0, 0, 2, 1 }; glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient); glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse); glLightfv(GL_LIGHT1, GL_POSITION, LightPosition); glEnable(GL_LIGHT1); //Регистрируем колбек на движение джойстика glutJoystickFunc(&onJoystick, 0); //Регистрируем колбеки шифтов glutMouseFunc(&onShift); //Регистрируем колбеки на кнопки glutKeyboardFunc(&onKeyDown); glutKeyboardUpFunc(&onKeyUp); //Дополнительные кнопки, типа стрелок, кнопки PSP (Home) glutSpecialFunc(&onSpecialKeyDown); glutSpecialUpFunc(&onSpecialKeyUp); //Начинаем главный цикл graphic.start(); //Выходим из игры по завершению главного цилка sceKernelExitGame(); return 0; }
Вот и все ничего сложного.
Собираем
Сборка осуществляется простым make-файлом:
TARGET = PSPOpenGL OBJS = main.o INCDIR = CFLAGS = -O2 -G0 -Wall CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti ASFLAGS = $(CFLAGS) LIBDIR = LDFLAGS = LIBS = -lglut -lGLU -lGL -lm -lc -lpsputility -lpspdebug -lpspge -lpspdisplay -lpspctrl -lpspsdk -lpspvfpu -lpsplibc -lpspuser -lpspkernel -lpsprtc -lpsppower -lstdc++ EXTRA_TARGETS = EBOOT.PBP PSP_EBOOT_TITLE = PSP OpenGL PSPSDK=$(shell psp-config --pspsdk-path) include $(PSPSDK)/lib/build.mak
И далее вызовом утилиты make из комплекта MinPSPW.
Заключение. Размышление на тему
Конечно, в моем коде получилась хромая реализация ООП, обычно я все разношу по файлам и классам. Здесь же я специально не сделал этого, чтобы все было наглядно и на виду.
Безусловно, несмотря на то, что скоро выходит новая платформа от разработчика (NGP), PSP ещё найдет себе применение.
Родной API очень богат (хотя моим кодом он почти не затронут), хотя и мало документирован. Например, есть даже раздел API, который позволяет создавать HTTP и даже HTTPS соединения с поддержкой Cookies и запросов. Так же есть возможность написания драйверов под USB-устройства. Все это не может не радовать. GU (как уже упоминал выше, родная графическая библиотека) имеет очень много возможностей, таких как цел-шейдинг, отражения, тени и даже тесселяцию. В будущем надеюсь со всем этим разобраться.
Список используемых материалов
При подготовке статьи использовались части кода из порта уроков NeHe на PSP и MinPSPW.