Программирование под PSP. Часть 1: использование glut

7 апреля 2011 г.

История

Доброго всем времени суток. Совсем недавно я стал счастливым обладателем устройства под названием PSP-3008. Я был просто поражен, когда увидел, сколько прыти помещается в столь малое и относительно старое устройство.

Естественно во мне заговорили программерские гены, и я захотел чего-нибудь написать под данную платформу. Меня ждало разочарования, когда в рунете я нашел лишь несколько уроков по настройке среды под хомбрю девкит и небольшие Hello World приложения. Непорядок.

Скачав MinPSPW, я улицезрел, что огромное количество библиотек портировано под данную платформу. Количество примеров хоть и не было большим, но они затрагивали почти все стороны. Конечно, программирование под голую систему интересно, но использование библиотек куда большее интересное и продуктивное занятие. Тем более что GU (родная графическая библиотека) очень схожа с OpenGL, но в отличие от OpenGL, немного неопрятная и нелогичная, как мне показалось.

У меня был выбор: использовать SDL+OpenGL или использовать glut+OpenGL. Я пошел по первому пути, но, увы, меня ждало разочарование. SDL напрочь отказывалась инициализироваться с флагом OpenGL. Поиск, естественно, не дал никаких результатов. Второй путь стал куда более продуктивным, так как по этой теме нашлись даже уроки от небезызвестного NeHe, хотя и здесь были свои подводные камни.

В данном посте я поведаю этот путь. Кому интересна эта тема – добро пожаловать под кат.

Что потребуется

Для того чтобы скомпилировать и запустить код из данного поста, вам потребуется:

  1. PSP с возможность запуска хомбрю приложений
  2. 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.

Теги: рубрика Программирование
  • Похожие статьи
  • Предыдущие из рубрики