Pull to refresh

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

История


Доброго всем времени суток. Совсем недавно я стал счастливым обладателем устройства под названием 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.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.